【Unity拡張】指定したスクリプトが付いているPrefabを選択するエディター拡張

# はじめに
Unityを使っていると、
「このスクリプトが付いたPrefabが置いてある場所って**どこだっけ**？」とPrefabを探すのに苦労することがあります。

そこで、指定スクリプトが付いたPrefabを探すエディター拡張を作ってみました。

# 作ったものについて
クラス名を入力してボタンをクリックするとPrefabを全選択してくれます。
![1.gif](https://qiita-image-store.s3.amazonaws.com/0/44288/528f5cba-4205-fce7-e6cd-2923c0c69a07.gif)

# 環境
Unity2017.2.0f3
Windows 10

# ソースコード
プロジェクト内にEditorフォルダを作成し、以下の二つのスクリプトを入れてください。

```TypeGetter.cs
namespace PrefabFinder
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEditor;

    /// <summary>
    /// タイプの取得を行うクラス
    /// </summary>
    public static class TypeGetter
    {
        private static Dictionary<string, List<Type>> typeDict;

        private static MonoScript[] monoScripts;

        /// <summary>
        /// プロジェクト内に存在する全スクリプトファイル
        /// </summary>
        private static MonoScript[] MonoScripts { get { return monoScripts ?? (monoScripts = Resources.FindObjectsOfTypeAll<MonoScript>().ToArray()); } }

        /// <summary>
        /// 指定した名前と同名のクラスを全て取得する
        /// </summary>
        public static Type[] GetComponentTypes(string className)
        {
            // Dictionary作成
            if (typeDict == null)
            {
                typeDict = new Dictionary<string, List<Type>>();
                foreach (var type in GetAllTypes())
                {
                    if (!type.IsSubclassOf(typeof(Component))) { continue; } // Component継承クラスかどうか判定

                    if (!typeDict.ContainsKey(type.Name))
                    {
                        typeDict.Add(type.Name, new List<Type>());
                    }
                    typeDict[type.Name].Add(type);
                }
            }

            if (typeDict.ContainsKey(className))
            {
                return typeDict[className].ToArray();
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 全てのクラスタイプを取得
        /// </summary>
        private static IEnumerable<Type> GetAllTypes()
        {
            // Unity標準のクラスタイプ
            var buitinTypes = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(asm => asm.GetTypes())
            .Where(type => type != null && !string.IsNullOrEmpty(type.Namespace))
            .Where(type => type.Namespace.Contains("UnityEngine"));

            // 自作のクラスタイプ
            var myTypes = MonoScripts
            .Where(script => script != null)
            .Select(script => script.GetClass())
            .Where(classType => classType != null)
            .Where(classType => classType.Module.Name == "Assembly-CSharp.dll");

            return buitinTypes.Concat(myTypes)
            .Distinct();
        }
    }
}
```

```PrefabFinderWindow.cs
namespace PrefabFinder
{
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEditor;

    /// <summary>
    /// Prefabを見つけるEditorWindow
    /// </summary>
    public class PrefabFinderWindow : EditorWindow
    {
        [SerializeField] private string baseClassName;

        [MenuItem("Tools/Prefab Finder")]
        static void Open()
        {
            GetWindow<PrefabFinderWindow>("Prefab Finder");
        }

        void OnGUI()
        {
            EditorGUILayout.LabelField("クラス名");
            this.baseClassName = EditorGUILayout.TextField(this.baseClassName);

            if (GUILayout.Button("Find"))
            {
                Selection.objects = GetPrefabs(this.baseClassName).ToArray();
                EditorApplication.RepaintProjectWindow();
            }
        }

        /// <summary>
        /// 指定したクラスがアタッチされているPrefabを検索して取得する
        /// </summary>
        static IEnumerable<UnityEngine.Object> GetPrefabs(string typeName)
        {
            var guids = AssetDatabase.FindAssets("t:prefab", null);
            foreach (var guid in guids)
            {
                var classTypes = TypeGetter.GetComponentTypes(typeName);
                if (classTypes == null) { continue; }

                var path = AssetDatabase.GUIDToAssetPath(guid);
                var go = AssetDatabase.LoadAssetAtPath(path, typeof(UnityEngine.GameObject)) as GameObject;
                if (go == null) { continue; }

                var components = classTypes.SelectMany(classType => go.GetComponents(classType))
                .Where(component => component != null)
                .Distinct()
                .ToArray();

                if (components != null && components.Length != 0)
                {
                    yield return go;
                }
            }
        }
    }
}
```

# 使ってみる
## 手順1. スクリプトを作成
```BaseClass.cs
using UnityEngine;

public class BaseClass : MonoBehaviour
{
}

```

```SubClass.cs
public class SubClass : BaseClass
{
}
```

## 手順2. Prefabを作成
上記のスクリプトが付いているPrefabを作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/44288/f28e2a18-b621-a2da-e5c0-4f6b9e8a1b37.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/44288/a3d046fa-f9eb-04d8-2321-fa7e0bbfe7ae.png)

## 手順3. PrefabFinderを起動
メニューの "Tools/Prefab Finder" を選択してウィンドウを開きます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/44288/b91ebc92-32ce-06d2-d6ab-5e0f03a3a4c8.png)

## 手順4. クラス名を入力してFindボタンをクリック
![image.png](https://qiita-image-store.s3.amazonaws.com/0/44288/029696b9-f59b-e070-b52a-90a9bf3f5955.png)

## 手順5. 結果
指定したPrefabが選択されることが確認できます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/44288/95d38699-959b-ef49-d10e-e69556107565.png)


## 補足
**BaseClass**を指定した場合は Baseクラス付きPrefabと、**Baseクラスを継承した**SubClass付きPrefabの両方が選択されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/44288/78502213-da87-081b-9ea1-269700109dad.png)