はじめに
1回目ではC++のクラス設計についてお話しました。
そこで、処理負荷が高くなってしまうためAnimBPのイベントグラフは使わずC++で書くと言いましたが、
EpicのサンプルであるLyraStarterGameではどうしているか見てみようと思います。
Tour#1 ABP_Mannequin_Baseのイベントグラフ
これがLyraStarterGameのベースとなるAnimBPです。
要約すると
イベントグラフはパフォーマンスのボトルネックになるためUE5で新しく追加された
BlueprintThreadsafeUpdateAnimation関数に処理を書きました。これは並列実行されます。
Lyraでもイベントグラフは空でした。でも、C++化するのではなくBPを並列実行できるようにして対策したようです。
BlueprintThreadsafeUpdateAnimation関数を見てみましょう
Tour#2 BlueprintThreadsafeUpdateAnimation関数
要約すると
この関数はアニメーションを選択するために必要なゲームデータを収集しています。
しかし、スレッドセーフ関数はゲームオブチェクとを直接参照することはできません。
代わりにProperty Access systemを使ってデータにアクセスします。
この関数は並列実行されるため、ワーカースレッド上で実行されます。1回目でお話したマルチスレッドの注意点がここでも言及されていますね。
PropertyAccessSystemが自動的にパラメータをコピーしてくれるみたいです。
PropertyAccessSystemとは?
右クリックから"PropertyAccess"と検索すると出てきます。
ここでバインドした値をGameThreadで事前にコピーしておいてくれるという仕組みのようです。
独自の関数をリストに追加するにはUAnimInstanceを継承したクラスにUFUNCTIONでBPに公開するするだけでよい。
UFUNCTION(BlueprintPure)
ALyraCharacter* GetLyraCharacter() const { return nullptr; }
自動とスレッドセーフって何でしょう?ドキュメントが無くてわからん!
エンジンの中身を調べるしかなさそうなので調べてみましょう。
Enumの定義はこれみたいです
UENUM()
enum class EAnimPropertyAccessCallSite
{
// Access is made on a worker thread in the anim graph or in a BP function
WorkerThread_Unbatched UMETA(DisplayName="Thread Safe"),
// Access is made on a worker thread before BlueprintThreadSafeUpdateAnimation is run
WorkerThread_Batched_PreEventGraph UMETA(DisplayName="Pre-Thread Safe Update Animation"),
// Access is made on a worker thread after BlueprintThreadSafeUpdateAnimation is run
WorkerThread_Batched_PostEventGraph UMETA(DisplayName="Post-Thread Safe Update Animation"),
// Access is made on the game thread before the event graph (and NativeUpdateAnimation) is run
GameThread_Batched_PreEventGraph UMETA(DisplayName="Pre-Event Graph"),
// Access is made on the game thread after the event graph (and NativeUpdateAnimation) is run
GameThread_Batched_PostEventGraph UMETA(DisplayName="Post-Event Graph"),
};
「スレッドセーフ」は何もしない直接参照みたいですね。
「自動」はコピー元と先が両方スレッドセーフの場合は何もしない「スレッドセーフ」、
そうじゃない場合はゲームスレッドで事前にコピーされる「ゲームスレッド(プリイベントグラフ)」
になるっぽいですね。
if(InContext.ContextId == ContextId_Automatic)
{
if(InContext.bSourceThreadSafe && InContext.bDestThreadSafe)
{
// Can only be in the worker thread batch if both endpoints are thread-safe
return (int32)EAnimPropertyAccessCallSite::WorkerThread_Unbatched;
}
else
{
return (int32)EAnimPropertyAccessCallSite::GameThread_Batched_PreEventGraph;
}
}
IKとかどうやってるの?
IKするためには足の下の地面の高さを取得する必要があります。ワーカースレッドのBPからレイキャストってできなかった気がするけど、どうやってるのか調べてみました。
ABP_Mannequin_BaseのコントロールリグでCR_Mannequin_FootPlantが登録されていて
そこでTraceされてました。ControlRigの処理負荷ってどんなもんなんでしょうね。足のIKくらいだったら自前で処理書いたほうが早い気がするのですが今度UE5のIKRigとか調べてみたいです。
UE4やC++でも並列処理にすることはできる
FAnimInstanceProxy::Updateをオーバーライドしてそこに処理を書けばc++でも並列処理にすることはできます。
ただ、C++の処理がそこまで重たくないのであれば並列処理する必要はないと思います。
実際に自分がやっているプロジェクトではほとんど変わらず、あまり意味がなかったので元に戻しました。
まとめ
-
Blueprintが好きな方はLyraのやり方が良いでしょう。
BPの処理はワーカースレッドで行われるため処理負荷を気にする必要なさそうです。 -
C++が好きな方はアニメーション設計①で説明したやり方が良いでしょう。
C++で書けば処理負荷は十分軽いのでゲームスレッドで処理しても問題ないでしょう。
そのほうがスレッドセーフじゃない関数も使えて便利です。