執筆バージョン: Unreal Engine 5.6
|
こんにちは。今回はControlRigを使って簡単なプロシージャルアニメーションを作ってみます。ControlRigには面白い機能をもったノードが多数あります。その中のTimeloopというノードを使い、おなじみのキーボーとムラスケに簡単なアニメーションをつけてみます。
キーボーとムラスケの3Dモデルは以下のリンクからダウンロードできます。
https://gamemakers.jp/article/2023_03_29_35480
プロシージャルアニメーション
Google先生に聞きました。
「プロシージャルアニメーションとは、数式やアルゴリズムなどの手続き的な方法でアニメーションを生成する技術です。手作業でキーフレームを打つ従来のキーフレームアニメーションとは異なり、自動的に動きを生成するため、より効率的に複雑な動きを表現できます」
とのことです。キーボーとムラスケのようなシンプルな構造は、Control Rigで自動的に動かす練習台に適していると思うのでやってみます。
Timeloop ノード
Timeloopノードは時間の値をシミュレートして出力します。設定した時間でループする値を受け取れるので、歩きなどのサイクルの動きを作るのに使えるノードです。Speedが1.0でDurationが1.0の場合、1秒後に1.0になる値が出力されつづけます(ちょっとややこしい言い方ですいません)。毎フレーム動くイベントの、FowardsSolveかBackwardsSolveで使うノードです。
パラメータの説明は後回しにして、まずはFlipFlopの出力を使ってアニメーションを作っていきます。FlipFlopは、0からDurationで設定した値まで出力され、Durationの値に到達したあとは逆に0に向けた値が出力されます。Durationが1.0 ならば、0~1、1~0 と繰り返します。
ムラスケの羽にリグを設定する
リグ設定の前にマテリアルを少し変更します。ムラスケの羽は裏面の表示がされていないので、ムラスケのマテリアルを開いてTow Sidedのチェックを入れます。

ではリグの設定をしましょう。ムラスケとキーボーの骨の構造はほぼ同じで、ムラスケには羽用の骨があります。その他の骨構造は全く同じなのでムラスケでControlRigを作成することでキーボーにも対応できます。ムラスケのスケルタルメッシュを右クリックのメニューで、Create > Control Rig のメニューを選択して、ControlRigを作ります。今回は、CR_MurasukeMoveという名前にしました。

最初は結果が確認しやすい羽の動きから作ります。ムラスケの羽の骨のローカル軸を確認すると、図のようにY軸が下を向いています。右側の羽も左側の羽と同じ向きでした。羽の回転はY軸だけ使えば良さそうです。この羽ばたきの動きを手付のアニメーションで作るには細かくキーフレームを打っていく必要があり、キーのコピペが使えるとはいえそこそこ面倒です。自動でできるならその方が楽ですし、羽ばたき速度をあとから自由に変えられるメリットもあります。

FowardsSolveのイベントに処理をつないでいきます。まず、SpeedとFlipFlopValueというfloatの変数を作りました。FlipFlopの出力結果をあとで使いまわししやすいように変数に入れておきます。Speedの値も同様です。Timeloopのノードを出して、SpeedとFlipFlopValueの変数を接続します。

特にコントローラーを仲介せずに骨の回転を直接制御しようと思います。羽の骨をリググラフにドラッグして出てきたメニューから、SetRotationを選択します。左右両方の羽のSetRotationノードを出しておきます。
いきなり完成図ですが、以下の図のようなノード構成にしました。大雑把に解説すると、TimeloopのFlipFlop出力を回転値にしてなんやかんややってから左右それぞれの羽のY軸へ渡している流れです。
- Spaceをローカル軸の回転にするためLocalSpaceにする。(ムラスケ本体が傾いた時にその傾きにあわせて回転してくれます)
- ValueにFromEulerノードを接続し、各軸のオフセットを設定する。(羽のローカル軸は、Y軸が下を向いていたのでそれに合わせます)
- RotationOrderは、Y軸を優先したYZXに設定する。(Orderを変えると、オフセットの角度設定も変わってしまいます)
- FlipFlopの出力が0~1なので、60を乗算して0~60の値にする。(最大60度の回転角度で往復させる意図です)
- -45を足し算する。(0度の状態から-45度回転させて良い感じの羽ばたきに見せる)
- 右側の羽は逆回転にしたいのでNegateノードを挟む。(入力値をネガティブにして出力します)

