リファクタリングによるPrefabの影響範囲を可視化するUnityエディタ拡張をつくってみた

サイバーエージェントのゲーム・エンターテイメント事業部(SGE)に所属する子会社QualiArtsのゲームタイトル運用プロジェクトでUnityエンジニアをしている中辻です。
本記事はQualiArtsの定期ブログ「QualiArts Tech Note」第3弾の記事となります。QualiArtsでは会社で使われている様々な技術の知見をブログで紹介しています。興味のある方は、QualiArtsとタグの付いている他の記事もチェックしてみてください。
QualiArts Tech Note
運用に入っているUnityプロジェクトだと怖いのが既存のクラスの変更によるエンバグです。これをどれだけ防げるかでクライアント起因のバグの量はかなり違ってくると思います。プログラム的には関数の参照やクラスの継承関係を調べればエディタで検索すればどうにかなります。
ただしUnityの場合はPrefabの問題があります。クラスに変更を加えたとして、どこのPrefabで使っているか検索する術がありません。運用が長いとプロジェクトも大きくなり、作った人もプロジェクトからいなくなったりするのでどんどん把握が困難になり既存で汎用でよく使うクラスに変更を加えることは困難になります。
それを解決するためにEditor拡張で特定のクラスがどのPrefabで使われているかを検索するツールを作りました。状況を絞れるようにするためPrefab内のクラスのフィールドがどのような値になっているかも検索できるようにしています。
▼作ったツールのスクリーンショット
大まかな話の流れは以下の通りです。
- アセンブリ内のクラスをリスト化
- Editor拡張UI部分でクラス名やフィールドの条件を入力
- mdfindによって対象のprefabを絞って候補をリスト化
- Prefabをロードして条件チェック
- ヒットしたものをリストで表示
1:アセンブリ内のクラスをリスト化する
GameObjectにアタッチできるクラスをprefabから検索するにはまず検索できる対象のクラス一覧(System.Type)をリスト化してもっておく必要があります。
Project内から見えるアセンブリの一覧は以下で取得できます。
var asmList = System.AppDomain.CurrentDomain.GetAssemblies ();
そしてそれぞれのアセンブリに対して以下でクラス(System.Type)の一覧が取得できます。
var classList = asm.GetTypes ();
そしてprefab内にシリアライズされるクラスは全てComponentをベースクラスとして持つので、以下の条件に当てはまるものに絞ります。
type.IsSubclassOf (typeof(Component))
注釈:今回は話さ ないですがシリアライズオブジェクトも検索できるツールを作る場合はScriptableObjectをベースクラスに持つものを取っておくとよいです。
これで検索対象になりうるクラスの一覧は取得できました。
また3:の項目で必要になるUnityEngineのアセンブリのクラスも把握しておく必要があるので以下で取っておきます。
var defaultAsm = Assembly.GetAssembly (typeof(Component));
2:Editor拡張UI部分でクラス名やフィールドの条件を入力
泥臭くUnityエディタ拡張でUIを書くところなのでほとんど割愛しま すが、シリアライズされた変数の条件を選ぶために変数一覧が必要になるのでその取得方法だけ説明しておきます。変数の情報はFieldInfoというクラスとして扱えます。FieldInfoは対象のクラスに対して以下で取得できます。
FieldInfo[] fs = type.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
この中にはシリアライズできないものも含まれているので、FieldInfoに対して以下のいずれかの条件に当てはまるものだけに絞ります。
- プリミティブなデータ型や特定のUnityビルトイン型(boolean,float,string,Vector3,etc..)であること
- System.Enumをベースクラスとすること(作ったenum系)
targetField.FieldType.IsArray == false && targetField.FieldType.IsGenericType == false && targetField.FieldType.IsClass() (シリアライズとして持てるクラス)
注:Array型や構造体は検索条件として指定する状況が少ないと考えたので、対応しませんでした。