執筆バージョン: Unreal Engine 5.5
|
こんにちは。今回は、ControlRigを使って土台の回転と砲身の回転を分けてなんかそれっぽく動く砲台を作ります。シーケンサーでアニメーションをつけるのではなく、ランタイムで動かすためにControlRigを使います。
ControlRigの詳細については過去のおかずさんのセッションがとても詳しいです。UE4.25のものですが今でも通用する内容です。
猫でも分かる Control Rig UE4.25 版 【UNREAL FEST EXTREME 2020 SUMMER】
下図のような砲台モデルを作りました。この記事の最後にプロジェクトのZipファイルのダウンロードリンクを記載しました。よかったらダウンロードして確認してみてください。
砲台モデルはControlRigで動かすのでスケルタルメッシュです。ルートの骨をZ軸だけで回転させて砲身のピボットをY軸だけで回すようにしていきます。バレルの中間にある骨は、弾を撃ったときの反動をアニメーションで作るために設定したものです。今回紹介する動きには関係ない骨ですが、射撃時に反動するとかっこいいので配置しておきました。(反動はスケルタルメッシュのアニメーションで動かす想定です)

さて、砲台なのでターゲットを狙うわけですが、狙うといえばAimです。
Aimノードって4つあるの?
ControlRigグラフで使えるAimノードは4種類あります。それぞれ細かなパラメータの名前など異なりますが、「対象をターゲットに向ける」という基本の機能は変わりません。
- Aim
- AimMath
- AimConstraint
- AimAt
このうち、AimAtは非推奨となっているのでご注意ください。下図のマウスオーバーの説明文にあるように、AimConstraintを使うのがよさそうです。

この記事では、Aimノードだけを使ってRigを組んでいきます。その他のノードを使ってもほとんど同じ処理内容になってしまうので割愛します。
Aimノードでターゲットをねらうようにする
まずControlRigアセットを作ります。一番手っ取り早い方法は、スケルタルメッシュを右クリックして、Create > ControlRig を選択することです。これで選択していたスケルタルメッシュのスケルトン階層構造が参照されたControlRigが作られます。

とりあえずシンプルに砲台の土台ごと回転させてターゲットの方向を向くようにします。ControlRigグラフのどこでも良いので右クリックの検索からAimと検索してAimノードを出します。
Aimノードで重要なのは一番上にあるItemで設定する「ターゲットに向けるモノ」です。砲台のRootボーンを直接設定します。次にターゲットに向く軸を設定します。今回使う砲台のモデルは、X軸正の方向が弾の発射方向を向いているのでAimの軸になります。

Aimノードの図のPrimary枠を展開してXに1.0を入力します。ターゲットの設定はひとまずおいといて、Secondaryを展開します。Secondaryは、いわゆるアップベクトルを設定するところです。この部分はすべて0でもAimは動作しますが(警告が出ますが)フリッピングが発生しやすくなるので設定したほうが良いです。フリッピングとは、突然急速に逆方向に回転してしまうような動作のことです(IKでもよく見かけます)。アップベクトル(どの方向を上とみなすのか)を設定すると「姿勢」が定まるのでエイムの回転が安定します。今回のモデルでは、Z軸の正の方向がアップベクトルになります。

続いてターゲットの設定をします。ターゲット用のコントローラーを作りリググラフ内にドラッグしてGetTransformノードを出します。作成したコントローラーはボーンの階層に含めないようにします。
GetTransformノードの出力を展開してTransrationだけをターゲットに接続します。これで砲台がターゲットの方に向くようになりました。Rootをエイムさせたので今のところ砲台の土台も含めて回転しています。

AimノードのSecondaryにもターゲットを設定する部分がありますが、ここにもアップベクトル用の新たなコントローラーを作ってGetTransformノードからつなぐのも良いと思います。説明図でZに100と入力しているのは、上を示す方向に100だけ進んだLocationを指定するという意図です。
あと、TargetSpace という項目がPrimaryとSecondaryの両方に存在していますが、この項目はほぼ触りません。展開してみると赤い文字になっていて気になりますがそのままで大丈夫です。ここは何も指定しなければGlobal Spaceを設定したことになるようです。(このSpaceについては後述します)
それぞれの回転軸を分離する
土台の部分はZ軸のみの回転にして、砲身はY軸のみの回転にします。
Aimによって方向が定まったRootのボーンをGetControlで取得し、そこから回転だけ抜き出します。回転の値はおなじみのローテータではなく、ControlRigではQuaternionになっています。このままX, Y, Zに分離するのは無理なので、Eulerに分解するToEulerノードを使います。プルダウンメニューで回転優先順位が選べるのでRootの回転軸であるZを先頭にします。

