環境:UE4.27、UE5.0
はじめに
さあ、UEでアニメーションのプログラムを始めましょう。何からやったらいいでしょう?
まずは軽くUEのアニメーションシステムについて説明します。
アニメーション関係のアセット
これらアニメーション関係のアセットのほとんどはSkeletonに依存しています。
同じSkeletonを共有していないと再生することができないようになっています。
アニメーション関係のクラス、構造体
USkeletalMeshComponentクラス | メッシュを表示してくれる、UAnimInstanceを保持している |
UAnimInstanceクラス | AnimBlueprintの親クラス、アニメーションの再生などの基本機能 |
FAnimInstanceProxy構造体 | AnimationNodeが参照するデータの置き場所 |
それぞれの関係は以下のようになっていて、
SkeletalMesh、AnimBPアセットはSkeletalMeshComponentに
AnimSequence、AnimMontageアセットはAniminstaceにセットして使います。
アニメーションのブレンドやロジックを組めるAnimBlueprint
AnimBlueprintには「イベントグラフ」と「AnimGraph」という編集タブがあり、ここに処理を書いていくことができそうです。
このあたりに機能を追加していけばいい感じにカスタムされたアニメーションシステムにできそうです。
このあたりを詳しく見ていきましょう。
その前に、設計の方針
- 処理負荷を優先する。なので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つ作ります。
メインのAnimBPはSkeletalMeshにセットします。
CharacterBPのSkeletalMeshComponentを選択すると
「詳細」のAnimationカテゴリーにセットするところが見つかると思います。
PostProcess用のAnimBPはスケルタルメッシュアセットにセットします。
スケルタルメッシュカテゴリーにセットするところがあります。
これで準備はできました。
AnimBPへ変数を公開する
このままではまだ何も変わらないのでブレンドツリー(AnimGraph)にパラメータを伝えるために先程作ったクラスに変数を用意します。
protected:
// これらBlueprintReadOnlyの変数はワーカースレッドから参照されるためNativeUpdateAnimation以外から変更してはいけません
UPROPERTY(BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
float WalkDirection;
AnimGraphで使えるようになりました。
LocomotionのBlendSpaceとかに使えそうですね。
ところで、こんなコメントが書かれていたと思います。
「これら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システムを見てみよう