[Unity] SortingLayerの使用状況を一覧表示するエディタ拡張

SortingLayerの使用状況を一覧で確認する方法がないか調たんですが、それっぽい機能がどこにもなさそうだったので自作しました。

- 仕様
「Renderer継承クラス（SpriteRendererなど）」「Canvas」を含むGameObjectを抽出して検索します。Sorting Layerに関連するクラスはこれでほぼ網羅できていると思いますが、ヌケモレあったら教えてください！

- 使い方
メニューの「UnmoEngineTools/Sorting Layer Finder」から実行できます。


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

namespace EditorScript
{
	/// <summary>
	/// エディタ拡張
	/// アセット全体／Hierarchy全体のGameObjectに対して、SortingLayer使用状況を一括検索
	/// メニュー選択した時点で検索開始するので、その間はUnityエディタが一時フリーズした感じになる
	/// </summary>
	public class SortingLayerFinder : EditorWindow
	{
		private Dictionary<string, List<string>> matchedObjectsPerLayer = new Dictionary<string, List<string>>();
		private List<Type> rendererTypes = new List<Type>();
		private Vector2 scrollPosition = Vector2.zero;

		[MenuItem("UnmoEngineTools/Sorting Layer Finder")]
		public static void Open()
		{
			var window = GetWindow<SortingLayerFinder>();
			window.Show();
		}

		private void OnEnable()
		{
			// Rendererの継承クラスを全て取得
			rendererTypes = AppDomain.CurrentDomain
				.GetAssemblies()
				.SelectMany(a => a.GetTypes())
				.Where(t => t.IsSubclassOf(typeof(Renderer)))
				.ToList();
			
			// 全てのSortingLayerに対して検索開始
			SortingLayer.layers
				.Select(layer => layer.name)
				.ToList()
				.ForEach(layerName => FindObjectsWithSortingLayer(layerName));
		}

		private void OnGUI()
		{
			// スクロールビュー追加
			scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
			
			// 各SortingLayerに対する結果を表示
			var style = new GUIStyle(EditorStyles.label);
			style.richText = true;
			foreach (var pair in matchedObjectsPerLayer)
			{
				EditorGUILayout.LabelField($"<b><color=green>Sorting Layer: {pair.Key}</color></b>", style);
				foreach (var matchedObject in pair.Value)
				{
					EditorGUILayout.LabelField(matchedObject);
				}

				EditorGUILayout.Space();
			}

			// スクロールビュー終了
			EditorGUILayout.EndScrollView();
		}

		private void FindObjectsWithSortingLayer(string sortingLayerName)
		{
			// SortingLayer毎の結果格納用リスト
			matchedObjectsPerLayer[sortingLayerName] = new List<string>();

			// Asset内のPrefabに対して検索
			string[] guids = AssetDatabase.FindAssets("t:Prefab");
			foreach (var guid in guids)
			{
				string assetPath = AssetDatabase.GUIDToAssetPath(guid);
				GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
				if (HasComponentWithSortingLayer(prefab, sortingLayerName))
				{
					matchedObjectsPerLayer[sortingLayerName].Add(assetPath);
				}
			}

			// Hierarchy内のGameObjectに対して検索
			GameObject[] allObjects = GameObject.FindObjectsOfType<GameObject>();
			foreach (var obj in allObjects)
			{
				if (HasComponentWithSortingLayer(obj, sortingLayerName))
				{
					string objectPath = GetGameObjectPath(obj.transform);
					matchedObjectsPerLayer[sortingLayerName].Add(objectPath);
				}
			}
		}

		private bool HasComponentWithSortingLayer(GameObject obj, string sortingLayerName)
		{
			// Renderer継承クラス
			if (rendererTypes.Any(t => HasComponentWithSortingLayerOfType(obj, t, sortingLayerName)))
			{
				return true;
			}

			// Canvas
			return HasComponentWithSortingLayerOfCanvas(obj, sortingLayerName);
		}

		private bool HasComponentWithSortingLayerOfType(GameObject obj, Type type, string sortingLayerName)
		{
			return obj.GetComponentsInChildren(type, true)
				.Cast<Renderer>() // typeはRendererのサブクラスのみであることが保証されているので、キャスト可能
				.Any(component => component.sortingLayerName == sortingLayerName);
		}

		private bool HasComponentWithSortingLayerOfCanvas(GameObject obj, string sortingLayerName)
		{
			return obj.GetComponentsInChildren<Canvas>(true)
				.Any(component => component.sortingLayerName == sortingLayerName);
		}
				
		private string GetGameObjectPath(Transform transform)
		{
			string path = transform.name;
			while (transform.parent != null)
			{
				transform = transform.parent;
				path = transform.name + "/" + path;
			}

			return path;
		}
	}
}
```