概要
UnrealEngine のゲームプレイタスクについてのメモです。
環境
Windows10
Visual Studio 2017
UnrealEngine 4.24, 5.3.2
更新履歴
日付 | 内容 |
---|---|
2020/06/23 | 初版 |
2024/09/02 | タスクの停止について追記 |
参考
以下を参考にさせて頂きました、ありがとうございます。
UnrealEngine : GameplayTasks
GameplayTasks API – GameplayTasksを使用して実現する
関連コード
"Engine\Source\Runtime\GameplayTasks\Classes\GameplayTask.h"
モジュール準備
Build.cs に GameplayTasks
モジュールを追加します。
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"InputCore",
"GameplayAbilities",
"GameplayTags",
"GameplayTasks" // ←追加
}
GameplayTasksの使用
GameplayTaskComponent の準備
ゲームプレイタスクを管理するコンポーネントを用意する必要があります。
また、コンポーネントを用意せずに、UGameplayTaskOwnerInterface
をアクターなどが継承して使用することもできるようです。
BP
C++
UPROPERTY(Category = "Component", EditAnywhere, BlueprintReadWrite)
class UGameplayTasksComponent* GameplayTasksComponent;
#include "GameplayTasksComponent.h"
// コンストラクタ
GameplayTasksComponent = CreateDefaultSubobject<UGameplayTasksComponent>(TEXT("GameplayTasks0"));
GameplayTaskクラスの作成
"Engine\Source\Runtime\GameplayTasks\Classes\Tasks" フォルダのクラスを参考にタスクが呼ばれている最中にTickが走るようなタスククラスを作成してみます。
#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;
};
#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
C++
// タスク終了時に呼ぶ処理
UFUNCTION()
void FinishFunc(){ UE_LOG(LogTemp, Log, TEXT("Finish")); }
#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秒間出力されます。
タスク終了後、終了時処理が呼び出され、[Finish]の文字列が出力されます。
タスクの停止
起動後のタスクを停止するには EndTask()
メソッドを呼ぶことできます。
if( _Task){
// タスクを停止する
_Task->EndTask();
}
途中終了であっても処理したい終了処理があるならば OnDestroy
を継承してそこに終了処理を書くと良いです。
以下コード例。
// タスクを終了してクリーンアップする処理(直接呼び出してはならない)
void UMyGameplayTask::OnDestroy(bool bInOwnerFinished)
{
// EndTask() または TaskOwnerEnded() から呼ばれる
UE_LOG(LogTemp, Log, TEXT("必ず呼ばれる終了処理"));
// 基底処理(必ず最後に呼ぶ)
Super::OnDestroy(bInOwnerFinished);
}
補足
コンポーネントからのタスクの呼び出しについて
タスク呼び出しコードにて、コンポーネントに直接タスクを追加する以下のような呼び出しをした場合、エラーがアウトプットログに出力されます。
// ゲームプレイタスクを作成
UMyGameplayTask* _Task = UMyGameplayTask::MyTestTask(GameplayTasksComponent, 2.0f);
if( _Task){
// アクティベーション(直接コンポーネントに追加する)
GameplayTasksComponent->AddTaskReadyForActivation(*_Task);
}
GameplayTaskComponent
の以下のマクロでエラーになるようです。
ensure(NewTask.RequiresPriorityOrResourceManagement() == true);
どうやらTaskを起動するために必要なリソースのチェックが行われているようです。
コンポーネントからは RunGameplayTask
でリソースを指定して起動させることができます。
// ゲームプレイタスクを作成
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 についての記事がほとんど見当たらないので、間違いがある可能性があります。問題がありましたらご指摘をお願いいたします。