ボトムアップ型のモーションコントローラーを作った
1. 概要
Live2DモデルをUnity上で動かすための自作コントローラーを作った。
最大の特徴は、単一のシステムが全体を統括するトップダウン型の管理ではなく、個々のモーションデータの側に再生条件やパラメータ変動などのロジックを個別に持たせる「ボトムアップ型」の設計を採用している点。
エディタ側ではノードベースでロジックを組み立てられるようになっており、数値やフラグの管理、条件分岐、反復処理などを視覚的に組むことができる。
▼Unityエディタ拡張で作成したノードベースエディタ

また、アニメーションカーブのデータもUnityエディタ上で作成できる仕組みにしており、後述するランダマイズ処理にも対応している。
▼Unityエディタ拡張で作成したアニメーションカーブエディタ

▼作成したアニメーションカーブの情報を受け取るノード。後述する「汎用キー」とアニメーションカーブを紐づけている。

- 🔞使用例(リンク先R18注意)
- https://www.pixiv.net/artworks/144581643
2. なぜ作ったのか(なぜ必要だったのか)
Live2DモデルをUnityで動かす際、既存の手法では「動的なロジックの構築」と「時系列の細かな微調整」を両立させるのが難しく、複雑な制御をする上で限界を感じていたため。
具体的には以下の2つの問題に直面していた。
-
① Mecanim(Animation Controller)の限界
レイヤーを大量に重ねると挙動が不安定になりやすい。
同じパラメータを操作していることによるキーフレームの干渉であれば原因も分かりやすいのだが、そうでない場合でも、レイヤーを一つ足しただけで途端におかしくなることが度々あった(当然、Write Defaults設定をオフにしていても発生する)。
細かい発生条件もよくわからないため、「とにかくレイヤーを多用しない」という消極的な選択肢しかとれず、結果として表現の幅を狭めてしまっていた。
-
② C#スクリプト(LateUpdate等)による直接制御の限界
プログラムから直接パラメータの値を操作する方法もある。
しかし、この方法にも限界はある。Live2Dの場合、「汗」のパーツをゆっくり、少しずつ垂らすような単純な処理ならやりやすいが、以下のような複雑な要件になると途端にハードルが上がる。
- 「腰の動き」パラメータが一定値以下なら「浮かせる」モーション(大小2パターン)を再生する。
- 逆に一定値以上の場合は「カクっと下げる」モーションを再生する。
- 毎回全く同じ動きにならないよう、再生のたびにパラメータの変動幅やタイミングを変化させる。
- モーションに合わせて、フォロースルーやオーバーシュートの役割を担う二次的なパラメータを少し遅らせて連動させる。
「仕様」が複雑でも、事前に計画していた通りに作るだけなら不可能ではないのだが、C#スクリプトで書いてしまうと「調整」が極めて難しい。
理由は大きく分けて2つある。
- コード上のどこをいじるとどうなるかが予測しづらい:
- どの処理がどのパラメータを操作しているのか追いづらく、「コードの数値をいじったら結果的にモーションがどうなるか」が予測しにくい。
- 時系列データを扱う難しさ:
- カーブの加減速(イージング)やタイミングをC#で直接書こうとすると、経過時間の管理や補間処理などでコードがやたらと肥大化しがちになる。
- 加えて、「このタイミングからカーブの角度を急にしたい」といった視覚的な微調整をしたい場合にも、いちいち計算式や数値を書き換える必要があり、柔軟性がない。
まとめ
以上をまとめると、既存の手法には以下の課題があった。
- 動的な条件分岐を噛ませつつ、複数の箇所(パラメータ)を時系列で「細かく」動かす実用的な方法がない。
- あるロジックに従ってモーションを再生する要件は実装できても、その後の微調整が難しすぎる。
これらを解決し、ロジックの構築と直感的なモーション調整を両立させるために、今回のコントローラーが必要だったという経緯になる。
3. 実際に作ったもの
3-1. ノードベースエディタ
Unityのエディタ拡張を用いて、ノードベースでロジックを組める仕組みを構築した。
順次実行・並列実行・条件分岐(IF)・反復処理(While)などを視覚的に組み立てることができ、作成したノードツリーはScriptable Objectとして保存できる。
かなり簡易的なものではあるが、ビジュアルスクリプティング環境として機能している。
▼実際に組んだノードの例。順次実行(SequenceTask)に加え、「Live2Dパラメータの現在の値」による分岐や、コンテキスト変数の値による分岐ノードなどを配置している。

