旋转炮塔

在这篇文章中,我们实现炮塔的旋转。

因为我们自身坦克和敌方坦克的炮塔都需要旋转,所以如代码清单 1 所示,我们把旋转功能放在它们的父类 ABasePawn 里。

代码清单 1 ABasePawn.h
  1. UCLASS()
  2. class TOONTANKS_API ABasePawn : public APawn
  3. {
  4.     GENERATED_BODY()
  5.  
  6. protected:
  7.     void RotateTurret(FVector LookAtTarget);
  8. };

代码清单 2 是炮塔旋转的实现。我们首先根据朝向的点获取朝向向量。FVector::Rotation() 函数可以根据一个向量,返回对应的旋转量。

因为炮塔只可以在水平方向上旋转,所以我们只保留 Yaw 分量。

接着我们使用 SetWorldRotation() 函数设置炮塔的旋转。

SetComponentRotation() 是自身局部空间;SetWorldRotation() 是世界空间。

FVector::Rotation() 返回的是基于世界的旋转量。

如果我们直接使用 LookAtRotation 设置旋转量,运行时会出现跳变的情况。为此,我们使用 FMath::RInterpTo() 函数,对旋转量进行插值。

代码清单 2 ABasePawn.cpp
  1. void ABasePawn::RotateTurret(FVector LookAtTarget)
  2. {
  3.     FVector ToTarget = LookAtTarget - TurretMesh->GetComponentLocation();
  4.     FRotator LookAtRotation = FRotator(0.0f, ToTarget.Rotation().Yaw, 0.0f);
  5.     TurretMesh->SetWorldRotation(
  6.         FMath::RInterpTo(
  7.             TurretMesh->GetComponentRotation(),
  8.             LookAtRotation,
  9.             UGameplayStatics::GetWorldDeltaSeconds(this),
  10.             15.0f));
  11. }

接着我们需要调用旋转函数。如代码清单 3 所示,调用点在上一节调试鼠标射线扫描的地方。我们将发生碰撞的点作为参数传入。

代码清单 3 旋转
  1. // Called every frame
  2. void ATank::Tick(float DeltaTime)
  3. {
  4.     Super::Tick(DeltaTime);
  5.  
  6.     if (PlayerControllerRef)
  7.     {
  8.         FHitResult HitResult;
  9.         PlayerControllerRef->GetHitResultUnderCursor(
  10.             ECollisionChannel::ECC_Visibility,
  11.             false, HitResult);
  12.  
  13.         //DrawDebugSphere(GetWorld(),
  14.         //  HitResult.ImpactPoint,
  15.         //  25.0f, 12, FColor::Red);
  16.  
  17.         RotateTurret(HitResult.ImpactPoint);
  18.     }
  19. }

我们尝试运行程序,会发现在边界位置,炮塔会受不到控制。我们可以把代码清单 3 中的 DrawDebugSphere() 函数注释回来。如图 1 所示,当鼠标位置超过边界的时候,会获取不到碰撞点,导致旋转量计算错误。

图1 碰撞点

为了修复这个问题,如图 2 所示,我们可以在场景周围添加 阻挡体积。阻挡体积默认不阻挡 Visibility。我们需要把 碰撞预设 设置为 Custom,然后将 Visibility 设置为阻挡。

图2 阻挡体积

设置好后,如图 3 所示,我们鼠标的射线扫描就可以指向场景外了。

图3 修复

最终运行的效果,如以下视频所示。