げぇむぷろぐらみんぐ

日々の生活で得た知識、経験を書きます

【Unity】任意のコンポーネントがアタッチされたPrefab一覧を探す

はじめに

今回は、Unityで任意のコンポーネントがアタッチされたPrefab一覧を取得する方法についてまとめます。

Unity標準の機能だと、シーン上で任意のコンポーネントがアタッチされたオブジェクト一覧を探すことはできますが、Project全体から探すことはできないようでした。

そのため今回は、シェルスクリプトを用いて探す方法と、Unity上でエディタ拡張をすることによって探す方法を説明します。

今回の環境は、

です。

シェルスクリプトによる方法

探し方としては、以下のようになります。

  1. アタッチされていることを調べたいスクリプトのGUIDを調べる
  2. プロジェクト内のPrefab一覧を調べる
  3. Prefab一覧のmetaファイルで、1で調べたGUIDを持っているものがないか調べる

Unityで管理されているものにはmetaファイルが存在し、その中でGUIDというユニークなIDを持っているため、それを利用して探したいものを特定することができます。

具体的なスクリプトは以下です。

#!/bin/sh

GUID=`find ./Assets -type f -name $1.meta | xargs grep guid | sed -e 's/guid: //g'`
find ./Assets -type f -name "*.prefab" -print0 | xargs -0 grep -l $GUID

引数に渡されたファイル名のmetaファイルを探して、その中からguidが書かれている行を選択し、余計な文字列を除去してGUIDのみを取得して変数に格納します。 その後、格納したGUIDを持つPrefabを検索し、その名前を表示しています。 findとxargsにオプションがついているのは、以下の記事を参考に区切り文字をnull文字にしているからです。

qiita.com

先程のシェルスクリプトをreference.shという名前で保存して、実際に実行してみた結果が以下です。

% sh reference.sh Test.cs
./Assets/AttachedPrefab.prefab
./Assets/SubAttachedPrefab.prefab

Test.csがアタッチされているPrefabを探して、その結果としてAttachedPrefab.prefabとSubAttachedPrefab.prefabが返されました。

エディタ拡張による方法

次に、Unityエディタ上で任意のコンポーネントがアタッチされたPrefabを探す方法について説明します。

以下が指定されたコンポーネントがアタッチされているPrefab一覧を探すためのエディタ拡張のコードです。

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace Sample.Editor
{
    public class ReferenceFinder : EditorWindow
    {
        private MonoScript _targetScript;
        private List<GameObject> _findAssets = new List<GameObject>();
        private Vector2 _currentScrollPosition;

        [MenuItem("Sample/ReferenceFinder")]
        private static void Open()
        {
            GetWindow<ReferenceFinder>("Reference Finder");
        }

        private void OnGUI()
        {
            EditorGUILayout.BeginHorizontal();
            _targetScript =
                EditorGUILayout.ObjectField("TargetScript", _targetScript, typeof(MonoScript), false) as MonoScript;
            EditorGUILayout.EndHorizontal();

            if (_targetScript == null || !_targetScript.GetClass().IsSubclassOf(typeof(MonoBehaviour)))
            {
                EditorGUILayout.LabelField("MonoBehaviourを継承したクラスを設定してください");
                _targetScript = null;
                return;
            }

            if (_findAssets.Count > 0)
            {
                _currentScrollPosition = EditorGUILayout.BeginScrollView(_currentScrollPosition);
                foreach (var asset in _findAssets)
                {
                    EditorGUILayout.ObjectField(asset.name, asset, typeof(GameObject), false);
                }

                EditorGUILayout.EndScrollView();
            }

            if (GUILayout.Button("Search"))
            {
                _findAssets.Clear();

                var guids = AssetDatabase.FindAssets("t:GameObject", null);


                foreach (var guid in guids)
                {
                    string path = AssetDatabase.GUIDToAssetPath(guid);
                    var loadAsset = AssetDatabase.LoadAssetAtPath<GameObject>(path);

                    if (loadAsset.GetComponent(_targetScript.GetClass()) != null)
                    {
                        _findAssets.Add(loadAsset);
                    }
                }
            }
        }
    }
}

やっていることは、

  1. 探したいコンポーネントをMonoScriptとして受け取る
  2. Searchボタンが押されたらプロジェクト内のGameObjectのGUID一覧を取得
  3. GUIDからオブジェクトをロード
  4. オブジェクトに対して任意のScriptをGetComponentして取得できるかをチェック
  5. 取得できたらリストに追加

です。

今回は、Prefabにアタッチできるスクリプトのみを指定できるようにしたかったため、MonoBehaviourを継承していないスクリプトは選択しても検索できないようにしました。

上記の拡張の結果以下のようなウィンドウで検索することができるようになりました。

f:id:siguma_sig:20181014155641p:plain

まとめ

今回は、プロジェクト内で任意のスクリプトがアタッチされたPrefabの一覧を取得する方法について説明しました。

今回の例では、Prefabの一覧のみを取得しましたが、任意のスクリプトがアタッチされたオブジェクトをシーン上に含むシーンの一覧なども今回のスクリプトを少し改造すれば作れるはずです。

なにか間違いや、もっと良い書き方があればぜひ教えてください!