本示例使用了C++时间轴来创建经典的基于距离的开门动画。
创建门Actor
使用空白(Blank)模板新建一个C++项目,并启用初学者内容包(Starter Content),将其命名为TimelineDoorActor。
找到内容浏览器,点击C++ Classes文件夹,然后点击添加(+)(Add (+))按钮并选择新建C++类(New C++ Class)。
选择Actor作为父类。
将创建的Actor命名为DoorActor。
新建Actor后,Visual Studio会自动打开
DoorActor.h以及DoorActor.cpp文件。 找到DoorActor.h文件并声明如下内容:DoorActor.h
C++#include "Components/TimelineComponent.h"接下来,在
DoorActor类定义中添加以下代码:DoorActor.h
C++protected: //MeshComponents to represent Door assets UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UStaticMeshComponent* DoorFrame; UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UStaticMeshComponent* Door; //TimelineComponent to animate Door meshes UPROPERTY(VisibleAnywhere, BlueprintReadWrite)找到
DoorActor.cpp。 需要包括以下类库,方可利用你的盒体组件。DoorActor.cpp
C++#include "Components/BoxComponent.h"在你的
ADoorActor::ADoorActor构造函数中声明以下内容:DoorActor.cpp
C++// Sets default values ADoorActor::ADoorActor() { // 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; //Create our Default Components DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrameMesh")); Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh")); DoorTimelineComp = CreateDefaultSubobject<UTimelineComponent>(TEXT("DoorTimelineComp"));注意:我们将门的相对变换保留为附件规则,以便稍后使用门Actor的自定义方法来操作它。 详情请参阅FAttachmentTransformRules。
编译你的代码。
设置门静态网格体
你需要设置静态网格体(Static Mesh)资产,直观地表示你的DoorFrame和Door静态网格体组件。
在内容浏览器中,找到你的C++ Classes文件夹。
右键点击你的DoorActor类,选择基于DoorActor创建蓝图类(Create Blueprint Class based on DoorActor)。
将你的蓝图Actor命名为Bp_DoorActor并将其放入相应的文件夹。
在组件(Components)选项卡中,选择DoorFrame静态网格体组件。
找到细节(Details)面板,将静态网格体更改为SM_DoorFrame。
找到组件(Components)选项卡,选择DoorMesh组件。
在细节(Details)面板中,将静态网格体更改为SM_Door。
然后找到变换(Transform)类别,将Y位置(Y Location)的值更改为45.0。
点击编译(Compile)和保存(Save)按钮。
创建UCurveFloat和时间轴事件轨道
时间轴组件需要时间轴曲线。 每个曲线都可以包含多个关键点,用于定义时间和值。曲线会为这些关键点插值,以计算时间轴中任意点的值。
我们将在此例中使用UCurveFloat。
找到
DoorActor.h中的ADoorActor类定义,并声明如下变量:DoorActor.h
C++public: // Variable to hold the Curve asset UPROPERTY(EditAnywhere) UCurveFloat* DoorTimelineFloatCurve; private: //Float Track Signature to handle our update track event FOnTimelineFloat UpdateFunctionFloat; //Function which updates our Door's relative location with the timeline graph找到
DoorActor.cpp并实现UpdateTimelineComp方法:DoorActor.cpp
C++void ADoorActor::UpdateTimelineComp(float Output) { // Create and set our Door's new relative location based on the output from our Timeline Curve FRotator DoorNewRotation = FRotator(0.0f, Output, 0.f); Door->SetRelativeRotation(DoorNewRotation); }然后,在
BeginPlay方法中添加以下代码:DoorActor.cpp
C++//Binding our float track to our UpdateTimelineComp Function's output UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp); //If we have a float curve, bind it's graph to our update function if (DoorTimelineFloatCurve) { DoorTimelineComp->AddInterpFloat(DoorTimelineFloatCurve, UpdateFunctionFloat); }编译你的代码。
阶段性代码
DoorActor.h
// Copyright 1998-2022 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Components/TimelineComponent.h"
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DoorActor.generated.h"
UCLASS()
class TIMELINEDOORACTOR_API ADoorActor : public AActor
{
DoorActor.cpp
//Copyright 1998-2022 Epic Games, Inc. All Rights Reserved.
#include "DoorActor.h"
#include "Components/BoxComponent.h"
// Sets default values
ADoorActor::ADoorActor()
{
// 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;
创建并绑定盒体碰撞重叠事件
盒体组件需要拥有在Actor进入或离开碰撞边界时做出反应的能力。
找到你的
DoorActor.h文件的类定义并声明如下内容:DoorActor.h
C++// Begin and End Overlap Events for our DoorProxVolume UFUNCTION() void OnOverlapBegin(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); UFUNCTION() void OnOverlapEnd(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);接下来,找到你的
DoorActor.cpp文件,实现OnOverlapBegin和OnOverlapEnd类方法:DoorActor.cpp
C++void ADoorActor::OnOverlapBegin(UPrimitiveComponent * OverlappedComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult) { DoorTimelineComp->Play(); } void ADoorActor::OnOverlapEnd(UPrimitiveComponent * OverlappedComp, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex) { DoorTimelineComp->Reverse(); }在
BeginPlay方法中绑定重叠函数,如下所示:DoorActor.cpp
C++void ADoorActor::BeginPlay() { Super::BeginPlay(); //Binding our float track to our UpdateTimelineComp Function's output UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp); //If we have a float curve, bind it's graph to our update function if (DoorTimelineFloatCurve) {编译你的代码。
在虚幻编辑器中创建曲线资产
你必须在虚幻编辑器中创建曲线资产,以将其指定给你的时间轴Actor蓝图。
找到内容浏览器,选择添加(+)(Add (+))> 杂项(Miscellaneous)> 曲线(Curve)。
选择CurveFloat并将资产命名为DoorCurveFloat。
双击新建的DoorCurveFloat,打开时间轴编辑器。
右键点击图表(Graph),然后选择添加关键点(Add Key),为浮点曲线添加两个关键点。 将第一个关键点的时间值调整为(0, 0)。 将第二个关键点的时间值调整为(4, 90)。
如需详细了解时间轴曲线的编辑方法,请参阅关键点和曲线。
按住Shift键并点击以选中这两个关键点,右键点击图表(Graph),将它们设置为自动(Auto)插值。
现在你的曲线内容应如下所示。 保存你的DoorCuveFloat并关闭时间轴编辑器。
打开你的Bp_DoorActor并在组件(Components)选项卡中选择Bp_DoorActor。
找到细节(Details)面板,打开门操作(Door Action)分段的门时间轴浮点曲线(Door Timeline Float Curve)下拉菜单,选择DoorCurveFloat。
找到内容浏览器并将Bp_DoorActor放入关卡。
编译并保存,然后按PIE。
你可以使用WASD键进行输入,以控制旁观者Pawn。 找到你的DoorActor的碰撞边界时,你可以观察时间轴的播放,而在退出边界时,可以观察到时间轴倒放。
已完成代码
DoorActor.h
// Copyright 1998-2022 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Components/TimelineComponent.h"
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DoorActor.generated.h"
UCLASS()
class TIMELINEDOORACTOR_API ADoorActor : public AActor
DoorActor.cpp
// Copyright 1998-2022 Epic Games, Inc. All Rights Reserved.
#include "DoorActor.h"
#include "Components/BoxComponent.h"
// Sets default values
ADoorActor::ADoorActor()
{
// 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;