Speedの値が1.0のままだと遅すぎなので、10くらいに設定しました。いい感じの羽ばたきになったと思います。
レベルに置いてAIで動かす
ムラスケのBPを作ってレベル上に置き、AIで動かします。あとでキーボーのBPも作るので両方の親とするBPを作って、その親BPにAIの処理を書きます。新規作成のBlueprint ClassからCharacterを選択してBP_CharBaseを作りました。

BP_CharBaseを開いて設定します。またまた完成図ですが、イベントグラフで以下のノードを組むことでプレイヤーを追いかけるAIの動きができます。EventTickではなくループするTimerを使うのも良いかもしれません。BPの中にあるSkeletalMeshは空っぽの状態にしておきます。MaxWalkSpeedの値など、キャラクターの設定はお好みでどうぞ。
- GetAIControlerを右クリックの検索から見つけて配置する。
- ControlledActorをSelfにする。
- AIControlerのReturnValueから検索してMoveToLocationノードを出す。
- AcceptanceRadiusを、0.0以上の値にする。
- UsePathfinding のチェックをTrueにする場合は、レベル上にナビメッシュが必要なので設定する。(ナビメッシュが無いならFalseにします)
- PlayerPawnをGetするノードを配置し、PlayerのActorLocationを取得する。
- Playerのロケーションを、MoveToLocationの、Distにつなぐ。

次はControlRigを使うためのAnimationBPを設定します。
ムラスケのスケルタルメッシュを右クリックして、Create >Anim Blueprint を選択してAnimationBPを作ります。ABP_Gamemakers という名前にしました。
イベントグラフに最初から配置されているUpdateAnimationのノードは無視して、Blueprint Initialize Animatonのイベントノードを出します。TryGetPawnOwnerノードからBP_CharBaseにキャストするノードを出します。キャスト結果の出力を変数に入れておきます。これは後で使います。

Animグラフに移動して、ControlRigを使えるようにノードをつなぎます。以下の図のようにしました。ControlRigノードを選択して、DetailsのControl Rig Classを設定するところでCR_MurasukeMoveを選択します。コンパイルすると、プレビューのムラスケが羽ばたくと思います。

BP_CharBaseを親として、BP_Murasukeを作ります。BP_CharBaseを右クリックして、一番上に出てくるCreate Child Blueprint Crass を選択します。

BP_Murasukeを開いて、スケルタルメッシュにムラスケのメッシュを設定します。飛んでいる想定なので、少し高い位置に配置して(図ではZ軸に50ほど)、Z軸で-90度回転させます。

スケルタルメッシュのAnimatonの項目で、先に作っていたBP_GamemakersのAnimationBPを設定します。

レベル上の適当な場所にBP_Murasukeを配置してプレイ開始すると、プレイヤーに向かって飛んでくると思います。
圧がすごい……。
ムラスケの体を傾ける
直立で飛んできてちょっと怖いので少し傾けてみます。あとついでにふわふわさせる上下動も入れます。
これまた完成図ですが、以下のようなノード構成にしました。
- Pelvis骨のTransformをGetとSetで両方配置する。Spaceは両方ともGlobalのまま。
- Transformを展開して、Scale3DだけそのままGetからSetへ接続する。
- FromEulerノードを出して、X軸に-30度を設定する。
- Quaternionの乗算ノードを出して、FromEulerの出力と、GetTransform PelvisからのRotationを接続する。
- 乗算の結果をSetTransform PelvisのRotationへ接続する。
- Timeloopノードを出してSpeedとDurationをいい感じに設定する。
- FlipFlopの値をfloatの乗算ノードにつないでBの側に上下に移動させたい高さを設定する。
- Vectorの加算ノードを出して、A側をGetTransform PelvisのTranslationからつなぎ、B側のZ軸にFlipFlopからの出力を接続する。

