目录
枪口发射调试射线
在武器类中
GetSocketTransform(socketname):通过插槽名获取transform
SocketTransform.GetLocation():获取变换的location
SocketTransform.GetRotation().GetForwardVector():获取向前向量
DrawDebugLine:调试射线:参数;World,开始位置,结束位置,颜色,判断射线是否永久绘制(默认false),存在时间,绘制深度的顺序,粗细
FHitResult:用于接收检测信息的结构体
GetWorld()->LineTraceSingleByChannel():射线检测:参数:包含交叉点的信息,开始位置,结束位置,需要检测的碰撞类型
#include "Engine/World.h" #include "DrawDebugHelpers.h" void ASTUBaseWeapon::MakeShot() { if (!GetWorld()) return; const FTransform SocketTransform = WeaponMesh->GetSocketTransform(MuzzleSocketName); //起始位置 const FVector TraceStart = SocketTransform.GetLocation(); //发射方向 const FVector ShootDirection = SocketTransform.GetRotation().GetForwardVector(); //终点位置 const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance; //绘制调试射线 参数:World,开始位置,结束位置,颜色,判断射线是否永久绘制(默认false),存在时间,绘制深度的顺序,粗细 DrawDebugLine(GetWorld(),TraceStart,TraceEnd,FColor::Red,false,3.0f,0,3.0f); FHitResult HitResult; FCollisionQueryParams CollisionParams; //添加射线检测忽略对象 CollisionParams.AddIgnoredActor(Player); //射线检测 参数:包含交叉点的信息,开始位置,结束位置,需要检测的碰撞类型 GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility,CollisionParams); //有检测到 if (HitResult.bBlockingHit) { //绘制球形调试 DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 3.0f); } }
将碰撞类型修改为Visibility可被阻挡(因为代码中设置的是这个类型,可以自己修改成不同的),
武器添加插槽,设置旋转
效果如下:
枪口发射射线
但是上面实现的方法还有问题,会导致射线打中角色之后还是会穿透过去,以及当相机视角向下时,射线依旧是从枪口向前发射,所以需要进行修改
需要使用相机的Transform来替换枪口的Transform,重新进行射线检测
Controller->GetPlayerViewPoint();输出摄像机检测到的位置
FMath::DegreesToRadians():角度转弧度FMath::VRandCone(Dir,halfAngle): 在一个圆锥中获得方向,可以用于实现因为后坐力导致弹道的偏移
FCollisionQueryParams CollisionParams;
CollisionParams.AddIgnoredActor(Player);添加忽略对象#include "Engine/World.h" #include "DrawDebugHelpers.h" void ASTUBaseWeapon::MakeShot() { if (!GetWorld()) return; const ACharacter* Player = Cast<ACharacter>(GetOwner()); if (!Player) return; const AController* Controller = Player->GetController<APlayerController>(); if (!Controller) return; FVector ViewLocation; FRotator ViewRotation; //输出摄像机检测到的loaction,rotation Controller->GetPlayerViewPoint(ViewLocation,ViewRotation); const FTransform SocketTransform = WeaponMesh->GetSocketTransform(MuzzleSocketName); //位置修改成相机的 //起始位置 const FVector TraceStart = ViewLocation; //SocketTransform.GetLocation(); //发射方向 //FMath::VRandCone 在一个圆锥中获得方向 const float HalfRad = FMath::DegreesToRadians(1.5f); const FVector ShootDirection = FMath::VRandCone(ViewRotation.Vector(),HalfRad);//SocketTransform.GetRotation().GetForwardVector(); //终点位置 const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance; //通过摄像机的旋转来检测 FHitResult HitResult; FCollisionQueryParams CollisionParams; //添加射线检测忽略对象 CollisionParams.AddIgnoredActor(Player); //射线检测 参数:包含交叉点的信息,开始位置,结束位置,需要检测的碰撞类型 GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility,CollisionParams); //有检测到 if (HitResult.bBlockingHit) { //绘制一条枪口到检测点的射线 //绘制调试射线 参数:World,开始位置,结束位置,颜色,判断射线是否永久绘制(默认false),存在时间,绘制深度的顺序,粗细 DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), HitResult.ImpactPoint, FColor::Red, false, 3.0f, 0, 3.0f); //绘制球形调试 DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 10.0f, 24, FColor::Red, false, 3.0f); } else { //绘制一条枪口到终点的射线 //绘制调试射线 参数:World,开始位置,结束位置,颜色,判断射线是否永久绘制(默认false),存在时间,绘制深度的顺序,粗细 DrawDebugLine(GetWorld(), SocketTransform.GetLocation(), TraceEnd, FColor::Red, false, 3.0f, 0, 3.0f); } }
修改后:
修改后的射线检测
发射子弹
对上面的代码进行整理,屏蔽掉调试射线
创建一个子弹类 TSubclassof<>()
class ASTUProjectile; UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) TSubclassOf<ASTUProjectile> ProjectileClass;
实现逻辑:
GetSafeNormal():归一化,获得单位向量
生成Actor的函数:
GetWorld()->SpawnActor<>():只适用于默认构造函数的情况,不能传参数
GetWorld()->SpawnActorDeferred<>()与FinishSpawning()搭配,可以在执行Actor的BeginPlay()前执行Actor的函数或者传参
UGameplayStatics::BeginDeferredActorSpawnFromClass()与UGameplayStatics::FinishSpawningActor()搭配,也是可以用于传参数
#include "Engine/World.h" #include "DrawDebugHelpers.h" #include "Kismet/GameplayStatics.h" void ASTUBaseWeapon::MakeShot() { if (!GetWorld()) return; const ACharacter* Player = Cast<ACharacter>(GetOwner()); if (!Player) return; const AController* Controller = Player->GetController<APlayerController>(); if (!Controller) return; FVector ViewLocation; FRotator ViewRotation; //输出摄像机检测到的loaction,rotation Controller->GetPlayerViewPoint(ViewLocation,ViewRotation); const FTransform SocketTransform = WeaponMesh->GetSocketTransform(MuzzleSocketName); //位置修改成相机的 //起始位置 const FVector TraceStart = ViewLocation; //SocketTransform.GetLocation(); //发射方向 //FMath::VRandCone 在一个圆锥中获得方向 const float HalfRad = FMath::DegreesToRadians(1.5f); const FVector ShootDirection = FMath::VRandCone(ViewRotation.Vector(),HalfRad);//SocketTransform.GetRotation().GetForwardVector(); //终点位置 const FVector TraceEnd = TraceStart + ShootDirection * TraceMaxDistance; //通过摄像机的旋转来检测 FHitResult HitResult; FCollisionQueryParams CollisionParams; //添加射线检测忽略对象 CollisionParams.AddIgnoredActor(Player); //射线检测 参数:包含交叉点的信息,开始位置,结束位置,需要检测的碰撞类型 GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility,CollisionParams); //发射子弹 //计算子弹的终点位置和方向 const FVector EndPoint = HitResult.bBlockingHit ? HitResult.ImpactPoint : TraceEnd; const FVector Direction = (EndPoint - SocketTransform).GetSafeNormal(); //子弹的生成位置(枪口) const FTransform SpawnTransform = FTransform(FRotator::ZeroRotator, SocketTransform); //ASTUProjectile* Projectile = GetWorld()->SpawnActor<ASTUProjectile>(ProjectileClass, SpawnTransform); //ASTUProjectile* Projectile = GetWorld()->SpawnActorDeferred<ASTUProjectile>(ProjectileClass, SpawnTransform); //Projectile->FinishSpawning(SpawnTransform); //生成子弹 ASTUProjectile* Projectile = Cast<ASTUProjectile>(UGameplayStatics::BeginDeferredActorSpawnFromClass(GetWorld(), ProjectileClass, SpawnTransform)); if (Projectile) { //将方向传递给子弹类 Projectile->SetShotDirection(Direction); UGameplayStatics::FinishSpawningActor(Projectile, SpawnTransform); } }
在子弹类中创建一个球形碰撞体USphereComponent以及物理模拟组件UProjectileMovementComponent
class USphereComponent; class UProjectileMovementComponent; void SetShotDirection(const FVector& Direction) { ShotDirection = Direction; }; UPROPERTY(VisibleDefaultsOnly, Category = "Weapon") USphereComponent* CollisionComponent; UPROPERTY(VisibleDefaultsOnly, Category = "Weapon") UProjectileMovementComponent* ProjectileMovementComponent; FVector ShotDirection;
设置子弹的发射
ASTUProjectile::ASTUProjectile() { PrimaryActorTick.bCanEverTick = false; CollisionComponent = CreateDefaultSubobject<USphereComponent>("CollisionComponent"); //初始碰撞体的半径 CollisionComponent->InitSphereRadius(5.0f); SetRootComponent(CollisionComponent); ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>("ProjectileMovementComponent"); } void ASTUProjectile::BeginPlay() { Super::BeginPlay(); check(ProjectileMovementComponent); ProjectileMovementComponent->Velocity = ShotDirection * ProjectileMovementComponent->InitialSpeed; SetLifeSpan(5.0f); }
在蓝图中设置速度和重力影响
也可以通过代码控制
ProjectileMovementComponent->InitialSpeed = 1000.0f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
效果:
发射子弹
子弹造成伤害
设置子弹碰撞体的碰撞
创建一些变量方便来蓝图修改
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Weapon",meta =(ClampMin= "0")) float DamageRadius = 200.0f; UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Weapon",meta = (ClampMin = "0")) float DamageAmount = 50.0f; UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Weapon") bool DoFullDamage = false; UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Weapon", meta = (ClampMin = "0")) float LifeSeconds = 5.0f; UFUNCTION() void OnProjectileHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
SetCollisionEnabled(ECollisionEnabled::类型):设置碰撞启用类型
SetCollisionResponseToAllChannels(ECollisionResponse::类型):设置所有通道的碰撞响应类型
IgnoreActorWhenMoving():添加子弹在飞行过程中的忽略对象,这里是为了防止对角色自身造成伤害
OnComponentHit.AddDynamic():订阅 委托,在碰撞体中有许多的委托
StopMovementImmediately():命中目标后停止移动
UGameplayStatics::ApplyRadialDamage():范围伤害接口,还有PointDamage,ApplyDamage等
DrawDebugSphere():绘制调试球形
#include "Components/SphereComponent.h" #include "Gameframework/ProjectileMovementComponent.h" #include "Kismet/GameplayStatics.h" #include "DrawDebugHelpers.h" ASTUProjectile::ASTUProjectile() { PrimaryActorTick.bCanEverTick = false; CollisionComponent = CreateDefaultSubobject<USphereComponent>("CollisionComponent"); //初始碰撞体的半径 CollisionComponent->InitSphereRadius(5.0f); //设置碰撞启用为纯扫描 CollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly); //设置获取所有通道的碰撞响应为阻挡 CollisionComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block); SetRootComponent(CollisionComponent); ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>("ProjectileMovementComponent"); ProjectileMovementComponent->InitialSpeed = 2000.0f; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } void ASTUProjectile::BeginPlay() { Super::BeginPlay(); check(ProjectileMovementComponent); check(CollisionComponent); ProjectileMovementComponent->Velocity = ShotDirection * ProjectileMovementComponent->InitialSpeed; //忽略owner CollisionComponent->IgnoreActorWhenMoving(GetOwner(),true); //订阅委托,命中目标 CollisionComponent->OnComponentHit.AddDynamic(this, &ASTUProjectile::OnProjectileHit); SetLifeSpan(LifeSeconds); } void ASTUProjectile::OnProjectileHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) { if (!GetWorld()) { return; } //停止移动 ProjectileMovementComponent->StopMovementImmediately(); //范围伤害接口 UGameplayStatics::ApplyRadialDamage ( GetWorld(), DamageAmount, GetActorLocation(), DamageRadius, UDamageType::StaticClass(), { GetOwner() }, this, GetController(), DoFullDamage); //在击中点绘制一个球形 DrawDebugSphere(GetWorld(), GetActorLocation(), DamageRadius, 24, FColor::Red, false, 3.0f); } AController* ASTUProjectile::GetController() { APawn* Pawn = Cast<APawn>(GetOwner()); return Pawn ? Pawn->GetController() : nullptr; }
对应在蓝图中的函数
SetCollisionEnabled:
OnComponentHit.AddDynamic:
实现效果:
子弹造成伤害