以下の内容はhttps://pafuhana1213.hatenablog.com/entry/2024/12/31/161529より取得しました。


【UE5】C++でChooser Table・Proxy Tableを使う方法について

はじめに

先日から続いてる Chooser 関連の記事シリーズ 第3弾!

pafuhana1213.hatenablog.com pafuhana1213.hatenablog.com

Chooserの設定方法やBPで使う方法については書きましたが、実際にプロジェクトで使い祭は C++ で Chooser Table, Proxy Tableを使う方法を知りたいところですよね!

早速ググってみると…出てこないいいいいいい! なんだ、この記事たちは!参考にならん!!!

ということで、世の中に情報がなかったのでエンジンコードを頼りに「Evaluate Chooser / Proxy を C++から呼ぶ方法について調べてみました…!


Evaluate Chooser を C++ で実行する方法

色々調べたところ、C++ で Chooser を使う際はUChooserFunctionLibraryで用意された各関数を使うのが良さそうです!

UChooserFunctionLibrary

EvaluateChooser

最もシンプル。ContextObjectにはChooserTableの入出力パラメータにおけるUObjectを渡し、ObjectClassにはChooserTableResultClassを設定

UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe, BlueprintInternalUseOnly = "true", DeterminesOutputType = "ObjectClass"))
static UObject* EvaluateChooser(const UObject* ContextObject, const UChooserTable* ChooserTable, TSubclassOf<UObject> ObjectClass);

EvaluateChooserMulti

EvaluateChooserの複数結果を返す版

 UFUNCTION(BlueprintPure, meta = (BlueprintThreadSafe, BlueprintInternalUseOnly = "true", DeterminesOutputType = "ObjectClass"))
 static TArray<UObject*> EvaluateChooserMulti(const UObject* ContextObject, const UChooserTable* ChooserTable, TSubclassOf<UObject> ObjectClass);

EvaluateObjectChooserBase

UObjectではなく、Chooser専用のFChooserEvaluationContextを渡す関数

FChooserEvaluationContextにはChooserTableの入出力パラメータを複数設定することが可能(後述)

UFUNCTION(BlueprintCallable, meta = (BlueprintThreadSafe, BlueprintInternalUseOnly = "true", DeterminesOutputType = "ObjectClass"))
static UObject* EvaluateObjectChooserBase(UPARAM(Ref) FChooserEvaluationContext& Context,UPARAM(Ref) const FInstancedStruct& ObjectChooser, TSubclassOf<UObject> ObjectClass, bool bResultIsClass = false);

EvaluateObjectChooserBaseMulti

EvaluateObjectChooserBaseの複数結果を返す版

UFUNCTION(BlueprintCallable, meta = (BlueprintThreadSafe, BlueprintInternalUseOnly = "true", DeterminesOutputType = "ObjectClass"))
static TArray<UObject*> EvaluateObjectChooserBaseMulti(UPARAM(Ref) FChooserEvaluationContext& Context,UPARAM(Ref) const FInstancedStruct& ObjectChooser, TSubclassOf<UObject> ObjectClass, bool bResultIsClass = false);

AddChooserObjectInput

FChooserEvaluationContextに 入出力パラメータである UObjectを設定する関数。C++のみで完結する場合は FChooserEvaluationContextAddObjectParam関数を使ったほうがシンプル

UFUNCTION(BlueprintCallable, meta = (BlueprintThreadSafe, BlueprintInternalUseOnly = "true"))
static void AddChooserObjectInput(UPARAM(Ref) FChooserEvaluationContext& Context, UObject* Object);

AddChooserStructInput

FChooserEvaluationContextに 入出力パラメータである Structを設定する関数。ただC++のみで完結する場合は FChooserEvaluationContextAddStructParam関数を使ったほうがシンプル。

UFUNCTION(BlueprintCallable, CustomThunk, meta = (BlueprintThreadSafe, BlueprintInternalUseOnly = "true", CustomStructureParam = "Value"))
static void AddChooserStructInput(UPARAM(Ref) FChooserEvaluationContext& Context, int32 Value);
    
DECLARE_FUNCTION(execAddChooserStructInput);

GetChooserStructOutput

FChooserEvaluationContextに 入出力パラメータである Structを取得する関数。ただこれはEvaluate Chooserノードが動的にピンを生成するのに使われているものであり、C++のみで完結する場合はあまり使用する機会はないかも

UFUNCTION(BlueprintPure, CustomThunk, meta = (BlueprintThreadSafe, BlueprintInternalUseOnly = "true", CustomStructureParam = "Value"))
static void GetChooserStructOutput(UPARAM(Ref) FChooserEvaluationContext& Context, int Index, int32& Value);
        
DECLARE_FUNCTION(execGetChooserStructOutput);

MakeEvaluateChooser

ChooserTableからInstancedStruct/Script/Chooser.ObjectChooserBase)を生成する関数。EvaluateObjectChooserBase(Multi)を使う際はこのInstancedStructが必要になる

UFUNCTION(BlueprintPure, Category = "Animation", meta = (BlueprintThreadSafe, BlueprintInternalUseOnly="true", NativeMakeFunc))
static FInstancedStruct MakeEvaluateChooser(UChooserTable* Chooser);

MakeChooserEvaluationContext

空のFChooserEvaluationContext を作成する関数。ただ FChooserEvaluationContext Context; で十分(内部実装もそうだし)

UFUNCTION(BlueprintCallable, Category = "Animation", meta = (BlueprintThreadSafe, BlueprintInternalUseOnly="true"))
static FChooserEvaluationContext MakeChooserEvaluationContext();