この上下動にも変数に格納してあるFlipFlopValueの値を使えそうですが、羽の動きに使うために速度を10倍にしていたので、上下動で使うにはスピードが早すぎです。Pelvisをゆらゆら揺らす方法は様々あるのですが、今回は最初に設定したものとは別のTimeloopを使いました。
これで少し前方に傾いてゆらゆらしながら近づいてくる動きになります。
やっぱり圧がすごい……。
キーボーを歩かせる
キーボーには特に何もしていないので、こいつは歩くような動きをつけてみようと思います。膝も足首も無いので、羽の動きとほぼ同様に設定できます。注意するところは左右が逆になって交互に動くことです。
まず、IsWalkというBoolの変数を作って、いったんFowardsSolveの直後で「歩き」と「飛ぶ」を分岐させる流れにします。毎度の完成図ですが、以下の図のようにノードを配置しました。歩くとき用のTimeloopの速度と飛ぶときの羽用の速度をIfノードで選択できるようにします。IsWalkをBranchで分岐させて、False側に今までに作った飛ぶ羽の動きをつないでおきます。True側に歩く処理を書いていきます。(ControlRigグラフでは、IfとBranchは別のノードです)

Trueの先に羽の動きをコピペして配置しておきます。ムラスケもIsWalkをTrueにすれば歩けるようにするためです。上記の図の流れで、歩き用のTimeloopにわたす速度を2.0に設定したので、歩くときは羽の動きがゆっくりになる想定です。(ただし、現状のムラスケのBPはスケルタルメッシュの位置が高いので、歩く場合はなんらかの工夫が必要になります)
歩きは左右の手足がそれぞれ動くので、SetRotationノードは4個必要です。手足のそれぞれの骨からリググラフ上に出しておきます。おなじみ完成図ですが、まずは脚の動きからです。(図のSequenceのBの流れに、歩く用の羽の動きをコピペしてつないでます)。ムラスケもキーボーも脚の付け根が少し特殊なところにありますが、軸が斜めに曲がっているわけではないので気にせず回転させて大丈夫です。
- SetRotationのSpaceは、LocalSpaceにする。
- SetRotationのValueにFromEulerをつなぐ。RotationOrderはZ軸優先のZYXにする。
- FlipFopValueの出力に120を乗算する。(0~120度の範囲で回転させる意図です)
- その出力に-60を加算する。(真ん中から前後に振る想定です)
- 加算からの出力を左足から伸びているFromEulerのZへ接続する。(これで左足ができます)
- 左足の回転に使った加算からの出力にNagateをつなぐ。
- その出力に180を加算して反対向きに回転するように設定する。(これで、左右が交互に動くようになります)

一気に行きましょう。同様の流れで腕も組んでしまいます。ほぼ脚と同じです。腕は少し下に下げた状態にしたいので、Y軸で135度ほどオフセットしておきました。左右は反転しているので、右腕のY軸はマイナス方向に回転です。

