Timeline with UCurveFloat from UActorComponent calls update only once

I am creating timeline in C++ like this (note that I want to generate UCurveFloat as well and not just reference it from UProperty), from inside of UActorComponent:

UCurveFloat* Curve = NewObject<UCurveFloat>(this);
FKeyHandle KeyHandle = Curve->FloatCurve.UpdateOrAddKey(0.f, 0.f);
Curve->FloatCurve.UpdateOrAddKey(1.f, 1.f);
Curve->FloatCurve.SetKeyInterpMode(KeyHandle, ERichCurveInterpMode::RCIM_Cubic, true);

TimelineCallback.BindUFunction(this, FName("RotateUpdate"));
TimelineFinishedCallback.BindUFunction(this, FName("RotateFinish"));
RotateToSeatTimeline.AddInterpFloat(Curve, TimelineCallback);
RotateToSeatTimeline.SetTimelineFinishedFunc(TimelineFinishedCallback);
RotateToSeatTimeline.PlayFromStart();

The problem is, I get only one call of RotateUpdate() and none of RotateFinish().

I read that currently timelines do not require to be updated manually in tick event (it would also render them pointless in first place) - and PlayFromStart() should be sufficient.

.h file

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TimerTest.generated.h"

UCLASS(Blueprintable)
class YOUROWN_API ATimerTest : public AActor
{
	GENERATED_BODY()
	
public:	
	ATimerTest();

protected:
	virtual void BeginPlay() override;

public:	
	virtual void Tick(float DeltaTime) override;

	UFUNCTION()
	void RotateUpdate();

	UFUNCTION()
	void RotateFinish();

	UPROPERTY(EditAnywhere,BlueprintReadWrite)
	float rotation = 0;
	
	UPROPERTY(editAnywhere, BlueprintReadWrite)
		UCurveFloat* Curve;

	UPROPERTY(editAnywhere, BlueprintReadWrite)
		class UTimelineComponent* RotateToSeatTimeline;
};

.cpp file

#include "TimerTest.h"
#include "TimerManager.h"
#include "Components/TimelineComponent.h"

ATimerTest::ATimerTest()
{
	// 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;
	Curve = CreateDefaultSubobject<UCurveFloat>(TEXT("Timeline Curve"));
	RotateToSeatTimeline = CreateDefaultSubobject<UTimelineComponent>(TEXT("Seat Timeline"));
}

void ATimerTest::BeginPlay()
{
	Super::BeginPlay();
	
	FOnTimelineFloat TimelineCallback;
	FOnTimelineEventStatic TimelineFinishedCallback;

	FKeyHandle KeyHandle = Curve->FloatCurve.UpdateOrAddKey(0.f, 0.f);
	Curve->FloatCurve.UpdateOrAddKey(1.f, 1.f);
	Curve->FloatCurve.SetKeyInterpMode(KeyHandle, ERichCurveInterpMode::RCIM_Cubic, true);
		
	TimelineCallback.BindUFunction(this, FName({ TEXT("RotateUpdate") }));
	TimelineFinishedCallback.BindUFunction(this, FName({ TEXT("RotateFinish") }));
	
	RotateToSeatTimeline->AddInterpFloat(Curve, TimelineCallback);
	RotateToSeatTimeline->SetTimelineFinishedFunc(TimelineFinishedCallback);
	RotateToSeatTimeline->PlayFromStart();
}

// Called every frame
void ATimerTest::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}


void ATimerTest::RotateUpdate() {
	rotation += 0.5f;
	if(GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Cyan, "Rotate "+ FString::SanitizeFloat(rotation));
}


void ATimerTest::RotateFinish() {
	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Cyan, "RotateFinish");
}
1 Like

Hi. I do exactly this but I need to have it inside my custom component (that inherits from UActorComponent). But when I move it there, the update is called only once (I guess from inside beginPlay where I craete timeline and run PlayFromStart).

Do I need to do something more when playing timeline from component? Or is it impossible?

No shouldn’t be a problem to put it in a component here is an example of a test component I did about a week ago with a timeline.

.h

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Components/TimelineComponent.h" 
#include "MySceneComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) , Blueprintable )
class YOUR_API UMySceneComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	UMySceneComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	

	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
	
	float passedTime;
	UPROPERTY(editAnywhere, BlueprintReadWrite)
		UCurveFloat* TimelineCurve;
	
	UPROPERTY(editAnywhere, BlueprintReadWrite)
		UTimelineComponent* MyTimeline;
	
	FOnTimelineFloat InterpFunction{};

	UFUNCTION()
		void TimelineUpdate(float val);
};

.cpp

#include "MySceneComponent.h"
#include "TimerManager.h"
#include "Components/TimelineComponent.h" 
#include "Curves/CurveFloat.h" 

UMySceneComponent::UMySceneComponent()
{	
	PrimaryComponentTick.bCanEverTick = false;
	TimelineCurve = CreateDefaultSubobject<UCurveFloat>(TEXT("Timeline Curve"));		
	MyTimeline = CreateDefaultSubobject<UTimelineComponent>(TEXT("Timeline"));		
}


void UMySceneComponent::BeginPlay()
{
	Super::BeginPlay();
	
	if (TimelineCurve != nullptr) {
		InterpFunction.BindUFunction(this, FName{ TEXT("TimelineUpdate") });
		MyTimeline->AddInterpFloat(TimelineCurve, InterpFunction, FName{ TEXT("doUpdate") });
	}
	MyTimeline->SetLooping(true);
	MyTimeline->SetPlayRate(1);
	MyTimeline->SetTimelineLengthMode(TL_LastKeyFrame);
	MyTimeline->PlayFromStart();

}


// Called every frame
void UMySceneComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	// ...
}


void UMySceneComponent::TimelineUpdate(float val) {		
	if (TimelineCurve != nullptr) {
		bool isplay = MyTimeline->IsPlaying();
		if (isplay == true) {
			if (GEngine) {
				GEngine->AddOnScreenDebugMessage(-1, 2, FColor::Green, GetName() + " Progress:" + FString::SanitizeFloat(val));
			}
		}
	}
}

The changes are minimal. You just need to add the finished bind. Just copy over the parameters and should be gold

2 Likes

Thank you, @3dRaven !
I finally spotted the problem by comparison. I was using FTimeline in place of UTimelineComponent. Now it all works fine.

It works perfect!!

I can’t find the original post, but I wanted to thank you anyway.

You Rock!! :heart:

2 Likes