Created a tool that read curve data of UE4 and create a velocity field that game object can follow with animation.
Originally, used for test in Riot Games.
C++, Unreal Engine 4
// Fill out your copyright notice in the Description page of Project Settings.#include "MyProject.h"#include "SplineMovementActor.h"ASplineMovementActor::ASplineMovementActor(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , Time(2.0f){ SceneComp = ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("SceneComp")); RootComponent = SceneComp; SceneComp->Mobility = EComponentMobility::Movable; SplineComp = ObjectInitializer.CreateDefaultSubobject<USplineComponent>(this, TEXT("JumpPathSpline")); SplineComp->SetupAttachment(RootComponent); StaticMeshComp = ObjectInitializer.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("StaticMeshComp")); StaticMeshComp->SetupAttachment(RootComponent); // Make sure all the spline comp to be absolute to world space not to this component SplineComp->bAbsoluteLocation = true; SplineComp->bAbsoluteRotation = true; SplineComp->bAbsoluteScale = true; SplinePointCount = 2; PlayedTime = 0.0f; HeadMeshScale = 1.0f; IsPlaying = false; bEndParticlePlayed = false; PrimaryActorTick.bCanEverTick = true; StartLocation = FVector::ZeroVector; EndLocation = FVector::ZeroVector; //SetActorTickEnabled(false); //RegisterAllActorTickFunctions(false, true);}void ASplineMovementActor::Tick(float DeltaSeconds){ // Make sure tick only execute while its playing. if (!IsPlaying) return; MovementImplementation(DeltaSeconds);}#if WITH_EDITORvoid ASplineMovementActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent){ Super::PostEditChangeProperty(PropertyChangedEvent); InitSpline(); // just to preview the updated spline}#endifvoid ASplineMovementActor::StartMovement(){ // Make sure start only its not playing. if (IsPlaying) return; StaticMeshComp->SetStaticMesh(HeadMesh); StaticMeshComp->SetWorldScale3D(FVector(HeadMeshScale, HeadMeshScale, HeadMeshScale)); InitSpline(); if (bDelayAfterStartParticle) { GetWorld()->GetTimerManager().SetTimer(ToggleMovementHandle, this, &ASplineMovementActor::ActivateTrail, DelayAfterStartParticle, false); } else { ActivateTrail(); } UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), StartParticle, StartLocation, FRotator::ZeroRotator, true);}void ASplineMovementActor::ActivateTrail(){ IsPlaying = true; // let the trail/spline animation actually start UGameplayStatics::SpawnEmitterAttached(TrailParticle, StaticMeshComp);}void ASplineMovementActor::MovementImplementation(float DeltaSeconds){ if (HeadMesh && StaticMeshComp->StaticMesh) { PlayedTime += DeltaSeconds; FVector2D TimeRange = FVector2D(0.0f, Time); FVector2D TargetRange = FVector2D(0.0f, SplineLength); if (PlayedTime >= Time) { FinishSplineMovement(); } else { if (EaseType == EEaseType::ET_EaseIn) { DistAtSpline = EaseIn(PlayedTime, 0.0f, SplineLength, Time); } else if (EaseType == EEaseType::ET_EaseOut) { DistAtSpline = EaseOut(PlayedTime, 0.0f, SplineLength, Time); } else { DistAtSpline = FMath::GetMappedRangeValueClamped(TimeRange, TargetRange, PlayedTime); } FVector Location = SplineComp->GetWorldLocationAtDistanceAlongSpline(DistAtSpline); StaticMeshComp->SetWorldLocation(Location); } } if (PlayedTime > EndParticleTime && !bEndParticlePlayed) { UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), EndParticle, EndLocation, FRotator::ZeroRotator, true); bEndParticlePlayed = true; }}void ASplineMovementActor::InitSpline(){ SetCustomSpline(); SplineComp->UpdateSpline(); SplineLength = SplineComp->GetSplineLength();}void ASplineMovementActor::InitSplinePoints(int32 PointCount){ if (SplinePoints.Num() > 0) { SplinePoints.Empty(); } SplineComp->ClearSplinePoints(); for (int32 i = 0; i < PointCount; ++i) { SplinePoints.Add(FVector::ZeroVector); SplineComp->AddSplineLocalPoint(SplinePoints[i]); }}void ASplineMovementActor::SetCustomSpline(){ // Look for USplineComp that has name "SplineMesh" USplineComponent* CustomSplineComp = NULL; if (!CustomSpline) return; USceneComponent* CustomSceneComp = CustomSpline->GetRootComponent(); for (int32 i = 0; i < CustomSceneComp->GetNumChildrenComponents(); ++i) { if (CustomSceneComp->GetChildComponent(i)) { if (CustomSceneComp->GetChildComponent(i)->GetName() == "SplineMesh") { CustomSplineComp = Cast<USplineComponent>(CustomSceneComp->GetChildComponent(i)); } } } // Make sure the custom spline comp is there and its points to be bigger than 1 (line need at least 2 points) if (CustomSplineComp && CustomSplineComp->GetNumberOfSplinePoints() > 1) { InitSplinePoints(CustomSplineComp->GetNumberOfSplinePoints()); TArray<FVector> NewSplinePoints; NewSplinePoints.Reserve(CustomSplineComp->GetNumberOfSplinePoints()); for (int32 i = 0; i < CustomSplineComp->GetNumberOfSplinePoints(); ++i) { // For the start point, make sure it starts from the right position for FX if (i == 0) { NewSplinePoints.Add(CustomSplineComp->GetWorldLocationAtSplinePoint(i)); StartLocation = CustomSplineComp->GetWorldLocationAtSplinePoint(i); /* FVector Offset = FVector::ZeroVector; CalculateOffset(TargetCharacter, Offset); FVector StartLocation = GetActorLocation() + Offset; NewSplinePoints.Add(StartLocation); */ } // For the end point, make sure it gets stored for FX else if (i == CustomSplineComp->GetNumberOfSplinePoints() - 1) { NewSplinePoints.Add(CustomSplineComp->GetWorldLocationAtSplinePoint(i)); EndLocation = CustomSplineComp->GetWorldLocationAtSplinePoint(i); } else { NewSplinePoints.Add(CustomSplineComp->GetWorldLocationAtSplinePoint(i)); } } SplineComp->SetSplineLocalPoints(NewSplinePoints); }}void ASplineMovementActor::FinishSplineMovement(){ // Things to do when spline movement is done PlayedTime = 0.0f; IsPlaying = false; bEndParticlePlayed = false; // play sequential fx if (bPlaySeqFX) { GetWorld()->GetTimerManager().SetTimer(ToggleSeqFX, this, &ASplineMovementActor::RunSeq, DelaySeqFXTime, false); }}void ASplineMovementActor::RunSeq(){ TArray<ASplineMovementActor*> SplineMovementActors; for (TActorIterator<ASplineMovementActor> Itr(GetWorld()); Itr; ++Itr) { SplineMovementActors.Push(*Itr); } int NextID = ID + 1; for (int i = 0; i < SplineMovementActors.Num(); i++) { if (SplineMovementActors[i]->ID == NextID) { SplineMovementActors[i]->StartMovement(); } }}/*// Sets default valuesASplineMovementActor::ASplineMovementActor(){ // 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;}*/// Called when the game starts or when spawnedvoid ASplineMovementActor::BeginPlay(){ Super::BeginPlay(); }// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "GameFramework/Actor.h"#include "ParticleDefinitions.h"//#include "Runtime/Engine/Classes/Particles/ParticleSystem.h"#include "Components/SplineComponent.h"#include "SplineMovementActor.generated.h"UENUM(BlueprintType)enum class EEaseType : uint8 { ET_EaseIn UMETA(DisplayName = "EaseIn"), ET_EaseOut UMETA(DisplayName = "EaseOut"), ET_None UMETA(DisplayName = "None")};UCLASS()class MYPROJECT_API ASplineMovementActor : public AActor{ GENERATED_UCLASS_BODY()public: UPROPERTY() USceneComponent* SceneComp; UPROPERTY() USplineComponent* SplineComp; UPROPERTY() UStaticMeshComponent* StaticMeshComp; // General UPROPERTY(EditAnywhere, Category = "General") uint32 ID; // Spline Movement UPROPERTY(EditAnywhere, Category = "SplineMovement") AActor* CustomSpline; // How long the spline movement will take UPROPERTY(EditAnywhere, Category = "SplineMovement") float Time; // Spline Animation type UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SplineMovement") EEaseType EaseType; // Are we going to delay after the first particle activation UPROPERTY(EditAnywhere, Category = "SplineMovement") bool bDelayAfterStartParticle; // How long the spiline movement will be delayed after the first particle activation UPROPERTY(EditAnywhere, Category = "SplineMovement") float DelayAfterStartParticle; // trail effects moving object UPROPERTY(EditAnywhere, Category = "Effects") UStaticMesh* HeadMesh; // scale of the moving object UPROPERTY(EditAnywhere, Category = "Effects") float HeadMeshScale; // start activation particle system UPROPERTY(EditAnywhere, Category = "Effects") UParticleSystem* StartParticle; // trail particle system which will be attached to the moving object UPROPERTY(EditAnywhere, Category = "Effects") UParticleSystem* TrailParticle; // particle system for end of the spline movement UPROPERTY(EditAnywhere, Category = "Effects") UParticleSystem* EndParticle; // time offset for trigger time to the end of the spline movement UPROPERTY(EditAnywhere, Category = "Effects") float EndParticleTime; // Are we going to play another spline related fx after this spline movement UPROPERTY(EditAnywhere, Category = "SeqFX") bool bPlaySeqFX; // If so, how many time later? UPROPERTY(EditAnywhere, Category = "SeqFX") float DelaySeqFXTime; // This func could be used in BP so having it callable. UFUNCTION(BlueprintCallable, Category = "SplineMovement") virtual void StartMovement(); // Sets default values for this actor's properties ASplineMovementActor(); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick( float DeltaSeconds ) override; virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;protected: bool IsPlaying; bool bEndParticlePlayed; int32 SplinePointCount; float DistAtSpline; float SplineLength; float PlayedTime; FVector StartLocation; FVector EndLocation; TArray<FVector> SplinePoints; void InitSpline(); // Custom generated spline virtual void SetCustomSpline(); void ActivateTrail(); virtual void MovementImplementation(float DeltaSeconds); virtual void FinishSplineMovement(); void InitSplinePoints(int32 PointCount); virtual void RunSeq(); /* UFUNCTION(Reliable, Server, WithValidation) virtual void ServerSplineMovement(AShooterCharacter* InCharacter); UFUNCTION(Reliable, netmulticast, WithValidation) virtual void MulticastSplineMovement(AShooterCharacter* InCharacter); */private: FTimerHandle ToggleMovementHandle; FTimerHandle ToggleSeqFX; // This ease functions have to be separated into Math class but for this test it will be part of this class /* @t - current time of the tween @b - beginning of the value @c - destination of the value @d - total time of the tween */ float EaseOut(float t, float b, float c, float d) { float diff = c - b; // destination value - beginning value = diff of start and end return diff * ((t = t / d - 1)*t*t + 1) + b; } /* @t - current time of the tween @b - beginning of the value @c - destination of the value @d - total time of the tween */ float EaseIn(float t, float b, float c, float d) { float diff = c - b; // destination value - beginning value = diff of start and end return diff * (t /= d)*t*t + b; }};