これでRigのプレビューで歩く動きが再生されると思います。これらの手足の処理はほとんど同じ流れなので、関数にしてしまうのも手だと思います。あと、歩幅の120とかオフセットの135など、直接値を入れてしまっているところも、変数にして調整可能にするのが良いかと思います。
まだ歩きとしては足りないので、さらに腰の上下動を入れます。PelvisのGetとSetのTransformを出し、TranslationのZ軸の値を加工します。
EvaluateCurveというノードを使います(詳細の説明は割愛します)。このノードは入力で受け取った値をカーブで変化させて出力してくれるノードです。Remapの機能も持っています。
- PelvisのGetTransoromからSetTransformへ、RotationとScale3Dをそのままつなぐ。
- SetTransformのTranslationに加算をつないで、AにはGetTransformからの出力をつなぐ。
- EvaluateCurveノードを出し、入力のValueにFlipFlapValue変数からの出力をつなぐ。
- 0.5の位置で値が1.0になるようにカーブ上にキーを追加する。(カーブの白い線の上を右クリックで追加できます)
- 1.0の位置のキーは値を0にする。
- キーの補間をAutoに設定して、両端のハンドルを傾けて以下の図のようなカーブにする。(これで、跳ねるような動きになります)
- Source MinimumとMaximumはそのまま0.0と1.0にしておく。
- Target Minimumを0.0 、Target Maximumを10.0ほどの値にする。(腰が上がるときの最大の高さとする値です)
- EvaluteCurveのResultを、Transformの加算ノードのB側Zにつなぐ。

いい感じに歩きました。
キーボーのBPで動かす
BP_CharBaseから、子BPとしてBP_keyboをつくります。スケルタルメッシュをキーボーに設定して、キーボーのメッシュのAnimationBPをABP_Gamemakersに設定しておきます。
親BPのBP_CharBaseで、IsWalkの変数を作ります。
親BPにある変数は子のBPのMy BlueprintのVARIABLESに出てこないので、子のBP側でMy Blueprintの歯車のメニューから、Show Inherited Variables にチェックを入れます。これで親BPの変数が全部見えるようになります。

キーボーのBPで、親BPから見えるようにしたIsWalk変数をConstruction Script かBeginPlayでSetで配置してTrueに設定しておきます。

ムラスケのControlRigで、IsWalk変数の目玉アイコンを開いた形にして、変数を外から見えるようにしておきます。

ABP_GamemakersのAnimグラフに配置してあるControlRigノードのDetailsから、InputにあるIsWalkにチェックを入れてControlRigノードに入力ピンを出しておきます。

ControlRigノードのIsWalk変数入力の上で右クリックして出てくるメニューから、Binding> CharBaseRef(キャストした先を入れていた変数名)> IsWalk と選択します。(キャストしていたので、この変数名が出てきます)。

IsWalkをキーボーはTrue、ムラスケはFalseにしておくことで、同じRigと同じAnimationBPを使いつつも動きを分けることができました。
かわいい。
今回はやりませんでしたが、キャラクターのVelocityを受け取って速度によって手足の動きのスピードを変えたり、前傾姿勢の角度を変えるなんてこともできると思います。
Timeloop のパラメータ
SpeedとDurationを両方とも1.0に設定したものとして説明します。
Absoluteは、1秒毎の速度で毎フレームの値が加算されていきます。加算されっぱなしの動きです。Relativeは、0から1になるまでの値が出力され、1になったら0に戻ります。1秒毎に0~1を繰り返します。FlipFlopはすでに使っているので割愛します。
EvenのBoolは、偶数回のループのときにTrueを出力します。このフラグはRelativeかFlipFlopの出力で使います。FlipFlopでは、0~1になるときにTrueになり、1~0になるときにFalseになります。
Normalize のBoolは、TrueにするとDurationで設定した値が0~1の範囲に収まるように調整されて出力されます。このフラグはAbsoluteでは効果がありません。

おわりに
今回の例はちょっと単純すぎたかもしれませんが、サイクルの値を作って利用するというのはプロシージャルアニメーションの基礎になると思います。TimeLoopのノードは一個だけでサイクルに必要な値が出力されるのでとても便利だと思います。
プロシージャルアニメーションといえば、UE5.6でLocomoter という機能が追加されました。まだ実験的なあつかいで少し不安定ですが、個人的にはとても期待している機能です。UEのプロシージャルアニメーションの進化が楽しみです。