次はZ回転のみにした値を再びルートの回転に戻す処理を書きます。
RootBoneをRigグラフ上にドラッグして出せるSetRotationノードを配置して、Valueに今度はFromEulerノードをつなぎます。ToEulerのZからFromEulerのZに接続します。その他の値は0にします。これでAimの回転がZ軸だけになりYaw回転だけするようになりました。ノードが大きめでちょっと邪魔ですがやってることは単純です。
次は砲身の回転を作ります。
Aimが実行された直後の回転値は、土台とその子になっている全体が回っている値なので再びそこからToEulerを使って取り出します。今度はY軸だけ取り出します。回転優先順位は今度はYを先頭にします。つなぐ先はBarrelPivotのSetRotationノードです。このとき、SetRotationノードのSpaceの設定をLocal Spaceに設定します。(このSpaceについては後述します)デフォルトのGlobalのままにすると、Y軸以外を0にしているため砲身の回転が土台についてこなくなります。土台の子としての回転をたもったまま砲身のY軸だけ回るようにするためにLocal Spaceに設定します。

実はこの例に上げた流れは「RootのAim結果は砲身と一緒だろう」という少々乱暴な前提のもとで処理をしています(実際Aimしているのはルートの骨なので、砲身が指し示す先は少しズレています)Aimノードをもう一つ作ってItemをBarrelPivotに指定すると砲身は正しい場所を指します。そりゃそうです。
Aimノードをもう一つ置く場合は、RootのAimノードをコピーしてItem欄をBarrelPivotに置き換え、同じターゲットを接続すればOKです。このとき2つ目のAimノードは、Root骨のSetRotationの処理が終わったあとに置きます。Aimノードを2つ並べてから軸の分離処理をするとうまく動きません。下図はAimノードをもう一つつけたパターンのものです。

これで土台がZ軸、砲身はY軸の回転のみになりました。
砲身の角度に制限をつける
砲身の仰角はまだいいとしても、俯角はいきすぎて土台に食い込みます。これに制限をつけていい感じにします。砲身の角度に制限をつけるので、Y軸の値をわたしてる間にクランプノードを入れます。ControlRigのPrintノードで確認してみたところ、使っているモデルでは-60~15度くらいの範囲で良さそうでした。

Aim回転にラグをつけてみる
今はクイックに追いかけすぎなので、少し遅れて反応するようにします。Quaternionに対応したAccumerateLerpノードを使います。
このノードの、Integrated Delta Time をTrueにして、Blendの値を1.0にすると、1秒かけて結果が変化します。0.5にすると2秒になり、2.0にすると0.5秒になります。

いったん1.0にしてつなぎます。
全体的にゆっくりついてくるようになりました。ゆっくり過ぎかもしれません。
レベル上で確認する
アニメーションBPと砲台のBPを作る
砲台はスケルタルメッシュなのでアニメーションBPで動かしたいと思います。そのため、砲台のBPをPawnかCharacterで作ります。今回はシンプルにしたいのでPawnにしました。それから、砲台のスケルタルメッシュからアニメーションBPを作ります。砲台のBPにスケルタルメッシュと、アニメーションBPを設定します。

ターゲットのロケーションを砲台のControlRigに渡す流れ確認
ここからは各BP間で通知して値の受け渡しを作っていきます。方法は色々あると思いますが今回の流れとしては、
- ControlRigで外からの値(今回はターゲットのロケーション)を受け取れるように変数を作って外から見えるようにする。
- レベル上に配置した砲台の参照をレベルBPに置く。
- レベルBPの砲台の参照からBluePrintインターフェイスの関数を介して、砲台のアニメーションBPにプレイヤー(ターゲット)のロケーションを送る。
- 砲台のアニメーションBPでBluePrintインターフェイスのイベントを配置し、プレイヤーのロケーションを変数に受け取る。
- 受け取った変数の値を、Animグラフに置いたControlRigにわたす。
- ControlRigは受け取ったターゲットのロケーションに砲台を向ける処理をする
という感じにしようと思います。
ControlRigで変数を作りターゲットのコントローラーに渡す
ControlRigのMy BlueprintタブでVector型の変数を作ります。右にある目玉マークをクリックして目玉が開いた状態にしておきます。この状態でアニメーションBPに配置したノードから変数をPinにして外に出すことができます。