サンプルコード

で、これらを使うことでEvaluateChooserと同様に、ChooserTableを使った選定処理を独自のC++コードで実装することができます!

UFUNCTION(BlueprintCallable)
    UObject* GetObjectFromChooserTable( UChooserTable* ChooserTable, const TSubclassOf<UObject> ObjectClass,
        UPARAM(Ref) FXXXInput& Input,FXXXOutput& Output);
UObject* AXXX::GetObjectFromChooserTable(UChooserTable* ChooserTable, const TSubclassOf<UObject> ObjectClass,
    FXXXInput& Input,FXXXOutput& Output)
{
    if(ChooserTable)
    {
        // シンプル版
        //return UChooserFunctionLibrary::EvaluateChooser(this, ChooserTable, ObjectClass);
    
      // パラメータ複数版
        // ChooserTableのパラメータを設定
        FChooserEvaluationContext Context;
        Context.AddObjectParam(this);
        Context.AddStructParam(Input);
        Context.AddStructParam(Output);

        // ChooserTableを使って選定処理を実行
        FInstancedStruct ChooserStruct = UChooserFunctionLibrary::MakeEvaluateChooser(ChooserTable);
        return UChooserFunctionLibrary::EvaluateObjectChooserBase(
            Context, ChooserStruct, ObjectClass, false);
    }

    return nullptr;
}

(表示するStaticMeshアセットをパラメータに応じて変化、というもの)

EvaluateChooserノードと違い「基底クラスで処理を書き、派生クラスで使用するChooserTableを設定する」ことができるため、前回の記事にて書いた欠点を解決することができます!

ただChooserTableのResult Class・入出力パラメータの変化には弱く、クラス設定やパラメータの数が変化すると問題が起きることが EvaluateChooserノードに比べると多そうです。

ちなみに、Chooserに関する処理は各クラスがオススメです!

  • FAnimNode_ChooserPlayer(Chooser Playerノード)
    • シンプルでわかりやすい!
  • UK2Node_EvaluateChooser2( Evaluate Chooserノード)
    • 動的にピンを生成したりしてるので少し複雑
    • UK2Node_EvaluateChooserというのもありますが、こちらは古いコードの模様

ちなみに、↑のChooser Playerノードはこちら。ABP(AnimInstance)を内部で渡してくれるので、Animation Chooser設定で作ったChooser TableをAnimGraph 内で使う際に便利です!

Evaluate Proxyを C++ で実行する方法

こちらは UProxyTableFunctionLibrary を使うのがオススメです。ただProxyAssetがまだExperimentalであったり、一時的な後方互換のために用意された関数があったりします。

そのため、実際に使うのはMakeLookupProxyWithOverrideTableだけです。

UProxyTableFunctionLibrary

MakeLookupProxyWithOverrideTable

ProxyTableの処理で使う InstnacedStruct(FLookupProxyWithOverrideTable)を生成する関数。

UFUNCTION(BlueprintPure, Category = "Animation", meta = (BlueprintThreadSafe, BlueprintInternalUseOnly="true"))
static FInstancedStruct MakeLookupProxyWithOverrideTable(UProxyAsset* Proxy, UProxyTable* ProxyTable);
FInstancedStruct UProxyTableFunctionLibrary::MakeLookupProxyWithOverrideTable(UProxyAsset* Proxy, UProxyTable* ProxyTable)
{
    FInstancedStruct Struct;
    Struct.InitializeAs(FLookupProxyWithOverrideTable::StaticStruct());
    FLookupProxyWithOverrideTable& Data = Struct.GetMutable<FLookupProxyWithOverrideTable>();
    Data.Proxy = Proxy;
    Data.OverrideProxyTable = ProxyTable;
    return Struct;
}

サンプルコード

で、これを使うことでEvaluateProxyと同様に、ProxyAsset, ProxyTableを使った選定処理を独自のC++コードで実装することができます!

UObject* AXXX::GetObjectFromProxyTable(UProxyTable* ProxyTable, UProxyAsset* ProxyAsset)
{
    if(ProxyTable && ProxyAsset)
    {
        FInstancedStruct ProxyStruct =
            UProxyTableFunctionLibrary::MakeLookupProxyWithOverrideTable(ProxyAsset, ProxyTable);
        FLookupProxyWithOverrideTable& LookupProxy = ProxyStruct.GetMutable<FLookupProxyWithOverrideTable>();

        FChooserEvaluationContext Context;
        return LookupProxy.ChooseObject(Context);      
    }

    return nullptr;
}

ProxyTableChooserTableと比べると設定パラメータが少ないので、C++で実装する際もシンプル!(その分できることも少ないですが…)

前回の記事にも書いた通り、ChooserTableの長所である条件判定処理が不要で、ID(ProxyAsset)が確定している場合は今回のようなコードでプロジェクト独自のアセット管理・取得処理を書いておくと便利だと思います。

さいごに

ChooserをC++で扱うには色々複雑になるのかなぁ…と思っていたら、案外シンプルに実装できることがわかって一安心です!

Chooserは多機能で便利ではありますが、その反面プロジェクトにおける運用・管理フローを整備しないとカオスなことになりそうです。そして、Evaluate Chooser / Proxyでは物足りない部分をC++で独自実装しておくとフローがスッキリしたり、設定ミスなどによるヒューマンエラーも防げる気がしました!

そんなこんなで少しでも参考になれば幸いです。(次はChooserTableアセットの自動生成・設定を調べていきたいところ)

おしまい




以上の内容はhttps://pafuhana1213.hatenablog.com/entry/2024/12/31/161529より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14