UE4 タイムラインについてのメモ

# 概要
UnrealEngine のタイムラインについてのメモ書きです。


## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.26

## 参考
以下を参考にさせて頂きました、ありがとうございます。

[公式 : FTimeline]
(https://docs.unrealengine.com/4.27/en-US/API/Runtime/Engine/Components/FTimeline/)
[公式 : タイムラインパリティの概要](https://docs.unrealengine.com/4.27/ja/ProgrammingAndScripting/Blueprints/UserGuide/Timelines/)
[UnrealEngine4のタイムラインの使い方まとめ](http://www.gamecorder.net/unrealEngine/blueprint/blueprint_timeline.php)

## 関連コード
"Engine\Source\Runtime\Engine\Classes\Components\TimelineComponent.h"
"Engine\Source\Runtime\Engine\Private\Timeline.cpp"
"Engine\Source\Runtime\Engine\Classes\Curves\CurveBase.h"


# タイムライン使用例
## カーブの用意
コンテンツブラウザから右クリック
「その他」 -> 「カーブ」 -> 「カーブクラスを選択」で`Float`/`LinearColor`/`Vector` いずれかを選ぶ。

![Create_Curve.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/d0a060a7-4a4b-b0cf-3090-f22b39e06946.png)

右クリックでキーを設定してカーブを作成します。
https://docs.unrealengine.com/4.26/ja/Basics/UI/CurveEditor/

floatのカーブ例：
![CurveData.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/1a864c5f-77cd-3a3f-2979-9824982585f4.png)


## BPでの実装例
右クリックで「タイムラインを追加する」を選び、タイムラインコンポーネントを追加します。
![create_timelineBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/e866f879-3052-c200-b4fc-670328ea27c7.png)

`Update`ピンで更新データが、`Finished`ピンでタイムライン終了時の処理が呼ばれます。
![TimelineBP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/116c04d0-1ee1-d90f-c628-db2a47ee3bbc.png)

タイムラインノードをダブルクリックするとカーブデータビューに切り替わります。ここで直接カーブデータを入力するか、外部カーブデータを指定します。
Vectorカーブ例：
![TimeLineCurve.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/bd50e834-3aa1-4019-5f45-cf3fb69276c3.png)


## C++での実装例1（タイムラインコンポーネントを使用）
タイムラインコンポーネントを使った例。
コンポーネントを作成してカーブデータと更新メソッド、終了メソッドを定義します。
以下コード例。

```cpp:MyActor.h
UCLASS()
class TEST_API AMyActor : public AActor
{
	GENERATED_BODY()
	
public:
	// ..省略..

	// タイムラインコンポーネント
	class UTimelineComponent*	MyTimelineComponent;
	// カーブ
	class UCurveFloat*	MyCurve;
	// タイマー
	float Timer;
	
	// タイムライン更新時に呼ばれる処理
	UFUNCTION()
	void TimelineStep(float _Value);
	
	// タイムライン終了時に呼ばれる処理
	UFUNCTION()
	void TimelineFinished();
};
```

```cpp:MyActor.cpp
#include "Components/TimelineComponent.h"

AMyActor::AMyActor()
{
	// ..省略..


	// カーブアセットの取得
	// カーブの種類によってクラスを選択(UCurveFloat / UCurveLinearColor / UCurveVector)
	const ConstructorHelpers::FObjectFinder<UCurveFloat>	_Find(TEXT("CurveFloat'/Game/TestCurve.TestCurve'"));
	if( _Find.Succeeded()) {
		MyCurve = _Find.Object;
	}

	MyTimelineComponent = CreateDefaultSubobject<UTimelineComponent>(TEXT("TimelineComponent0"));
	if (MyTimelineComponent) {
		// タイムライン更新時に呼び出されるメソッドの定義
		FOnTimelineFloat	_MyTimelineStepFunc;
		_MyTimelineStepFunc.BindUFunction(this, TEXT("TimelineStep"));
		MyTimelineComponent->AddInterpFloat(MyCurve, _MyTimelineStepFunc);
		
		// タイムライン終了時に呼び出されるメソッドの定義
		FOnTimelineEvent	_MyTimelineFinishedFunc;
		_MyTimelineFinishedFunc.BindUFunction(this, TEXT("TimelineFinished"));
		MyTimelineComponent->SetTimelineFinishedFunc(_MyTimelineFinishedFunc);
	}

}

void AMyActor::BeginPlay()
{
	Super::BeginPlay();

	Timer = 0.0f;

	// タイムライン開始
	if (MyTimelineComponent) {
		MyTimelineComponent->PlayFromStart();
	}

}

void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	Timer += DeltaTime;
}

// タイムライン更新時に呼ばれる処理
void AMyActor::TimelineStep(float _Value)
{
    UE_LOG(LogTemp, Log, TEXT("Timeline(%.2f): Val=%.2f"), Timer, _Value);
}

// タイムライン終了時に呼ばれる処理
void AMyActor::TimelineFinished()
{
    UE_LOG(LogTemp, Log, TEXT("TimelineFinished."));
}

```



## C++での実装例2（タイムラインコンポーネントを未使用）
コンポーネントを使わずに内包している `FTimeline` 構造体を使った例。
カーブアセットを動作させて値を取得する場合、通常はカーブデータの最後のキーまで取得します。
特定時間まで取得する場合はモードを`ETimelineLengthMode::TL_TimelineLength` に切り替えて時間を入れる。

以下コード例

```cpp:MyActor.h
UCLASS()
class TEST_API AMyActor : public AActor
{
	GENERATED_BODY()
	
public:
	// ..省略..

	// タイムライン
	FTimeline*			MyTimeline;
	// カーブ
	class UCurveFloat*	MyCurve;
	// タイマー
	float Timer;
	
	// タイムライン更新時に呼ばれる処理
	UFUNCTION()
	void TimelineStep(float _Value);
	
	// タイムライン終了時に呼ばれる処理
	UFUNCTION()
	void TimelineFinished();
};
```

```cpp:MyActor.cpp
#include "Components/TimelineComponent.h"

AMyActor::AMyActor()
{
	// ..省略..


	// タイムライン初期化
	MyTimeline = new FTimeline();

	// ＃＃タイムライン時間を指定したい場合は下のようにモードを設定して長さを指定する＃＃
	// タイムラインモード設定
//	MyTimeline->SetTimelineLengthMode(ETimelineLengthMode::TL_TimelineLength);
//	MyTimeline->SetTimelineLength(1.0f);

	// カーブアセットの取得
	// カーブの種類でクラスを指定(UCurveFloat / UCurveLinearColor / UCurveVector)
	const ConstructorHelpers::FObjectFinder<UCurveFloat>	_Find(TEXT("CurveFloat'/Game/TestCurve.TestCurve'"));
	if( _Find.Succeeded()) {
		MyCurve = _Find.Object;
	}

	
	// タイムライン更新時に呼び出されるメソッドの定義
	FOnTimelineFloat	_MyTimelineStepFunc;
	_MyTimelineStepFunc.BindUFunction(this, TEXT("TimelineStep"));
	MyTimeline->AddInterpFloat(MyCurve, _MyTimelineStepFunc);

	// タイムライン終了時に呼び出されるメソッドの定義
	FOnTimelineEvent	_MyTimelineFinishedFunc;
	_MyTimelineFinishedFunc.BindUFunction(this, TEXT("TimelineFinished"));
	MyTimeline->SetTimelineFinishedFunc(_MyTimelineFinishedFunc);
	

}

void AMyActor::BeginPlay()
{
	Super::BeginPlay();

	Timer = 0.0f;

	// タイムライン開始
	if (MyTimeline != nullptr) {
		MyTimeline->PlayFromStart();
	}

}

void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);

	// タイムライン始末
	if(MyTimeline){
		delete MyTimeline;
		MyTimeline = nullptr;
	}
}

void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	Timer += DeltaTime;

	// タイムライン実行処理
	if( MyTimeline != nullptr && MyTimeline->IsPlaying() ){
		MyTimeline->TickTimeline(DeltaTime);
	}
}

// タイムライン更新時に呼ばれる処理
void AMyActor::TimelineStep(float _Value)
{
    UE_LOG(LogTemp, Log, TEXT("Timeline(%.2f): Val=%.2f"), Timer, _Value);
}

// タイムライン終了時に呼ばれる処理
void AMyActor::TimelineFinished()
{
    UE_LOG(LogTemp, Log, TEXT("TimelineFinished."));
}

```

# C++でのカーブデータ作成について
アセットではなくC++で作成するには以下のようになります。
以下コード例：

```cpp:Floatカーブ
	UCurveFloat*  MyCurve;

	// オブジェクトフラグ
	auto _ObjectFlag = EObjectFlags::RF_Public 
						| EObjectFlags::RF_Transactional 
						| EObjectFlags::RF_WasLoaded 
						| EObjectFlags::RF_LoadCompleted;

	// Floatカーブオブジェクト生成
	MyCurve = NewObject<UCurveFloat>(this, UCurveFloat::StaticClass(), TEXT("MyFloatCurve"), _ObjectFlag);
	if(MyCurve){
		MyCurve->FloatCurve.AddKey(0.0f, 0.0f);	// Time, Value
		MyCurve->FloatCurve.AddKey(1.0f, 0.6f);
		MyCurve->FloatCurve.AddKey(2.0f, 3.0f);
	}

```

```cpp:Vectorカーブ
	UCurveVector* 	MyVectorCurve;

	// Vectorカーブオブジェクト生成
	MyVectorCurve = NewObject<UCurveVector>(this, UCurveVector::StaticClass(), TEXT("MyVectorCurve"), _ObjectFlag);
	if(MyVectorCurve){
		// X
		MyVectorCurve->FloatCurves[0].AddKey(0.0f, 0.2f);
		MyVectorCurve->FloatCurves[0].AddKey(1.0f, 0.8f);
		
		// Y
		MyVectorCurve->FloatCurves[1].AddKey(0.0f, 1.0f);
		MyVectorCurve->FloatCurves[1].AddKey(1.0f, 0.9f);
		
		// Z
		MyVectorCurve->FloatCurves[2].AddKey(0.0f, 0.5f);
		MyVectorCurve->FloatCurves[2].AddKey(1.0f, 0.7f);
	}
```

アセットにするまでもない場合は使ってもいいかもです。



# まとめ
タイムラインコンポーネントを乱発で使うと処理負荷が高そうなので使いどころに注意が要りそうです。単純な計算式では表現できないところだけで使うべきかなと思われます。