作った変数をRigグラフ内にドラッグしてGetノードを出します。Getノードを、ターゲットのコントローラーのSetTransform ノードを出して、Transrationにセットします。(とくにSetTransformを仲介せずに直接Aimのターゲットに変数を接続しても問題はないですが、1段階挟むことで別の処理を試したくなったときに繋ぎ変えが楽になったりします)
このとき、ターゲットのロケーションの変数と、Transrationを直接つなぐとSpaceの違いで不都合が生じます。間に、座標系変換ノードのFromWorldノードをはさみます。名前と見た目そのままのノードでWorld座標からGlobal座標へ変換してくれます。(逆のToWorldというノードもあります)
この接続をしたあとは、ControlRig内ではターゲットのコントローラーを動かすことができなくなります。デフォルトでは0, 0, 0 の位置を指し示すので変数の初期値に適当な値を入れて離れたところに置いておくのも良いでしょう。(結局あとから受け取ったプレイヤーのロケーションで上書きされます)

ControlRigのSpaceの違いについて
Controlリググラフでは、3つの座標系を区別する必要があります。「World」「Local」「Global」です。WorldはUEエディタのレベル上の座標のことです。Localは親を基準としたコントロールやボーン自身が持つ座標系になります。GlobalはControlRig内の独立した空間のことです。Rigスペースと呼ばれることもあります。ControlRigをエディット中のプレビュー画面で見ている空間がGlobalスペースです。このGlobalスペースはWorldとは異なるので、Worldの「何か」を扱う場合は座標を変換するノードが必要です。
今回のターゲットはレベル上を自由に歩き回るプレイヤーにする想定なので、World座標で受け取ってからGlobal座標に存在しているターゲットのコントローラーに渡す流れにしました。
BPインターフェイスを作る
ターゲットの値を受け渡すためにBPインターフェイスを作ります。コンテンツドロワの何もないところを右クリックして出てくるメニューから、Blueprint > Blueprint Interface を選択します。作られたBlueprint Interface をダブルクリックで開くと右側に関数を作る部分があるので、すでにある関数の名前を変えるかAddをクリックして関数を作ります。

今回のは、ISetTargetLocation としました。ディテールタブの中のInputにVector型の変数を作って、ターゲットのロケーションを渡せるようにします。

ターゲットの値を受け取るアニメーションBPで、BPインターフェイスを設定します。Class Setingsをクリックして、ディテールの中にあるImplemented Interfaces のAdd から作成したBPインターフェイスを選択します。これで、ターゲットロケーションの値を受け取る準備ができました。

レベルから砲台のアニメーションBPに通知する
とりあえず砲台のBPをレベルのお好きな場所に配置します。ちょっと高いところに置いてみました。

レベル上の砲台をターゲットに向かせるためにレベルBPで砲台の参照をとります。参照ノードは配置したアクター(砲台)を選択した状態で、レベルBPのグラフでなにもないところを右クリックして出てくるメニューから、Create a Reference To ~~ を選択します。

砲台の参照ノードからAnim Instance(アニメーションBP)をゲットして変数に入れています。あとは、Tickの流れでプレイヤーのロケーションを取得して、すでに作成済みのベクター型の変数を受け取るISetTargetLocationというBPインターフェイスの関数を呼び出します。作成していたTargetLocationのピンにプレイヤーのロケーションを渡します。

アニメーションBPでControlRigノードを配置し変数を外に出す
まずアニメーションBPのイベントグラフでレベルBPからのインターフェイス通知をイベントで受け取ります。Outputが無い関数だったので呼び出し先のアニメーションBPではイベントとして配置できます。
そして、送られてきたTargetLocationの値を受け取るためのVector型の変数をアニメーションBPで作り、BPインターフェイスからのTargetLocationを作った変数にSetします。

Animグラフにうつり、ControlRigノードを配置します。デフォルトスロットはなくても構いませんが、あとでアニメーションモンタージュなどが使えるので挟んでおくのが良いと思います。あと、何か適当なアニメーションシーケンスがないと警告が出るので、何も動いていない1秒程度のスケルタルメッシュアニメーションを作って配置しました。

配置したコントロールリグに、エディットしたControlRigクラスを設定し、TargetLocation用の変数のUsePinにチェックを入れてピンが見えるように設定します。先ほど作ったアニメーションBPのTargetLocationの変数をGetで配置してUsePinで見えるようになったピンにつなぎます。上の図はその一連の設定をしたあとのモノです。

これでPlayボタンを押して実行してみると、レベルに配置した砲台がプレイヤーを追いかけるはずです。
おわり
Aimの処理はFindLookAtや、アニメーションBPのLookAtノードが定番だと思いますが、ControlRig内にもAimノードがあるのでせっかくなのでやってみました。
スケルタルメッシュになっている砲台のサンプル的なものが見つからなかったので自分で作りました。テクスチャを貼っていませんがUV展開はしてあります。シンプルすぎるモデルで恐縮ですがControlRigに興味をもってもらえるきっかけになれば幸いです。
今回使用したUEのプロジェクトをアップしました。
UE5プロジェクトダウンロード