はじめに
StateTreeから制御対象のActorの関数・イベントを呼びたい、というのは多々あるかと思いますが、そのたびに専用のStateTree Taskを作るのは少々めんどうです。
基底クラスで関数を用意したり、(BP)Interfaceなどを用いることで1つのTaskを流用することはできますが、プロト制作時などスピード感が必要な段階では…その準備もサボりたい所…
ということで、文字列から関数・イベントを呼び出せる CallFunctionByNameWithArguments を使ったStateTree Taskを作ってみました!
事前準備不要でStateTreeからActor(UObject)が持つ処理を呼び出せますし、

呼び出す関数名・引数をStateTreeのParameterと紐づけておけば、Actor側のStateTree Component で設定することが可能になります。

その結果、実装が多少違うBP・Actorであっても、StateTreeを超汎用的に使用することが可能になります!これは便利!(なはず)
ということで、今回はそんなStateTree Taskを実現する C++コードを紹介します。なお、もしBPのみで実現したい場合はKEコマンドを使用することになるはずです。
【UE4】ConsoleCommand「CE,KE」について【★★☆】 | キンアジのブログ
StateTreeで文字列から関数・イベントを呼び出す C++コード
…といっても、CallFunctionByNameWithArguments 自体はむかーしからある関数で、解説している記事もいくつかあったりしますが…
UE4 UnrealC++リフレクションについてのメモ #UnrealEngine4 - Qiita
今回はユースケースということで何卒何卒( 自作StateTree Taskに関する記事はまだまだ少ないですし!)
では余談はさておき、C++コードをぺたり
#pragma once #include "StateTreeTaskBase.h" #include "StateTreeTask_CallFunction.generated.h" USTRUCT(BlueprintType) struct XXX_API FStateTreeTask_CallFunction_InstanceData { GENERATED_BODY() UPROPERTY(EditAnywhere, Category = Context) TObjectPtr<AActor> Actor; // 呼び出す関数・イベント名 UPROPERTY(EditAnywhere, Category = Parameter) FName FunctionName; // 引数 UPROPERTY(EditAnywhere, Category = Parameter) TArray<FName> Arguments; // FinishTaskを呼ぶか否か。同時に複数のTaskを呼ばない場合はtrueに UPROPERTY(EditAnywhere, Category = Parameter) bool bShouldCallFinishTask = false; }; // 文字列から指定Actorの関数・イベントを引数付きで実行 USTRUCT(meta = (DisplayName = "Call Function")) struct XXX_API FStateTreeTask_CallFunction : public FStateTreeTaskCommonBase { GENERATED_BODY() using FInstanceDataType = FStateTreeTask_CallFunction_InstanceData; virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; // StateTreeのTask上での表示名 #if WITH_EDITOR virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override; #endif };
#include "StateTreeTask_CallFunction.h" #include "StateTreeExecutionContext.h" #define LOCTEXT_NAMESPACE "StateTreeTask_CallFunction" EStateTreeRunStatus FStateTreeTask_CallFunction::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const { const FInstanceDataType* InstanceData = Context.GetInstanceDataPtr<FInstanceDataType>(*this); check(InstanceData); if(!InstanceData->Actor) { UE_VLOG_UELOG(Context.GetOwner(), LogStateTree, Warning, TEXT("%hs Failed. Reason: Invalid Actor"), __FUNCTION__); return EStateTreeRunStatus::Failed; } bool bResult = false; // 指定の関数・イベント名があるかを確認し、 // 存在したら、引数も合成した文字列を元に関数・イベントを実行 if (const UFunction* Function = InstanceData->Actor->FindFunction(InstanceData->FunctionName)) { FString Cmd = Function->GetName(); for (const FName& Arg : InstanceData->Arguments) { Cmd += FString::Printf(TEXT(" %s"), *Arg.ToString()); } bResult = InstanceData->Actor->CallFunctionByNameWithArguments(*Cmd, *GLog, InstanceData->Actor, true); } else { UE_VLOG_UELOG(Context.GetOwner(), LogStateTree, Warning, TEXT("%hs Failed. Reason: Function not found"), __FUNCTION__); return EStateTreeRunStatus::Failed; } if(InstanceData->bShouldCallFinishTask) { return bResult ? EStateTreeRunStatus::Succeeded : EStateTreeRunStatus::Failed; } return EStateTreeRunStatus::Running; } #if WITH_EDITOR FText FStateTreeTask_CallFunction::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting) const { const FInstanceDataType* InstanceData = InstanceDataView.GetPtr<FInstanceDataType>(); check(InstanceData); return FText::Format(LOCTEXT("StateTreeTask_CallFunction", "CallFunction : {0}"), FText::FromName(InstanceData->FunctionName)); } #endif
さいごに
かんたんな内容ではありましたが、StateTreeのTaskはまだまだ数が十分ではないので、こういう小粒な便利Taskを自前で用意しておくとState Tree実装担当者が少ししあわせになると思います!この記事がきっかけとなって、そんな少し便利Taskが色々出てきたら嬉しいです(そして、記事書いてください!)。
おしまい