方法1:使用 Lerp
(线性插值) + Timeline
(时间轴)
适合控制移动时长和曲线效果
该方法需要三个变量:
StartLocation
(Vector):起始位置
TargetLocation
(Vector):目标位置
MoveDuration
(Float):移动时长(秒)
设置时间轴:
添加 Timeline
组件,命名为 MoveTimeline
。
在时间轴中添加 浮点轨道(Float Track) ,命名为 Alpha
。
编辑曲线:0.0
秒时值为 0.0
,MoveDuration
秒时值为 1.0
(可调整曲线形状控制缓动效果)。
蓝图逻辑:我这里为了测试方便,直接设置了三个变量的值
方法2:使用 VInterp To
(向量插值)
适合每一帧平滑移动,无需设置时间轴,需要配合Event Tick 一起使用
需要两个变量:
TargetLocation
(Vector):目标位置
InterpSpeed
(Float):插值速度(推荐 5-10)
蓝图逻辑:
方法3:使用 MoveComponentTo
(组件平滑移动)
注意使用此方法移动的是相对位置
蓝图逻辑:
实现物体匀速到达不同长度的目的地
实际上本例子是物体按照样条线上的样条点进行移动
设置的变量:
MoveSpeed
:移动速度
StopThreshold
:距离目的地多远可以停止
TargetLocation
:目的地
CurrentLocation
:当前位置
isMoving
:是否正在移动
ToTarget
:剩余方向向量 (TargetLocation - CurrentLocation
)
VTarget
:单位方向向量(Normalize(ToTarget)
)
Distance
:距离目的地的距离(VSize(ToTarget)
)
实现步骤:
开始移动时,判断是否已经在目的地:若否,执行下面步骤;若是,不执行
根据当前位置和目的地位置,得到单位方向向量VTarget
计算当前帧需要移动的距离 MoveEachDeltaSeconds
:MoveSpeed * DeltaSeconds
如果当前位置和目标位置小于最小步长,则直接移动到目标位置
否则向目标前进一步CurrentLocation = CurrentLocation + VTarget * MoveEachDeltaSeconds
回到步骤一
BoxMove函数
附加:实现物体按照样条线(非样条点)进行移动 蓝图逻辑: 时间轴参照上述方法1设置
触发函数
使用UEC++实现 UEC++实现时间轴
创建一个时间轴组件
绑定一个浮点曲线(如果需要的话),用于控制时间轴的变化
绑定时间轴更新和结束的委托
实现各个函数内的不同需求
创建一个浮点曲线,内部设置同蓝图创建时间轴的浮点曲线设置
MyTimeLine.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #pragma once #include "CoreMinimal.h" #include "Components/TimelineComponent.h" #include "GameFramework/Actor.h" #include "MyTimeLine.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam (FOnTimelineUpdateDelegate, float , Alpha);UCLASS ()class ASTAR_TEST2_API AMyTimeLine : public AActor{ GENERATED_BODY () public : AMyTimeLine (); virtual void Tick (float DeltaTime) override ; UPROPERTY (VisibleAnywhere,BlueprintReadWrite, Category = "Timeline" ) UTimelineComponent* MyTimeline; UPROPERTY (BlueprintReadWrite,EditAnywhere, Category = "Timeline" ) UCurveFloat* FloatCurve; UPROPERTY (BlueprintAssignable, Category = "Timeline" ) FOnTimelineUpdateDelegate FOnTimelineUpdate; UFUNCTION () void OnTimelineUpdate (float Value) ; UFUNCTION () void OnTimelineFinished () ; UFUNCTION () void OnTimelineEvent () ; UFUNCTION (BlueprintCallable, Category = "Timeline" ) void SetPlayRate (float NewRate) ; void PlayTimeline () ; protected : virtual void BeginPlay () override ; private : FOnTimelineFloat UpdateFunction; FOnTimelineEvent FinishedFunction; };
MyTimeLine.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include "MyTimeLine.h" AMyTimeLine::AMyTimeLine () { PrimaryActorTick.bCanEverTick = true ; MyTimeline = CreateDefaultSubobject <UTimelineComponent>(TEXT ("MyTimelineComponent" )); UpdateFunction.BindUFunction (this , FName ("OnTimelineUpdate" )); FinishedFunction.BindUFunction (this , FName ("OnTimelineFinished" )); static ConstructorHelpers::FObjectFinder<UCurveFloat> CurveAsset (TEXT("CurveFloat'/Game/BP/TimeLineCurve.TimeLineCurve'" )) ; if (CurveAsset.Succeeded ()) FloatCurve = CurveAsset.Object; } void AMyTimeLine::BeginPlay () { Super::BeginPlay (); if (FloatCurve) { MyTimeline->AddInterpFloat (FloatCurve, UpdateFunction); UE_LOG (LogTemp, Warning, TEXT ("FloatCurve,存在" )); } MyTimeline->SetTimelineFinishedFunc (FinishedFunction); } void AMyTimeLine::Tick (float DeltaTime) { Super::Tick (DeltaTime); if (MyTimeline->IsPlaying ()) { MyTimeline->TickComponent (DeltaTime, LEVELTICK_TimeOnly, nullptr ); } } void AMyTimeLine::OnTimelineUpdate (float Value) { FOnTimelineUpdate.Broadcast (Value); UE_LOG (LogTemp, Warning, TEXT ("Timeline Value: %f" ), Value); } void AMyTimeLine::OnTimelineFinished () { UE_LOG (LogTemp, Warning, TEXT ("Timeline Finished!" )); } void AMyTimeLine::OnTimelineEvent () { UE_LOG (LogTemp, Warning, TEXT ("Timeline Event Triggered!" )); } void AMyTimeLine::SetPlayRate (float NewRate) { if (MyTimeline) { MyTimeline->SetPlayRate (NewRate); } } void AMyTimeLine::PlayTimeline () { if (MyTimeline) MyTimeline->Play (); }
剩余部分实现:
BoxMove.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class ASpine ;class USplineComponent ;UCLASS ()class ASTAR_TEST2_API ABoxMove : public AActor{ GENERATED_BODY () public : ABoxMove (); virtual void Tick (float DeltaTime) override ; UFUNCTION () void MoveBySpline () ; UFUNCTION () void HandleTimelineUpdate (float Alpha) ; AMyTimeLine* TimelineActor; protected : virtual void BeginPlay () override ; private : USplineComponent* SplineComp ; ASpine* SpineRef = nullptr ; };
BoxMove.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ABoxMove::ABoxMove () { PrimaryActorTick.bCanEverTick = true ; } void ABoxMove::BeginPlay () { Super::BeginPlay (); } void ABoxMove::Tick (float DeltaTime) { Super::Tick (DeltaTime); } void ABoxMove::MoveBySpline () { TimelineActor = GetWorld ()->SpawnActor <AMyTimeLine>(); if (!TimelineActor) UE_LOG (LogTemp, Warning, TEXT ("TimelineActor不存在" )); if (TimelineActor) { TimelineActor ->FOnTimelineUpdate.AddDynamic (this , &ABoxMove::HandleTimelineUpdate); TimelineActor->SetPlayRate (0.2f ); TimelineActor->PlayTimeline (); } for (TActorIterator<ASpine> It (GetWorld ()); It; ++It) { SpineRef = *It; } SplineComp = SpineRef -> FindComponentByClass <USplineComponent>(); } void ABoxMove::HandleTimelineUpdate (float Alpha) { float SplineLength = SplineComp->GetSplineLength (); double Lerp = UKismetMathLibrary::Lerp (0.0f , SplineLength, Alpha); FVector LocationAtDistanceAlongSpline = SplineComp -> GetLocationAtDistanceAlongSpline (Lerp, ESplineCoordinateSpace::World); LocationAtDistanceAlongSpline += FVector (0.0f , 0.0f , 25.0f ); SetActorLocation (LocationAtDistanceAlongSpline); }