UE4 GameplayTasksについてのメモ

# 概要
UnrealEngine のゲームプレイタスクについてのメモです。


## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.24, 5.3.2

## 更新履歴
| 日付 | 内容 |
|:-----|:----|
|2020/06/23| 初版 |
|2024/09/02| タスクの停止について追記 |


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

[UnrealEngine : GameplayTasks](https://docs.unrealengine.com/en-US/API/Runtime/GameplayTasks/index.html)
[GameplayTasks API – GameplayTasksを使用して実現する](https://kazuhiro77unreal.hatenablog.com/entry/2019/03/10/190624)

## 関連コード
"Engine\Source\Runtime\GameplayTasks\Classes\GameplayTask.h"


# モジュール準備
Build.cs に `GameplayTasks`モジュールを追加します。

```cs:Build.cs
PublicDependencyModuleNames.AddRange(
	new string[]
	{
		"Core",
		"InputCore",
		"GameplayAbilities",
		"GameplayTags",
		"GameplayTasks"		// ←追加
	}
```


# GameplayTasksの使用

## GameplayTaskComponent の準備
ゲームプレイタスクを管理するコンポーネントを用意する必要があります。
また、コンポーネントを用意せずに、`UGameplayTaskOwnerInterface` をアクターなどが継承して使用することもできるようです。

### BP
![GameplayTasksComponent.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/27053616-c804-7f8a-a25f-a4e5de884a31.jpeg)

### C++

```cpp:MyActor.h
UPROPERTY(Category = "Component", EditAnywhere, BlueprintReadWrite)
class UGameplayTasksComponent* GameplayTasksComponent;
```

```cpp:MyActor.cpp
#include "GameplayTasksComponent.h"

// コンストラクタ
GameplayTasksComponent = CreateDefaultSubobject<UGameplayTasksComponent>(TEXT("GameplayTasks0"));
```


## GameplayTaskクラスの作成
"Engine\Source\Runtime\GameplayTasks\Classes\Tasks" フォルダのクラスを参考にタスクが呼ばれている最中にTickが走るようなタスククラスを作成してみます。

```cpp:MyGameplayTask.h
#include "CoreMinimal.h"
#include "GameplayTask.h"
#include "MyGameplayTask.generated.h"

UCLASS()
class TEST_API UMyGameplayTask : public UGameplayTask
{
	GENERATED_BODY()
	
	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FTaskFinishDelegate);

public:
	UMyGameplayTask(const FObjectInitializer& ObjectInitializer);

	// 終了時に呼ばれるデリゲート
	UPROPERTY(BlueprintAssignable)
	FTaskFinishDelegate OnFinished;


	/** デリゲートがセットアップされたら、実際のタスクをトリガーするために呼び出されます
	 * デフォルトの実装は何もせず、呼び出す必要がないことに注意してください */
	virtual void Activate() override;
	
	// Tick処理
	virtual void TickTask(float DeltaTime) override;

	// デバッグ文字列
	virtual FString GetDebugString() const override;

	// タスク生成
	UFUNCTION(BlueprintCallable, Category = "GameplayTasks", meta = (AdvancedDisplay = "TaskOwner, Priority", DefaultToSelf = "TaskOwner", BlueprintInternalUseOnly = "TRUE"))
	static UMyGameplayTask* MyTestTask(TScriptInterface<IGameplayTaskOwnerInterface> TaskOwner, float Time, const uint8 Priority = 192);

	static UMyGameplayTask* MyTestTask(IGameplayTaskOwnerInterface& InTaskOwner, float Time, const uint8 Priority = FGameplayTasks::DefaultPriority, const FName InInstanceName = FName());


private:
	float Time;
	float TimeStarted;

	uint32 bIsFinish : 1;
};

```


```cpp:MyGameplayTask.cpp

#include "MyGameplayTask.h"
#include "Engine/EngineTypes.h"
#include "VisualLogger/VisualLogger.h"
#include "GameplayTasksComponent.h"
#include "Engine/World.h"

// コンストラクタ
UMyGameplayTask::UMyGameplayTask(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	Time = 0.f;
	TimeStarted = 0.f;

	bIsFinish = false;
	bTickingTask = true;
}

// タスク生成
UMyGameplayTask* UMyGameplayTask::MyTestTask(TScriptInterface<IGameplayTaskOwnerInterface> TaskOwner, float Time, const uint8 Priority)
{
	UMyGameplayTask* MyTask = NewTaskUninitialized<UMyGameplayTask>();
	if (MyTask && TaskOwner.GetInterface() != nullptr)
	{
		MyTask->InitTask(*TaskOwner, Priority);
		MyTask->Time = Time;
	}
	
	return MyTask;
}

// タスク生成
UMyGameplayTask* UMyGameplayTask::MyTestTask(IGameplayTaskOwnerInterface& InTaskOwner, float Time, const uint8 Priority, const FName InInstanceName)
{
	UMyGameplayTask* MyTask = NewTaskUninitialized<UMyGameplayTask>();
	if (MyTask)
	{
		MyTask->InstanceName = InInstanceName;
		MyTask->InitTask(InTaskOwner, Priority);
		MyTask->Time = Time;
	}
	return MyTask;
}

// 実行開始
void UMyGameplayTask::Activate()
{
	UWorld* World = GetWorld();
	TimeStarted = World->GetTimeSeconds();

}

// Tick処理
void UMyGameplayTask::TickTask(float DeltaTime)
{
	FColor _Col = FColor::White;
	FVector2D _Scl(1.5f, 1.5f);
	GEngine->AddOnScreenDebugMessage(-1, 0.0f, _Col, GetDebugString(), true, _Scl);



	const float _TimeLeft = Time - GetWorld()->TimeSince(TimeStarted);
	if(_TimeLeft <= 0.0f){
		
		if(!bIsFinish){
			OnFinished.Broadcast();
		}
		bIsFinish = true;
		EndTask();
	}

}

// デバッグ文字列
FString UMyGameplayTask::GetDebugString() const
{
	const float _TimeLeft = Time - GetWorld()->TimeSince(TimeStarted);
	return FString::Printf(TEXT("MyTask TimeLimit for %s. Time: %.2f. TimeLeft: %.2f"), *GetNameSafe(GetChildTask()), Time, _TimeLeft);
}

```

## Taskの生成、呼び出し
作成したゲームプレイタスクを生成し、タスク終了時にも処理を1つ呼び出します。
引数となる時間設定は2秒にします。

### BP
![UseMyGameplayTask.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/d01558f6-d1b8-4482-75a9-d305c5d81cdd.jpeg)


### C++

```cpp:MyActor.h
// タスク終了時に呼ぶ処理
UFUNCTION()
	void FinishFunc(){ UE_LOG(LogTemp, Log, TEXT("Finish")); }
```

```cpp:MyActor.cpp
#include "MyGameplayTask.h"

// 2秒設定でタスク呼び出し
UMyGameplayTask* _Task = UMyGameplayTask::MyTestTask(GameplayTasksComponent, 2.0f);
if( _Task){
	// タスク終了時に呼ぶ処理を追加
	_Task->OnFinished.AddDynamic(this, &AMyActor::FinishFunc);
	// アクティベーション
	_Task->ReadyForActivation();
}

```

## 実行結果
`TickTask`が呼び出され、`[MyTask TimeLimit for %s. Time: %.2f. TimeLeft: %.2f]`が2秒間出力されます。

![Result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/dcfc6cdc-9317-6261-68b3-1737cc2905a2.jpeg)

タスク終了後、終了時処理が呼び出され、[Finish]の文字列が出力されます。


## タスクの停止
起動後のタスクを停止するには `EndTask()`メソッドを呼ぶことできます。

```cpp:.cpp
if( _Task){
	// タスクを停止する
	_Task->EndTask();
}
```

途中終了であっても処理したい終了処理があるならば `OnDestroy` を継承してそこに終了処理を書くと良いです。
以下コード例。

```cpp:MyTask.cpp

// タスクを終了してクリーンアップする処理（直接呼び出してはならない）
void UMyGameplayTask::OnDestroy(bool bInOwnerFinished)
{
	// EndTask() または TaskOwnerEnded() から呼ばれる
	
	UE_LOG(LogTemp, Log, TEXT("必ず呼ばれる終了処理"));
	
	// 基底処理（必ず最後に呼ぶ）
	Super::OnDestroy(bInOwnerFinished);
}


```


# 補足
## コンポーネントからのタスクの呼び出しについて
タスク呼び出しコードにて、コンポーネントに直接タスクを追加する以下のような呼び出しをした場合、エラーがアウトプットログに出力されます。

```cpp:.cpp

// ゲームプレイタスクを作成
UMyGameplayTask* _Task = UMyGameplayTask::MyTestTask(GameplayTasksComponent, 2.0f);
if( _Task){
	// アクティベーション（直接コンポーネントに追加する）
	GameplayTasksComponent->AddTaskReadyForActivation(*_Task);
}

```

`GameplayTaskComponent` の以下のマクロでエラーになるようです。

```cpp:GameplayTaskComponent.cpp
ensure(NewTask.RequiresPriorityOrResourceManagement() == true);
```

どうやらTaskを起動するために必要なリソースのチェックが行われているようです。
コンポーネントからは `RunGameplayTask` でリソースを指定して起動させることができます。

```cpp:.cpp
// ゲームプレイタスクを作成
UMyGameplayTask* _Task = UMyGameplayTask::MyTestTask(*GameplayTasksComponent, 2.0f);
if( _Task){
	// タスク起動に必要なリソースの設定
	FGameplayResourceSet _AdditionalRequiredResources = FGameplayResourceSet::NoResources();
	FGameplayResourceSet _AdditionalClaimedResources = FGameplayResourceSet::NoResources();

	// ゲームプレイタスクを走らせる
	GameplayTasksComponent->RunGameplayTask(*GameplayTasksComponent, *_Task, 0, _AdditionalRequiredResources, _AdditionalClaimedResources);
}
```



# まとめ
GameplayTasks についての記事がほとんど見当たらないので、間違いがある可能性があります。問題がありましたらご指摘をお願いいたします。:pray: