LoginSignup
4
4

More than 1 year has passed since last update.

UnrealEngine アニメーション設計① スレッドセーフなクラス設計

Last updated at Posted at 2022-09-26

環境:UE4.27、UE5.0

はじめに

さあ、UEでアニメーションのプログラムを始めましょう。何からやったらいいでしょう?
まずは軽くUEのアニメーションシステムについて説明します。

アニメーション関係のアセット

image.png Skeletonアセット 骨構造、リターゲット
image.png SkeletalMeshアセット メッシュ、リファレンスポーズ、PostProcessAnimBP
image.png AnimBlueprintアセット Update処理、ブレンドツリー、StateMachine
image.png AnimSequenceアセット キーフレームアニメーション、Additive設定、ルートモーション
image.png AnimMontageアセット 複数のAnimSequence、ループ、ブレンド設定

これらアニメーション関係のアセットのほとんどはSkeletonに依存しています。
同じSkeletonを共有していないと再生することができないようになっています。
image.png

アニメーション関係のクラス、構造体

USkeletalMeshComponentクラス メッシュを表示してくれる、UAnimInstanceを保持している
UAnimInstanceクラス AnimBlueprintの親クラス、アニメーションの再生などの基本機能
FAnimInstanceProxy構造体 AnimationNodeが参照するデータの置き場所

それぞれの関係は以下のようになっていて、
SkeletalMesh、AnimBPアセットはSkeletalMeshComponentに
AnimSequence、AnimMontageアセットはAniminstaceにセットして使います。

image.png

アニメーションのブレンドやロジックを組めるAnimBlueprint

AnimBlueprintには「イベントグラフ」と「AnimGraph」という編集タブがあり、ここに処理を書いていくことができそうです。
このあたりに機能を追加していけばいい感じにカスタムされたアニメーションシステムにできそうです。
このあたりを詳しく見ていきましょう。

image.png

その前に、設計の方針

  • 処理負荷を優先する。なのでAnimBPのイベントグラフは使わずC++に書く
  • 補助骨や揺れ物などの処理はメッシュごとに変わるのでPostProcessAnimBPに書く
  • UE4はStateMachineを複数のSkeletonで共有できないので使わない(UE5はAnimtionBlueprintTemplateが実装されてできるようになった)
  • ブレンドツリー(AnimGraph)はなるべくシンプルに

クラスを用意しよう

イベントグラフ(BP)は使わないという方針なので、AnimBlueprintの親クラスをC++で用意します。
(名前は適当)

  • UMyAnimInstance
  • UMyAnimInstancePostProcess

メイン用とPostProcess用に2つ作ります。

UCLASS()
class UMyAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

public:
	UMyAnimInstance();
};

UMyAnimInstancePostProcessも同じ感じで作ります。

後はAnimBPを作るときにSkeletonと先ほど作ったクラスを選択します。
AnimBPも2つ作ります。
image.png

メインのAnimBPはSkeletalMeshにセットします。
CharacterBPのSkeletalMeshComponentを選択すると
「詳細」のAnimationカテゴリーにセットするところが見つかると思います。
image.png
image.png

PostProcess用のAnimBPはスケルタルメッシュアセットにセットします。
スケルタルメッシュカテゴリーにセットするところがあります。

image.png

これで準備はできました。

AnimBPへ変数を公開する

このままではまだ何も変わらないのでブレンドツリー(AnimGraph)にパラメータを伝えるために先程作ったクラスに変数を用意します。

protected:
	// これらBlueprintReadOnlyの変数はワーカースレッドから参照されるためNativeUpdateAnimation以外から変更してはいけません
	UPROPERTY(BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	float WalkDirection;

AnimGraphで使えるようになりました。
image.png
LocomotionのBlendSpaceとかに使えそうですね。
image.png

ところで、こんなコメントが書かれていたと思います。
「これらBlueprintReadOnlyの変数はワーカースレッドから参照されるためNativeUpdateAnimation以外から変更してはいけません」

これについて説明します。

スレッドセーフな作りにする

AnimGraphは並列で動作するようになっています。なので変数はスレッドセーフでなければいけません。
並列処理は複数のCPUで同時に処理ができるため高速に処理できますが、同じメモリ領域に複数のスレッドが同時にアクセスするとバグったり最悪クラッシュすることになります。

例えば、配列のサイズを取得してfor文で処理していたらいつの間にか配列のサイズが変わっていて配列外参照でクラッシュ。とかが考えられます

	int32 arrayNum = array.Num();
	for(int32 i = 0; i < arrayNum; ++i)
	{
		array[i] = 0;
	}

どのタイミングで書き換えればよいかと言うと、UAnimInstance::NativeUpdateAnimationです。この関数はAnimGraphの処理が走る前にGameThreadで実行されるので安全に書き換えることができます。

わかった、じゃあNativeUpdateAnimationでいろんな情報を取得して変数に突っ込めばいいのね。となるのですが。
そうしてしまうとUAnimInstanceがいろんなコンポーネントに依存してしまって再利用性が著しく悪い設計になってしまいます。

なのでそれを受け止める新しいクラスを作ります。
ActorComponentを継承したクラスです。このクラスの変数はいつでも自由に変更してOKなので、キャラクターアクターに持たせてこのクラスの関数を呼び出すようにしてもらいます。

UCLASS()
class GAMEMODULE_API UMyAnimationComponent : public UActorComponent
{
	GENERATED_BODY()

protected:
	virtual void Update(float DeltaSeconds);

protected:
	UPROPERTY()
	UMyAnimInstance* AnimInstance;
}

パラメータをUMyAnimationComponentに一旦貯めておき、Update()関数内で安全にUMyAnimInstanceを書き換えるという戦略です。
そのために、NativeUpdateAnimationのタイミングでUpdateが呼び出されるようにしましょう。

UMyAnimInstanceにイベントを用意してNativeUpdateAnimationからBroadcastしてやります。
UMyAnimationComponentは初期化時にOnUpdateAnimation()を使ってUpdate関数にバインドしてあげればいいですね。

あと、protectedの変数にアクセスできるようにフレンドにしておきます。

UCLASS()
class UMyAnimInstance : public UAnimInstance
{
	friend class UMyAnimationComponent;

	省略

public:
	/// ポーズの更新前に呼び出されるイベント、このタイミングなら安全に変数を書き換えられる
	DECLARE_EVENT_OneParam(UAnimInstanceGame, FUpdateAnimationEvent, float /*DeltaSeconds*/);
	FUpdateAnimationEvent& OnUpdateAnimation() { return UpdateAnimationEvent; }

private:
	// イベント
	FUpdateAnimationEvent UpdateAnimationEvent;
};

void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	UpdateAnimationEvent.Broadcast(DeltaSeconds);
}

こうすることで、安全に外から値をセットしてもらうことができるのでAnimationシステムは他のコンポーネントを参照しな
くてすみます。依存関係も最低限に抑えられ再利用性も保たれていいですね。

UMyAnimationComponent::Update()には変数をコピーする以外にも顔向けやIK、ブレンド率、エイミングなどの処理を書いていくことになります。次回以降で説明していこうと思います。

今回新規に作成したクラス

UMyAnimInstance AnimBlueprintの親クラス
UMyAnimInstancePostProcess PostProcessAnimBPの親クラス
UMyAnimationComponent Characterシステムなどから直接アクセスするクラス

次回

次回はUE5のLyraStarterではどうやっているか見てみましょう。


UnrealEngine アニメーション設計② UE5のLyraStarterGameのAnimationシステムを見てみよう

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4