1.降ってきたタスク
仕事のUnityのプロジェクトで、デザイナーの方からキャラクターモデルを修正したので差し替えてくださいという話が来ました。
モデルの差し替えって言っても結構大変です。
本当にメッシュをちょろっと変更してるだけならエクスプローラからFBXファイルを新しいものに上書きすれば行けます。これだけで終了です。
ボーンの座標などが変わってしまってる場合はモデルを丸ごと差し替える必要があります。今回の記事で想定してるのはこのタイプのモデル差し替えです。
ボーン構造やボーンの名前まで変わってしまってる場合はもはや別モデルなので、今回の記事の方法では対処できません。諦めて手動で頑張る感じです。
2.発生した問題
すでに旧モデルの中の階層にCollider用のオブジェクトを追加したりSmoothJointの揺れ物設定やらのコンポーネント類を大量に追加してるため、全部の階層を一つ一つ確認してコンポーネントを差し替えモデルで手作業で再現していくのは大変な苦行であり、しかも今後も差し替えが何度も発生しうる。
また、コンポーネントがモデル内の別のtransformを参照していたりして、単にコンポーネントのコピー、ペーストをしても参照先は旧モデルのままだったりするのでこれもイチイチ差し替えるハメになる。
3.解決案
エディタ拡張でできるだけ自動化したい。
4.実装されたもの
Ⅰ、Utils→CopyTransformComponentsを選ぶと出てくるエディタ拡張のウインドウでヒエラルキー上の旧モデルと新モデルを指定する。実行ボタンを押すと以下のプロセスが実行される。
Ⅱ、旧モデルのtransform階層を一つずつなめていって、コンポーネントがあったら新モデルの方の同階層かつ同名のtransformにペーストする。(コンポーネントのコピペはEditorInternalの機能でスクリプトから実装できる)
ただしSkinnedMeshRenderコンポーネントについてはモデルに最初から貼り付けられてるコンポーネントであり、いじくる必要性がないためこの処理はスキップする。
Ⅲ、旧モデル側にあるけど新モデル側に無いtransformオブジェクトを発見した場合、何かの意図があって追加したオブジェクトだと判断して、旧モデル側から新モデルの方の同階層にオブジェクトをコピー、ペーストする。
Ⅳ、各コンポーネントの中でGameObjectまたはTransformを参照してる変数があったら、参照先を旧モデル側のものから新モデル側の同名transformを探して差し替える(モデルはプレハブ的な運用を想定していて、自分の下の階層だけを参照していて、シーン上の別のオブジェクトは参照してないハズという事にする)
5.実装の方法
・コンポーネントのコピペ
テラシュールブログの椿さんがこちらでオブジェクトの全コンポーネントをコピー、ペーストできるコードを書かれていたので参考に(ほぼコピペ)しました。
すでにコピペ先のオブジェクトに同じ種類のコンポーネントがあれば上書き、無ければ新規追加の処理をいい感じにやってくれます。
ComponentUtility.CopyComponentなどを使って実現しています。これを使えばエディタ上でコンポーネントコピーするのと同じ事をscriptからできるようです。
・自分の下のtransformを全階層辿って探す処理
transform.Findだと自分の一つ下の階層までしかサーチしてくれません。
全階層サーチするのはこちらのFindDeepChildを参考にしました。(ほぼコピペ)
・コンポーネントからGameObjectまたはTransform型の変数を見つけて差し替える
不特定種類のコンポーネントがTransform型変数を持ってるか調べたり、見つけたら値を差し替えたりするにはリフレクションを使うしかありません。
というわけでリフレクションで書きました。
Transform型の場合の処理、GameObjectの場合、Transform[]型、GameObject[]型などそれぞれ場合分けして処理を書くハメになって大変でした。
List<Transform>型の場合の処理も書くべきでしょうが、疲れたのでやってません。
リフレクションについて参考にした記事
6.ソース
7.課題
旧モデルと新モデルのボーン構造が変わってない事が前提条件になっています。
ボーン構造が変わってしまうと意図しないオブジェクトコピペが発生したりして正しく動作しないでしょう。
GameObjectやTransform以外にも参照先を付け替えないと行けないコンポーネントが色々ありそうですが、型ごとにイチイチリフレクションで書いて対応するのが大変なので、とりあえず手動で対応して、使ってる頻度が多いヤツは逐次処理を追記していけばいいと思います。
8.感想
エディタ拡張で自動化書くのは結構楽しいですね。リフレクションも結構便利。
汎用的に書こうとすると大変ですが、とにかく今目の前の作業を手作業したくない時にピンポイントで自動化するために使い捨て気分で書くのもアリなのでは。
ちなみにエディタ拡張は便利ですが、やたらに書きまくってると、ずっとメンテするハメになったりUnityのバージョンアップで動かなくなったりして技術的負債になる可能性があります。
便利な自動化エディタ拡張を書いてSlackで社内に自慢&共有してしまうと、あとで「動かなくなった!」とか「こういう機能も欲しい!」とか要望が殺到するのは必至です。
別に業務時間中ずっとエディタ拡張のメンテしてても大丈夫な状況ならそれもアリですが、普通に別の業務があって忙しい人は業務の合間を縫ってサビ残でメンテ対応とかになってしまうとつらみがあります。
メンテしきれないエディタ拡張は自分の手元でこっそり使うだけにしとく方がいいかもしれないケースもあるとかないとか?
じゃあブログで共有してコメントで要望が飛んできたけど対応しきれない場合はどうするのかですって?
どうしよう…