炮弹类
在之前的文章中,我们已经实现了玩家坦克和敌方炮塔的开火逻辑。但是我们只是调试绘制了炮弹的初始生成位置。在这一篇文章中,我们将实现:在开火时显示炮弹 Mesh,并发射移动。
和坦克和炮塔类似,我们也要创建内置类承载炮弹的 Mesh。这正好让我们重新复习一下之前的创建流程。
如图 1 所示,我们选择菜单 工具/新建C++类。然后此处父类选择 Actor 就足够了,因为我们的炮弹不需要接控制器。
我们将炮弹类命名为 Projectile,具体的实现如代码清单 1 和清单 2 所示,我们使用 UStaticMeshComponent 组件承载 Mesh,并使用 CreateDefaultSubobject() 函数创建它。然后将其设置为根组件。
此处我们关闭 Actor 内每帧 Tick() 函数的执行。因为我们后续会引入其他组件控制炮弹的移动,关闭可以提高性能。
- UCLASS()
- class TOONTANKS_API AProjectile : public AActor
- {
- GENERATED_BODY()
- private:
- UPROPERTY(EditDefaultsOnly, Category = "Combat")
- UStaticMeshComponent* ProjectileMesh;
- };
- AProjectile::AProjectile()
- {
- // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
- // PrimaryActorTick.bCanEverTick = true;
- PrimaryActorTick.bCanEverTick = false;
- ProjectileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Projectile Mesh"));
- RootComponent = ProjectileMesh;
- }
接着,如图 2 所示,我们可以基于创建的炮弹 C++ 类,再创建一个蓝图类。然后在蓝图类的 UStaticMeshComponent 组件里指定 Mesh。
完成好炮弹类之后,我们需要在游戏运行时动态创建这个 Actor 实例。我们需要使用 SpawnActor() 函数来创建。其函数原型为:
- template< class T >
- T* SpawnActor(
- UClass* Class,
- FVector const& Location,
- FRotator const& Rotation,
- const FActorSpawnParameters& SpawnParameters = FActorSpawnParameters() );
其中,Class 参数指定实际要生成的类对象;Location 参数指定生成的位置;Rotation 参数指定生成的旋转角度;SpawnParameters 参数指定其他生成细节。
因为我们要生成的炮弹类是蓝图类,所以我们需要在编辑器中指定。如代码清单 3 所示,我们创建变量供编辑器里指定。
TSubclassOf 是带类型检查的 UClass 类。UClass 类是其他类的“说明书”。
- UCLASS()
- class TOONTANKS_API ABasePawn : public APawn
- {
- GENERATED_BODY()
- private:
- UPROPERTY(EditDefaultsOnly, Category = "Combat")
- TSubclassOf<AProjectile> ProjectileClass;
- };
在编辑器中,我们将 ProjectileClass 指定为我们创建好的炮弹蓝图类。接着如代码清单 4 所示,我们修改原先的开火函数,使其使用 SpawnActor() 函数生成炮弹。
- void ABasePawn::Fire()
- {
- FVector Location = ProjectileSpawnPoint->GetComponentLocation();
- FRotator Rotation = ProjectileSpawnPoint->GetComponentRotation();
- GetWorld()->SpawnActor<AProjectile>(ProjectileClass, Location, Rotation);
- }
现在生成的炮弹是静止不动的,我们还需要使其运动起来。我们可以自己在 Tick 函数里指定炮弹的位置,或者开启物理模拟作用冲量。但还有一种很方便的方法,就是使用 UProjectileMovementComponent 组件,它是 Unreal Engine 中专门用于处理投射物运动逻辑的组件。
如代码清单 5 和清单 6 所示,我们添加并创建 UProjectileMovementComponent 组件。然后初始化一些参数:InitialSpeed 指定投射物生成时的初始速度;MaxSpeed 指定投射物允许达到的最大速度。
- UCLASS()
- class TOONTANKS_API AProjectile : public AActor
- {
- GENERATED_BODY()
- private:
- UPROPERTY(VisibleAnywhere, Category = "Movement")
- UProjectileMovementComponent* ProjectileMovementComponent;
- };
- AProjectile::AProjectile()
- {
- // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
- // PrimaryActorTick.bCanEverTick = true;
- PrimaryActorTick.bCanEverTick = false;
- ProjectileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Projectile Mesh"));
- RootComponent = ProjectileMesh;
- ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Projectile Movement Component"));
- ProjectileMovementComponent->MaxSpeed = 1300.0f;
- ProjectileMovementComponent->InitialSpeed = 1300.0f;
- }
目前炮弹运行的逻辑是,遇到碰撞就停止不动了,后续我们会添加爆炸特效,并使导弹消失。在此之前,我们需要知道炮弹何时发生了碰撞。
我们可以按照代码清单 7 第 5 行的方式添加碰撞的“回调函数”。
- void AProjectile::BeginPlay()
- {
- Super::BeginPlay();
- ProjectileMesh->OnComponentHit.AddDynamic(this, &AProjectile::OnHit);
- }
- void AProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
- {
- UE_LOG(LogTemp, Warning, TEXT("OnHit"));
- UE_LOG(LogTemp, Warning, TEXT("HitComp: %s"), *HitComp->GetName());
- UE_LOG(LogTemp, Warning, TEXT("OtherActor: %s"), *OtherActor->GetName());
- UE_LOG(LogTemp, Warning, TEXT("OtherComp: %s"), *OtherComp->GetName());
- }
注意响应的函数必须添加 UFUNCTION 宏声明。
- UFUNCTION()
- void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
这块机制比较复杂,看着和反射机制有关,留作后续研究。
最终的效果如下方视频所示。可以在控制台中看到碰撞响应函数对各个参数的打印:
HitComp 参数是自己身上发生碰撞的组件。因为一个 Actor 下不止一个组件,各个组件都有可能发生碰撞。
OtherActor 和 OtherComp 参数同理,是碰到的那个物体所在的 Actor 和组件。
NormalImpulse 参数是碰撞产生的冲量;Hit 参数是具体的碰撞结果信息。