实现移动立方体

在上一篇文章 《创建 C++ 类》 中,我们创建了一个继承于 Actor 的 C++ 类。取的类名是 MovingPlatform,所以我们会基于这个创建的类,实现一个移动的立方体平面。

具体要实现的功能为:以物体的初始点作为中心,朝一个方向移动指定的距离,然后朝相反的方向移动。即实现类似“振荡”的移动效果。

现在把 C++ 类拖入到视口里,是没有东西显示的,也没有 transform 参数。如图 1 所示,我们选中 C++ 类的实例。在细节窗体中,点击“添加”按钮,添加一个 Cube。

添加完组件之后,在视口中就会显示相应的物体,且出现了 transform 参数。我们调整它的形状,调整成合适的平面样子。并将其移动到合适位置。

在参数上右击,可以出现复制、粘贴。

像位置信息,我们想放在人物附近。我们可以先复制、粘贴人物的位置,再在其基础上进行调整。

图1 添加立方体组件

以上内容完成后,我们就可以在 C++ 侧实现代码了。如代码清单 1 所示,_StartPosition 用于记录物体的初始位置;_CurrentTravelDistance 是物体移动了的距离;MovementVelocity 指定物体的移动向量,包含了大小和方向;MaxTravelDistance 指定移动的范围。

UPROPERTY 宏,我们第一次接触。它用于在 Unreal Engine 编辑器中公开 C++ 类成员变量。EditAnywhere 指定属性可以在编辑器的任何地方进行修改;Category 指定这个属性所在的分组。

UPROPERTY 具体的效果,可以在图 1 右侧的编辑器中看到。可以看到编辑器中多了一个“移动”分组(被自动翻译成了中文),且分组下有我们添加的两个变量。

UPROPERTY 宏是空的。Unreal Engine 应该自己有一套构建工具链,识别翻译这些宏,再进行编译。

代码清单 1 类定义
  1. UCLASS()
  2. class OBSTACLE_API AMovingPlatform : public AActor
  3. {
  4.     GENERATED_BODY()
  5.    
  6. public:
  7.     // Sets default values for this actor's properties
  8.     AMovingPlatform();
  9.  
  10. protected:
  11.     // Called when the game starts or when spawned
  12.     virtual void BeginPlay() override;
  13.  
  14.     // Original position of the platform
  15.     FVector _StartPosition;
  16.  
  17.     // Current travel distance
  18.     float _CurrentTravelDistance;
  19.  
  20. public:
  21.     // Called every frame
  22.     virtual void Tick(float DeltaTime) override;
  23.  
  24.     // Direction and speed of the platform's movement
  25.     UPROPERTY(EditAnywhere, Category = "Movement")
  26.     FVector MovementVelocity;
  27.  
  28.     // Maximum distance the platform can move from the start point
  29.     UPROPERTY(EditAnywhere, Category = "Movement")
  30.     float MaxTravelDistance;
  31.  
  32. private:
  33.     void HandleMovement(float DeltaTime);
  34. };

具体的功能实现,见代码清单 2,我们在构造函数中,设置成员变量的默认值。

BeginPlay 函数,我们在学习蓝图的时候已经接触过,和 BeginPlay 事件的概念是一样的。它在此 Actor 开始运行或被生成的时候调用。在此函数中,我们记录 Actor 的初始位置信息。

Tick 函数,我们第一次接触。它在每帧被调用。注意其中的 DeltaTime 参数,它代表自上一帧以来经过的时间,单位是秒。我们通过它来确保物体移动的一致性,即移动距离不会因为帧率的变动而改变。

我们在 HandleMovement 函数中实现移动逻辑。我们首先根据物体的当前位置以及移动速度,计算得到“建议”的位置。如果“建议”的位置没有超出移动范围,就直接用此位置更新物体的位置。如果超过了,我们需要“修正”,把超过的部分,算到“返程”的距离上。这就确保了物体严格在指定范围内“振荡”。

代码清单 2 类实现
  1. #include "MovingPlatform.h"
  2.  
  3. // Sets default values
  4. AMovingPlatform::AMovingPlatform()
  5. {
  6.     // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
  7.     PrimaryActorTick.bCanEverTick = true;
  8.  
  9.     MovementVelocity = FVector(100, 0, 0);
  10.     MaxTravelDistance = 500;
  11.     _CurrentTravelDistance = 0;
  12. }
  13.  
  14. // Called when the game starts or when spawned
  15. void AMovingPlatform::BeginPlay()
  16. {
  17.     Super::BeginPlay();
  18.  
  19.     _StartPosition = GetActorLocation();
  20.     _CurrentTravelDistance = 0;
  21. }
  22.  
  23. // Called every frame
  24. void AMovingPlatform::Tick(float DeltaTime)
  25. {
  26.     Super::Tick(DeltaTime);
  27.  
  28.     HandleMovement(DeltaTime);
  29. }
  30.  
  31. void AMovingPlatform::HandleMovement(float DeltaTime)
  32. {
  33.     FVector CurrentLocation = GetActorLocation();
  34.     FVector TravelDirection = MovementVelocity.GetSafeNormal();
  35.     FVector ProposedNewLocation = CurrentLocation + (MovementVelocity * DeltaTime);
  36.  
  37.     float DistanceFromStart = FVector::Dist(_StartPosition, ProposedNewLocation);
  38.     if (DistanceFromStart <= MaxTravelDistance)
  39.         SetActorLocation(ProposedNewLocation);
  40.     else
  41.     {
  42.         float Overshoot = DistanceFromStart - MaxTravelDistance;
  43.         FVector EndPoint = _StartPosition + (TravelDirection * MaxTravelDistance);
  44.         FVector NewLocation = EndPoint - (TravelDirection * Overshoot);
  45.  
  46.         // Reverse direction
  47.         MovementVelocity *= -1;
  48.  
  49.         SetActorLocation(NewLocation);
  50.     }
  51. }

最后我们编译程序。因为我们之前已经编译出 Unreal Engine C++ 类了,所以此时我们不需要再完全关闭 Unreal Engine 进行代码编译。我们点击界面最右下角,有一个方块被溶解样式的图标。它是即时编译,不用关闭 Unreal Engine 就可以编译。

我们设置的默认速度是 100 个单位,单位是厘米,即 1 米。乘上 DeltaTime,单位是秒。即我们想要物体以 1 米每秒的速度进行移动。最终的效果如以下视频所示。