この「ノードツリー」アセットは、ロジック本体だけでなく「コンテキスト変数」と「汎用キー」という2つの情報を保持している。
- コンテキスト変数:
- ツリーの実行を制御するための変数として機能する。ツリー内部・および外部のスクリプトからこの変数を操作することで、モーションの再生などをトリガーする。
- 汎用キー:
- ロジック内で扱うパラメータの「仮の名前(ただの文字列)」として扱われる。
▼コンテキスト変数の例

▼汎用キーの例

そして、この「汎用キー(仮の名前)」と「実際のLive2Dパラメータ」を紐づける役割として、もう一つのコア要素となる「マッピング」アセットを用意した。このアセットが実行時(MonoBehaviour側)に直接参照される。
マッピングアセットは、コンテキスト変数の初期値を持たせることや、モーションの優先度を設定することもできる。
▼「マッピング」アセットの例。「Parameter Mapping」内で汎用キーとLive2Dパラメータを紐づけている。

なぜわざわざ「ノードツリー」と「マッピング」を分けたのかというと、モーションデータの抽象化(流用)をしたかったというのが主な理由として挙げられる。
例えば、「左足の汗」と「右足の汗」という別々のパラメータがあったとする。これらが「似たような速度で動く」という同じロジックで動くのであれば、マッピングの参照先を変えるだけで同じノードツリーを使い回せる。わざわざノードツリーごと新規作成(コピペ)すると保守性が下がる。
(ノードツリー上では、汎用キーは単なる文字列として扱われているため、将来的にはLive2Dのパラメータ以外ともマッピングできる拡張性を持たせている。この点については後述する。)
ただ、この設計のトレードオフとして、特定のモデル専用・特定のパラメータ専用の「一点もの」のモーションを作る場合であっても、必ず「ノードツリー」と「マッピング」の両方のアセットを作らなければならないため、一長一短な部分はあった。
3-2. アニメーションカーブ
時系列データを直感的に扱うため、「アニメーションカーブ」アセットもUnityエディタ上で作成できるようにした。
▼「アニメーションカーブ」作成画面

また、このエディタではモーションの「ランダマイズ」機能を実装している。
全く同じ動きを繰り返すループモーションは、見ていると目が慣れてしまい機械的な印象を与えてしまう(個人的な見解)。これを避けるため、同じモーションでも再生のたびにパラメータの変動量やモーションの長さを若干変える、という手法をよく用いている。
これをランタイムでも実現したかった(というよりも、する必要があった)。つまり、プレイヤーの入力によってゲーム内でモーションが再生される際、「毎回少しずつ違うモーション」として再生したいという要件があった。この仕組みを実装するため、キーフレームの概念を若干拡張している。
Live2Dエディタをはじめとする一般的なアニメーションソフトのキーフレームは「時間も数値も固定的」となっているが、今回のシステムでは「前のキーフレームからの経過時間と差異」を基準にした、浮動的なキーフレーム(値に幅を持たせたキーフレーム)を設定できるようにしている。
▼「ランダマイズ」に対応しているアニメーションカーブの例。ランダマイズ係数が0の時は基準となる曲線を、係数が1の時は一番離れている曲線を辿る。

3-3. その他:検索窓フィールドによる複雑性の吸収
システムの大枠とは直接関係ないが、実装して非常に開発体験が良かった機能として「検索窓フィールド」が挙げられる。
▼「検索窓フィールド」の例。ここでは「対象となるLive2Dモデルが持つ全てのパラメータID」だけが入力可能な候補となる。

▼実際に文字を打ち込んでいくと候補が絞られていく。

