UE4 Spline based Velocity Field Generator
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_EDITOR
void ASplineMovementActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
InitSpline(); // just to preview the updated spline
}
#endif
void 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 values
ASplineMovementActor::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 spawned
void 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;
}
};