[Unity] 指定したAssetがどこで参照されているか探すEditor拡張


## 環境
- Unity 5.5.2f1

## やりたいこと
「このScript/Prefab/SpriteってどのScene/Prefabにアタッチしてたっけ？」を解消する。
要するに [ReferenceViewer](https://github.com/anchan828/ReferenceViewer) もどきです。

実際に上記の事例で困った && Editor拡張の練習がてら作ってみました。

## Script

```cs
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

namespace FindReferencesInProject
{
    public class FindReferencesInProject : EditorWindow
    {
        private static Dictionary<AssetData, List<AssetData>> Results = new Dictionary<AssetData, List<AssetData>>();
        private static Dictionary<AssetData, bool> Foldouts = new Dictionary<AssetData, bool>();
        private Vector2 ScrollPosition = Vector2.zero;

        [MenuItem("Assets/Find References In Project", true)]
        static bool IsEnabled()
        {
            return Selection.objects.Any();
        }

        [MenuItem("Assets/Find References In Project", false, 25)]
        static void Search()
        {
            Results.Clear();
            Foldouts.Clear();

            foreach (var target in AssetDatabase.FindAssets("t:Scene t:Prefab").Select(AssetData.CreateByGuid)) {
                foreach (var referent in AssetDatabase.GetDependencies(target.Path).Select(AssetData.CreateByPath))
                {
                    if (target.Equals(referent)) { continue; }

                    foreach (var selected in Selection.objects.Select(AssetData.CreateByObject))
                    {
                        if (referent.Equals(selected))
                        {
                            Results.AddSafety(referent, new List<AssetData>());
                            Results[referent].Add(target);
                        }
                    }
                }
            }

            GetWindow<FindReferencesInProject>();
        }

        void OnGUI()
        {
            this.ScrollPosition = GUILayout.BeginScrollView(this.ScrollPosition);

            foreach (var referent in Results.Keys.OrderBy(key => key.Name).ToList())
            {
                Foldouts.AddSafety(referent, true);

                if (Foldouts[referent] = EditorGUILayout.Foldout(Foldouts[referent], referent.Name))
                {
                    foreach (var target in Results[referent])
                    {
                        var iconSize = EditorGUIUtility.GetIconSize();
                        EditorGUIUtility.SetIconSize(Vector2.one * 16);

                        var obj = target.ToObject();
                        var content = new GUIContent(target.Name, EditorGUIUtility.ObjectContent(obj, obj.GetType()).image);

                        if (GUILayout.Button(content, "Label"))
                        {
                            Selection.objects = new[] { obj };
                        }

                        EditorGUIUtility.SetIconSize(iconSize);
                    }
                }
            }

            GUILayout.EndScrollView();
        }
    }

    public class AssetData
    {
        public string Name { get; }
        public string Path { get; }
        public string Guid { get; }

        public AssetData(string name, string path, string guid)
        {
            this.Name = name;
            this.Path = path;
            this.Guid = guid;
        }

        public static AssetData CreateByObject(Object obj)
        {
            var path = AssetDatabase.GetAssetPath(obj);
            var guid = AssetDatabase.AssetPathToGUID(path);
            var name = obj.name;
            return new AssetData(name, path, guid);
        }

        public static AssetData CreateByPath(string path)
        {
            var guid = AssetDatabase.AssetPathToGUID(path);
            var name = AssetDatabase.LoadMainAssetAtPath(path).name;
            return new AssetData(name, path, guid);
        }

        public static AssetData CreateByGuid(string guid)
        {
            var path = AssetDatabase.GUIDToAssetPath(guid);
            var name = AssetDatabase.LoadMainAssetAtPath(path).name;
            return new AssetData(name, path, guid);
        }

        public Object ToObject()
        {
            return AssetDatabase.LoadAssetAtPath<Object>(this.Path);
        }

        public override bool Equals(object obj)
        {
            var other = obj as AssetData;
            return other != null && this.Guid == other.Guid;
        }

        public override int GetHashCode()
        {
            return this.Guid.GetHashCode();
        }
    }

    public static class DictionaryExtension
    {
        public static void AddSafety<K,V>(this Dictionary<K,V> self, K key, V value)
        {
            if (!self.ContainsKey(key))
            {
                self.Add(key, value);
            }
        }
    }
}
```

## デモ

探したいScript選択して、右クリックから「Find References In Project」で、そのScriptを参照しているScene/Prefabを検索してくれます。

![find_references_project_1.gif](https://qiita-image-store.s3.amazonaws.com/0/43938/15d9faa2-9bb8-d045-d55a-2b78d72d0380.gif "find_references_project_1.gif")

Scriptだけでなく、Sprite/Prefabに対しても検索できます。

![find_references_project_2.gif](https://qiita-image-store.s3.amazonaws.com/0/43938/2850063d-94a3-f57b-2c35-e800ae9b9196.gif "find_references_project_2.gif")

複数選択で一度に検索できます。

![find_references_project_3.gif](https://qiita-image-store.s3.amazonaws.com/0/43938/c1e5c405-a731-37e6-f8ca-1f6a3995a790.gif "find_references_project_3.gif")

機能はこれだけですし、見た目も全く適当ですが、結構便利な感じがします。