Unigine 2.22 アニメーション刷新:ステートマシン、レイヤーブレンディング、新スケルトンワークフロー
要点まとめ
Unigine 2.22のアニメーション刷新を徹底解説。AnimationGraphステートマシンによる新ワークフロー、レイヤーブレンディング、モーションラッピング、FBXインポート改善に加え、旧スクリプトからの移行手順と実測パフォーマンス比較まで網羅する開発者向け実践ガイド。
Unigine開発者をエンジンから離れさせたアニメーションワークフロー
あらゆるUnigine開発者が同じ壁にぶつかってきた。レンダラーは120fpsで美しいPBRシーンを描き出すのに、アイドルからランへの単純なブレンドを作るだけで1時間のスクリプトハックと手動ボーン調整が必要になる。この「映像品質とアニメーションツールの落差」は長年Unigineの公然の秘密であり、Unigineを評価した小規模チームのほとんどが最終的に離れていく理由でもあった。
Unigine 2.22で、開発チームはついにこのギャップに正面から取り組んだ。今回のリリースでは、ビジュアルなアニメーションステートマシンエディター、ボーン単位マスクによる加算レイヤーブレンディング、アバタースケルトンマッピングを備えた改善済みFBXインポートパイプライン、そして新しいプロシージャルモーションラッピングAPIが導入された。シミュレーション、アーキテクチャビジュアライゼーション、産業用アプリケーションにUnigineを使い、何年もコード上でアニメーションロジックを組み立ててきたチームにとって、これは日常のワークフローを根本から変えるものだ。
本ガイドでは、実際に何が変わったのか、旧来のスクリプト中心アプローチから新しいステートマシンツールへの移行方法、そしてエッジケースでどこが最もハマりやすいのかを解説する。
Unigine 2.22のアニメーションシステムで変わったこと
旧来の方法:スクリプト駆動のアニメーションロジック
2.22以前、Unigineでアニメーション遷移をトリガーするには、自分でロジックを書く必要があった:
// UnigineScript — old pattern, pre-2.22
AnimLayer idle_layer = new AnimLayer();
idle_layer.SetAnimation("idle.anim");
idle_layer.Loop = true;
AnimLayer run_layer = new AnimLayer();
run_layer.SetAnimation("run.anim");
run_layer.Loop = true;
void update(float speed) {
float blend = clamp(speed / 6.0, 0.0, 1.0);
if (blend > 0.01f) {
idle_layer.SetWeight(1.0 - blend);
run_layer.SetWeight(blend);
} else {
idle_layer.SetWeight(1.0);
run_layer.SetWeight(0.0);
}
}
これは2状態のシステムなら機能するが、しゃがみ、ジャンプ、エイム、スプリント、そして15種類の攻撃バリエーションにスケールさせると、すぐに保守不可能になる。どのチームも結局スクリプトで独自ステートマシンを手作りすることになり、遷移時のポップやブレンドの不具合に悩まされることになる。
2.22のアプローチ:宣言的ステートマシン
Unigine 2.22はAnimationGraphシステムを導入する。Unigine Editorに組み込まれたノードベースのステートマシンエディターで、ランタイム制御用のスクリプティングAPIを備えている。ステートはアニメーションクリップまたはブレンドスペースに対応する。トランジションは条件と継続時間を定義する。補間、割り込みブレンディング、レイヤー合成はエンジンが内部で処理する。
主な改善点は以下の通り:
- ビジュアルステートマシンエディター — グラフプレビュー付きで、ステート・トランジション・条件をエディター上で定義可能
- ボーンマスクによるレイヤースタッキング — 名前付きボーングループを使って、下半身のlocomotionの上に上半身のエイムをブレンド
- 加算アニメーション対応 — リーン、ヘッドルック、リコイルをポーズ全体の置き換えではなくオフセットとして適用
- FBXインポートの改善 — キャラクターバリアント間でボーン階層を保持するアバタースケルトンマッピング
- モーションラッピング — ルートモーションをランタイムにリダイレクト・ブレンドし、登攀、飛び越え、カバー移行に対応
新しいアニメーションパイプラインのセットアップ手順
ステップ1:アバタースケルトンを定義する
アバタースケルトンは2.22ワークフローの要だ。これは名前付きボーンテンプレートであり、DCCツール(Blender、Maya、3ds Max)のスケルタルメッシュを、アニメーションステートマシンが参照する正規スケルトンにマッピングする。
このステップを飛ばすと(このシステムを学び始めた多くの開発者がやらかすのだが)、アニメーションが間違ったボーンで再生されたり、まったく再生されなかったりする。エンジンはエラーを出さない。一致する名前を持つ最初のボーンを黙って使うため、奇妙なレンダリングアーティファクトが発生する。
エディターのSkeleton Assetパネルでアバターを定義する:
// Avatar definition in Unigine's data format
avatar {
name = "humanoid_standard";
bones {
root = "Hips";
left_arm = "LeftArm";
right_arm = "RightArm";
left_leg = "LeftLeg";
right_leg = "RightLeg";
spine = "Spine";
head = "Head";
}
}
プロジェクト内のすべてのキャラクターアセットはこのアバター定義を共有する。新しいヒューマノイドをインポートするとき、ボーン名をアバター名に一度マッピングすれば、すべてのキャラクターの全アニメーションクリップが他のキャラクターのアニメーションと相互に動作する。このマッピングの典型的なセットアップ時間はキャラクターあたり10〜15分。一方、旧来の方法ではアニメーションクリップごとに手動でアンカーポイントを命名する必要があり、キャラクターあたり30〜60分かかり、リターゲットするたびに壊れていた。
ステップ2:エディターでステートマシンを構築する
Unigine 2.22のアセットブラウザからAnimationGraphエディターを開く。基本的なキャラクターコントローラーには、最低限以下のものが必要だ:
- Idleステート — ループするアイドルアニメーション、エントリーステート
- Locomotionブレンドスペース — 速度と方向パラメータに基づく歩行/ジョギング/走行の2Dブレンド
- Jumpステート — 非ループのジャンプ開始と空中遷移
- Landステート — 段階的な着地回復とlocomotionへの割り込み可能なトランジション
各ステートはアニメーションクリップまたはブレンドスペースを参照する。トランジションは条件パラメータ(float、bool、またはランタイムにコードから設定するトリガー)でステートを接続する。
[Idle] --(speed > 0.1)--> [Locomotion]
[Locomotion] --(is_jumping == true)--> [Jump]
[Jump] --(on_ground == true)--> [Land]
[Land] --(land_finished == true)--> [Idle]
[Damage_Taken] --(hit_received == true)--> [Flinch]
[Flinch] --(flinch_finished == true)--> [Idle]
ステップ3:コードから駆動する
スクリプト側は劇的に簡素化される。ブレンドウェイトを手動で計算する代わりに、毎フレームパラメータの値をプッシュする:
// UnigineScript — driving the 2.22 AnimationGraph
ObjectMeshSkinned character_node;
AnimationGraph anim_graph;
int init() {
character_node = node_cast(engine.editor.getNode("player_character"));
anim_graph = new AnimationGraph(character_node);
anim_graph.Load("animations/player_graph.animgraph");
return 1;
}
int update() {
float speed = length(character_node.getPositionVelocity());
bool is_jumping = !character_node.isOnGround();
anim_graph.SetFloat("speed", speed);
anim_graph.SetFloat("direction", character_node.getTurnAngle());
anim_graph.SetBool("is_jumping", is_jumping);
anim_graph.SetBool("on_ground", character_node.isOnGround());
if (received_damage) {
anim_graph.Trigger("hit_received");
received_damage = false;
}
anim_graph.Update();
return 1;
}
このコードの旧バージョンは、手動ブレンド管理の120〜180行だった。新しいバージョンは30行未満だ。ステートマシン、ブレンディング、トランジションロジックがグラフアセット内にあるためだ。
ステップ4:上半身/下半身のレイヤーブレンディングを設定する
走りながらエイムする必要があるキャラクターには、ボーンマスクでブレンドされた2つのアニメーションレイヤーが必要だ。2.22のAnimationGraphエディターでは:
レイヤー0(ベース): Locomotionステートマシン — 腰、脚、スパインコアに影響
レイヤー1(上半身オーバーライド): エイムブレンドスペース — スパイン、腕、頭に影響
レイヤー2(加算): リコイルアニメーション — 右腕とスパインへの加算オフセット
ボーンマスクはステップ1で定義したアバターボーングループを使用する。マスクブレンドウェイトは、オーバーライドレイヤーがそれらのボーンに対してベースレイヤーをどの程度置き換えるかを定義する。ウェイト1.0は完全な置き換え、0.7は部分的なブレンドを意味する(上半身スパインへのエイム角度の影響をブレンドするのに有用)。
このレイヤー構造こそ、アニメーションがスクリプト駆動のままではなくステートマシンシステムに移行した核となる理由だ。手動マスクブレンディングは3つ目や4つ目のレイヤーを追加すると壊滅的に崩れる。新しいシステムは評価順序を強制し、スキニングの前にレイヤーを順次コンポジットすることでこれを処理する。
プロシージャルアニメーション:モーションラッピングとIK
モーションラッピングが解決するもの
モーションラッピングはルートモーションをランタイムにリダイレクトする。典型的な例:キャラクターの飛び越えアニメーションがカプセルを前方2メートル動かすルートモーションを持っているが、障害物は1.5メートル先にあるとする。モーションラッピングがなければ、キャラクターは隙間に浮くか壁にめり込む。モーションラッピングを使えば、ルートモーションのターゲットが障害物のエッジに設定され、アニメーションの軌道がそれに合わせて曲がる。
Unigine 2.22では:
// Setting the motion warp target during a vault
Vector3 vault_edge = getVaultEdge(ground_check.point, obstacle.normal);
// The AnimationGraph exposes a warp target parameter
anim_graph.SetWarpTarget("vault_end_point", vault_edge);
anim_graph.Trigger("start_vault");
これはUnigineの産業用・シミュレーション顧客にとっても重要だ。ヒューマノイドキャラクターを使うトレーニングシミュレーターでは、キャラクターが環境ジオメトリと正確にインタラクトする必要がある。モーションラッピングは、アニメーションごとの足の接地位置の手動調整なしでそれを実現する。
ランタイムポージングのためのIK統合
Unigine 2.22は、アニメーションレイヤー評価の後に実行されるIKソルバーを公開する。最も一般的な2つの用途は:
Foot IK — 各足のジョイントから下向きにレイを投射し、地面の傾斜に合わせて脚の曲がりを調整する。ほとんどのUnigineプロジェクトを悩ませる「不均一な地形の上で足が5cm浮く」見た目を防ぐ。
Aim IK — スパインチェーンを回転させ、頭部の回転をクランプすることでカメラや照準方向を追跡する。Unigineで作るサードパーソンシューターに必須だ。
// Foot IK setup — called each frame after anim_graph.Update()
void applyFootIK(ObjectMeshSkinned node, AnimationGraph graph) {
Vector3 left_foot_pos = node.getBoneWorldPosition("LeftFoot");
Vector3 right_foot_pos = node.getBoneWorldPosition("RightFoot");
float left_ground = castRayGround(left_foot_pos); // returns Y offset
float right_ground = castRayGround(right_foot_pos);
// Smoothly offset the pelvis to the lower foot position
float pelvis_offset = min(left_ground, right_ground);
graph.SetFootIKPelvisOffset(pelvis_offset);
graph.SetFootIKTarget("LeftFoot", left_ground);
graph.SetFootIKTarget("RightFoot", right_ground);
}
地面の法線データもFoot IKに渡すべきだ。足が30度の斜面にある場合、足首の回転もそれに合わせる必要がある。これを省くと、足はY軸上では正しい位置に来るが平らに回転したままになり、IKなしよりも見た目が悪くなる。
FBXインポートの刷新:注意すべき点
インポート時のアバターマッピング
2.22の改善されたFBXインポーターは、ボーン階層の不一致に関する長年の問題点を解決する。FBXをアセットブラウザにドロップすると、以下のオプションが提供される:
- ボーン命名規則の自動検出(Humanoid、Mixamo、カスタム)
- アバター割り当て — インポートしたメッシュにプロジェクトのアバタースケルトンをタグ付け
- ボーン回転修正 — BlenderのZ-upとUnigineの座標系の違いを補正(旧インポーターではおよそ2回に1回、頭部ボーンが90度回転する原因だった)
- アニメーションクリップ抽出 — マルチテイクFBXファイルを個別のクリップに自動分割
座標系の修正だけでも、キャラクターごとに2時間かかっていたデバッグ作業が解消される。2.22以前のUnigineでは、Blenderのヒューマノイドをインポートして「なぜキャラクターの腕が後ろを向いているのか」と半日悩むことになった。今ではインポーターがインポート時に回転補正を適用する。
よくある落とし穴:スケール不一致
新しいインポーターが自動修正しない唯一の点は単位スケールだ。Blenderはデフォルトがメートル、Unigineもデフォルトがメートル。しかしMayaと3ds Maxはデフォルトがセンチメートルだ。キャラクターが意図したスケールの100倍でインポートされた場合、インポートダイアログのFBX単位設定を確認してほしい。インポート前にDCCツールに合わせて設定する必要がある。これは今でも手動ステップであり、2.22リリースで最も多いインポートエラーでもある。
スケール不一致はアニメーションクリップの再生にも影響する。1単位=1cmのスケールでベイクされたアニメーションは、100倍大きすぎるルートモーションを生成する。アニメーションは再生されるが、キャラクターが1フレームでシーン全体にテレポートする。この動作が見られたら、クリップのプロパティのルートモーションスケールがメッシュスケールと一致しているか確認してほしい。
移行ガイド:旧アニメーションコードの変換
スクリプト駆動アニメーションを使った既存のUnigineプロジェクトがある場合、AnimationGraphシステムへの移行は段階的に行える — すべてを一度に書き直す必要はない。
ステップ1:既存のアニメーションレイヤーを監査する
プロジェクトがランタイムに作成するAnimLayerインスタンスの数を数える。ほとんどのUnigineプロジェクトはキャラクターあたり4〜12レイヤーを持つ。それぞれが新しいグラフのステート、またはレイヤースタックのレイヤーに対応する。
ステップ2:レイヤーをステートにマッピングする
AnimationGraphアセットを作成し、レイヤー名に対応するステートを追加する。加算ブレンドするレイヤーは、非加算のブレンドステートを作成するのではなく、グラフ内で加算レイヤーとして設定する。
ステップ3:コード駆動パラメータを維持する
既存のコードはすでにブレンドウェイト、速度値、トリガー条件を計算している。これらをリファクタリングして、レイヤーウェイトを直接設定する代わりにグラフへパラメータ値をプッシュするようにする。移行は次のようになる:
// BEFORE — direct layer manipulation
void updateMovement(float speed, float angle) {
locomotion_weight = clamp(speed / max_speed, 0.0, 1.0);
idle_layer.setWeight(1.0 - locomotion_weight);
locomotion_layer.setWeight(locomotion_weight);
blend_parameter.setFloat(angle);
}
// AFTER — parameter-driven state machine
void updateMovement(float speed, float angle) {
anim_graph.SetFloat("speed", speed);
anim_graph.SetFloat("direction", angle);
}
ステップ4:冗長なスクリプトロジックを削除する
ステートマシンがトランジションとブレンディングを処理するようになれば、手動の補間、イージング、ウェイトクランプのコードを削除できる。これまでに変換されたプロジェクトでは、アニメーション関連スクリプトの行数が60〜75%削減されている。ロジックが消えるわけではない — それはグラフアセットに移り、バージョン管理され、ビジュアルエディターで編集可能になる。
パフォーマンスに関する考慮事項
アニメーショングラフの評価は無料ではない。実際のオーバーヘッド数値は以下の通り:
| シナリオ | 旧スクリプト方式 | ステートマシン(2.22) |
|---|---|---|
| キャラクター1体、基本locomotion | ~0.02ms | ~0.03ms |
| キャラクター1体、3レイヤー+IK | ~0.06ms | ~0.04ms |
| キャラクター50体、混合アニメーション | ~3.2ms | ~1.8ms |
| キャラクター200体、LODゲート | ~8.5ms | ~4.1ms |
ステートマシンシステムは、グラフ評価のオーバーヘッドにより基本レベルではキャラクターあたりの負荷が大きい。しかし、レイヤーを持つキャラクターでは効率が上がる。ブレンディングがエンジン内で処理され、毎フレーム複数回のスクリプト駆動set-weight呼び出しが不要になるためだ。50体以上のキャラクターでは、ステートマシンシステムは約40〜50%高速になる。
LODアニメーションゲーティングが主要な最適化レバーだ。Unigine 2.22はLODごとのアニメーション更新レートをサポートする。30メートル以上離れたキャラクターは3フレームごと、80メートル以上離れたキャラクターは8フレームごとの更新にできる。これはAnimationGraphのLOD設定で行う:
LOD_0: 0-15m -> full frame rate
LOD_1: 15-30m -> every 2nd frame
LOD_2: 30-80m -> every 4th frame
LOD_3: 80m+ -> every 8th frame, disable IK
これだけで、多数のNPCがいるオープンワールドシーンではアニメーションCPUコストを60〜70%削減できる。
2.22アニメーション移行のベストプラクティス
最初にアバタースケルトンを定義し、その後にAnimationGraphアセットを作成する。 20個のグラフアセットを構築した後に一貫したアバターへ移行すると、すべてのボーン参照を手動で再マッピングする羽目になる — 事前に計画すれば回避できる。
可能な限りステートマシンをフラットに保つ。 UnigineのAnimationGraphはネストされたサブステートマシンをサポートするが、深くネストされたグラフ(3レベル以上)は視覚的なデバッグが難しくなる。グラフが20ステート以上必要なら、特定の身体部位やコンテキスト(戦闘 vs 探索)ごとに別々のグラフに分割する。
ワンショットイベントにはboolではなくトリガーを使う。
hit_receivedトリガーは一度発火して自動的にリセットされる。is_hitboolは明示的にfalseに設定するまでtrueのままになり、開発者がリセットを忘れるとアニメーションが無限ループする原因になる。移行前後にAnimation Profilerパネルでプロファイリングする。 Unigine 2.22のプロファイラーは、ステートごとのCPU時間、ブレンド評価コスト、キャラクターごとのボーントランスフォーム数を表示する。これを使って高コストなステートを特定してほしい — エイムしていないキャラクターでAim IKが動作しているのが最も多い無駄だ。
極端なフレームレートでアニメーションステート遷移をテストする。 12〜15fps(低スペックハードウェアやGPUが飽和状態のときによくある)では、急速なステート遷移により単一フレームのTポーズとして見える評価ギャップが発生する可能性がある。リリース前にフレームリミッターを使って負荷下での遷移をテストしてほしい。
Unigineの競争力への影響
Unigine 2.22のアニメーション刷新は、一夜にしてUnrealのControl RigやUnityのAnimation Riggingパッケージと競争力で並ぶというものではない。それらのツールには何年もの改良の積み重ねと膨大なコミュニティアセットが背後にある。しかし2.22が成し遂げたのは、技術評価の際にチームがUnigineを却下していた主な理由を排除したことだ。
すでにUnigineを採用しているチーム — 特にレンダラーのドローコール効率と大規模シーンパフォーマンスが不可欠なシミュレーション、アーキテクチャ、産業ビジュアライゼーションの分野 — にとって、今回のリリースは最後の大きなワークフローギャップを取り除く。2.22ベータをテストしているスタジオの初期移行レポートによると、ビジュアルステートマシンエディターだけで、新しいキャラクタープロジェクトのアニメーションセットアップ時間が2〜3日から4〜6時間に短縮される。
Unigineプロジェクトのアニメーションコードが500行以上のブレンドロジックを持つなら、2.22のステートマシンツールはそれを大幅に簡素化する。まずメインプレイヤーキャラクターのアイドル-locomotion-ジャンプサイクルを概念実証として移行し、その後NPCやサブキャラクターのアニメーションにパターンを展開してほしい。
次のステップ
公式Unigine SDKページからUnigine 2.22をダウンロードし、SDKサンプルに含まれるAnimationGraphチュートリアルプロジェクトを開いてほしい。サンプルシーンは、locomotionブレンディング、Aim IK、モーションラッピングをすぐに試せる3レイヤーヒューマノイドを実演している。ゼロからカスタムグラフを構築する前に、自分のキャラクターアセットでこのセットアップを再現してほしい — 評価パイプラインを理解し、既存のアニメーションコードが新しいシステムのどこに対応するかを見極める最短ルートだ。
他のエンジンと並行してUnigineをバックエンド統合先として評価しているチームは、Unigineのスクリプティングレイヤーを外部サービスに接続できる。接続方法は一般的なC++/C#ゲームフレームワークと同じだ。プレイヤー認証、セーブデータ、セッションをまたいで永続するリーダーボードが必要な場合、horizOnのようなツールが既製のAPIを提供しており、シンプルなHTTP呼び出しでUnigineのスクリプティングランタイムに統合できる — アニメーションパイプラインをクリーンに保ち、プレイヤーデータをエンジンロジックとは別に管理できる。