UI上は単なる文字列の指定に見えるが、内部でシリアライズできる機能として作っている。
ノードツリー内の変数や「汎用キー」、あるいは「プロジェクト内に含まれるLive2Dモデルの一覧」や「そのモデルに含まれるパラメータの一覧」を候補としてリストアップし、インクリメンタルサーチで絞り込んで指定できる。
今回のシステムは、オブジェクト指向的に設計したことで柔軟性が高まった反面、扱うパーツ(特に汎用キーなど)が増えて複雑性が増してしまった部分もあった。さらに言えば、そもそもLive2Dモデル自体もパラメータが膨大になりやすい。
この検索窓フィールドがあるおかげで、パラメータ名を一々暗記する必要がなくなり、タイポによるバグも完全に防げるようになったため、システムの複雑さを上手く吸収してくれたと思う。
4. 問題点・課題が多い所
ノードベースエディタには課題が多く残っている。
- そもそも「ロジックをデータ化して視覚的に組み立てる」というのは、簡易的とはいえプログラミング特有の複雑さをエディタ上に持ち込んでいる状態だとも言える。そのため、少し踏み込んだことをしようとすると、様々な問題に直面することになった。
- 例えば、「別のノードツリーを呼び出して処理をさせる(プログラミングでいうメソッド化)」という機能を実装してみたものの、変数の受け渡し(コンテキストの解決)周りが上手く動作していない。
- また、特定のノードを組み合わせるとツリー全体の挙動が破綻する現象も起きたが、その原因を特定することが非常に困難だった。
- こうした問題の背景にある最大の課題は、現在のシステムの「デバッグ機能」が貧弱すぎることにある。
- 抽象度の高いコードを書くと処理の流れを追うのが難しくなり、結果的に自動テストなどの仕組みが不可欠になっていく……という、ソフトウェア開発ではよくある問題にそのままぶつかっている状態と言える。まずはこのデバッグ周りから改善していく必要があると感じている。
5. 今後の展望
- ロジックとデバッグ機能の強化
- 前述の通り、(開発を続けるのであれば)まずはバグの特定と修正を容易にするための仕組みを整えたい。
- データの扱いの強化とフローの効率化
- ノードのテンプレート機能を追加したり、Live2Dのmotion3.jsonファイルをそのまま受け取れるようにしたりするなど。
- Live2Dモデルに関する応用
- プレスコ(音声を先行収録する手法)で制作された音声の受け皿として。口パクの話ではなく、音声ファイルを起点として、ループモーション全体をある程度動的に生成するといったアプローチを考えている。 今回、キーフレームの概念を拡張したことで、ランタイムでも動きを柔軟にコントロールできる仕組みが整った。これを利用すれば、音声をトリガーとして動きに揺らぎを持たせたり、他のパーツを連動させたりと、より自然なループモーションをリアルタイムに生成できる見込みがある。
- また、この仕組みの大きな利点は、音声ファイルの長さ(例えば30秒間)に合わせて固定的なキーフレームが延々と打ち込まれているわけではないという点にある。モーションの調整が必要になった際も修正箇所は限定的になるので、データとしての保守性もかなり高くなる(と思う)。
- Live2D以外への応用
- UIアニメーション
- チュートリアル
- (完全に思いつきで書いているが)SE〇IRO風のアクションゲームのようなものにも使えるかもしれない。今回のシステムは「モーションデータ」と「ロジック」が非常に近い位置にあるため、「特定のアクションから別の技へ派生させる」「攻撃モーションが途中でキャンセルされたら、専用の怯みモーションに繋ぐ」といった複雑な処理がやりやすい……ような気がしている(本当に機能するかは未知数)。
- 現在のシステムはLive2Dのパラメータ(単一の数値)を操作する前提の作りになっている。ここを拡張し、3Dモデルのボーン(座標や回転といった多次元のデータ)に紐づける仕組みや、それに対応したアニメーションカーブのデータ構造さえ用意できれば、理論上は3Dモデルの制御にも流用できるのではないかと考えている。
- *余談だがUEのAnimBPなるものの存在をこの記事の執筆中に知った。3Dモデルの制御はわざわざ自作しなくてもいいかもしれない
6. 最後に
-
「アニメーションを作ること」と「それをプログラム上で再生・制御すること」の間には、相対的に未開拓の領域が広がっているらしい(開発中に使用していたAIがそう言っていた。真偽のほどは不明)。これがいわゆるブルーオーシャンなのか、単純に「魚がいない(=誰もそんなことを気にしていない・いたとしてもかなり少数)」だけなのかは分からないが、おそらく後者に近いのではないかと思っている。 とはいえ、個人的には面白い分野ではあった。高く評価してくれる人もいたので、今後も開発は続ける予定ではいる。
-
今回はAI駆動開発(厳密にはAIにコードを書かせて人間が確認する「レビュー駆動開発」)の手法を取り入れたが、なんだかんだで1ヶ月強もかかってしまった。しかし、システムの規模や「実際にある程度動く」という水準まで持っていけたことを考えると、少し早すぎるペースだと感じる。もしこれを人力で一からやっていたら、Unityのエディタ拡張の文法を調べるところから始まり、半年から1年、あるいはそれ以上の時間を要していてもおかしくはない。
-
余談になるが、今回の開発の根底には「プレイヤーがそのように操作するから 、そういう反応が返ってくる」という、「プレイできるアニメ」としての水準を達成したいという目標があった。「頭をなでるから、鬱陶しがられる」といったような、インタラクションにおける説得力を持たせるための試みでもあったが、今回のシステムだけでそれを十分に表現しきれたとは言えない。結局のところ演出面も大きく関わってくる部分なので、技術的なシステムだけで解決するのはなかなか難しいというのが率直な印象として残った。