Kakoのいろいろやったこと記

主にUnity関連でやったことをかいていきます

Unityのプロジェクト内で、Componentや自作クラスの参照を見つけるEditor拡張をちょっと良くしました

これはなに?

以前作った、
kakovail.info
こちら、右クリックでサクッと探したいな、と思ったり、リロードの問題があったりしたので、リファクタがてら書き直していました。

使い方と変わったところ

前回に追加して、Projectビューからお好きなスクリプトを右クリック→メニューから選択でも調べられるようになりました。(Assets/KEditorExtensions/から)
Sceneロードでのバグも消しました。
非アクティブなSceneの対処はちょっと保留です。
f:id:Kakovail:20210118143638p:plain

コード

// Copyright (c) 2021 Hiroyuki Kako
// This software is released under the MIT License, see LICENSE.

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

public class ReferenceFinder : EditorWindow
{
    private static string _targetComponentName;
    private static List<GameObject> _foundAssets = new List<GameObject>();
    private static IEnumerable<Type> _cashedComponents;
    private static Dictionary<string, List<Type>> _typeDict;
    private static bool _isFirstTime = true;
    static MonoScript[] _monoScripts;

    private Vector2 _currentScrollPosition;

    [InitializeOnLoadMethod]
    private static void InitializeEvent()
    {
        EditorSceneManager.sceneClosing += OnClosing;
    }

    private static void OnClosing(Scene scene, bool removingScene)
    {
        if (!HasOpenInstances<ReferenceFinder>()) return;
        GetWindow<ReferenceFinder>().Close();
    }

    [MenuItem("KEditorExtensions/Reference/ReferenceFinder")]
    private static void Open()
    {
        _cashedComponents = GetAllTypes();
        GetWindow<ReferenceFinder>("Components Reference Finder.");
    }

    [MenuItem("Assets/KEditorExtensions/Find references in active scene and all prefabs")]
    private static void FindWithRightClick()
    {
        var name = Selection.activeObject;

        _cashedComponents = GetAllTypes();
        GetWindow<ReferenceFinder>("Components Reference Finder.");
        _targetComponentName = name.name;
        SearchObjects();
    }

    private void OnGUI()
    {
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("Never change Scene before reset this window");
        EditorGUILayout.EndHorizontal();
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("This window can search active scene objects and all prefabs");
        EditorGUILayout.EndHorizontal();
        EditorGUILayout.BeginHorizontal();
        _targetComponentName =
            EditorGUILayout.TextField("Target Component Name: ", _targetComponentName);
        EditorGUILayout.EndHorizontal();

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

            EditorGUILayout.EndScrollView();
        }

        if (GUILayout.Button("Search"))
        {
            SearchObjects();
        }
    }

    private static void SearchObjects()
    {
        _foundAssets.Clear();

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

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

            var typeCash = GetType(_targetComponentName) ?? null;

            if (typeCash == null)
            {
                Debug.Log("No Type Found.");
                return;
            }

            var tmp = loadAsset.GetComponentsInChildren(GetType(_targetComponentName) ?? null);

            foreach (var kari in tmp)
            {
                _foundAssets.Add(kari.gameObject);
            }
        }

        var allObj = GetObjectsInAllScene();
        foreach (var obj in allObj)
        {
            var typeCash = GetType(_targetComponentName) ?? null;
            var tmp = obj.GetComponentsInChildren(GetType(_targetComponentName) ?? null);

            if (typeCash == null)
            {
                Debug.Log("No Type Found.");
                return;
            }

            foreach (var kari in tmp)
            {
                _foundAssets.Add(kari.gameObject);
            }
        }
    }

    /// <summary>
    /// All script files that exist in the project
    /// </summary>
    static MonoScript[] MonoScripts
    {
        get { return _monoScripts ?? (_monoScripts = Resources.FindObjectsOfTypeAll<MonoScript>().ToArray()); }
    }

    /// <summary>
    /// Getting the type from the class name
    /// </summary>
    private static Type GetType(string className)
    {
        if (_typeDict == null)
        {
            _typeDict = new Dictionary<string, List<Type>>();
            foreach (var type in _cashedComponents)
            {
                if (!_typeDict.ContainsKey(type.Name))
                {
                    _typeDict.Add(type.Name, new List<Type>());
                }

                _typeDict[type.Name].Add(type);
            }
        }

        // If the class exists, show it in the list
        if (_typeDict.ContainsKey(className))
        {
            return _typeDict[className][0];
        }
        else
        {
            // If the class doesn't exist, get it just in case, and run again.
            if (_isFirstTime)
            {
                Debug.Log("Not found. ReScanning...");
                _cashedComponents = GetAllTypes();
                _isFirstTime = false;
                GetType(className);
            }

            _isFirstTime = true;

            return null;
        }
    }

    /// <summary>
    /// Get all class
    /// </summary>
    private static IEnumerable<Type> GetAllTypes()
    {
        var types = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(asm => asm.GetTypes())
            .Where(type => type != null && !string.IsNullOrEmpty(type.Namespace))
            .Where(type => type.Namespace.Contains("UnityEngine"));

        var localTypes = MonoScripts
            .Where(script => script != null)
            .Select(script => script.GetClass())
            .Where(classType => classType != null)
            .Where(classType => classType.Module.Name == "Assembly-CSharp.dll");

        return types.Concat(localTypes).Distinct();
    }

    private static GameObject[] GetObjectsInAllScene(bool includeInactive = true)
    {
        var sceneNumber = SceneManager.sceneCount;

        IEnumerable<GameObject> resultObjects = (GameObject[]) Enumerable.Empty<GameObject>();

        for (int i = 0; i < sceneNumber; i++)
        {
            var obj = SceneManager.GetSceneAt(i).GetRootGameObjects();
            resultObjects = resultObjects.Concat(obj);
        }

        return resultObjects.ToArray();
    }
}
#endif