📗

【Unity】キャンバスの描画順を表示する拡張機能

2024/08/29に公開

はじめに

初めての方も、そうでない方もこんにちは!
現役ゲームプログラマーのたむぼーです。
自己紹介を載せているので、気になる方は見ていただければ嬉しいです!

今回は
 キャンバスの描画順を表示する拡張機能
について紹介します

https://zenn.dev/tmb/articles/1072f8ea010299

使い方

エディター上部のメニューからCustomEditorのCanvasOrderViewでウィンドウを表示
ウィンドウ内のShow Canvas Treeボタンを押す

スクリプト

こちらの処理をEditorフォルダに配置してください。

CanvasOrderView.cs
using UnityEngine;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using System.Collections.Generic;
using System.Linq;

namespace Editor
{
#if UNITY_EDITOR
    public class CanvasOrderView : EditorWindow
    {
        private bool _isView = false;
        private TreeViewState _treeViewState;
        private CanvasOrderTreeView _treeView;

        /// <summary>
        /// ウィンドウを表示
        /// </summary>
        [MenuItem("CustomEditor/CanvasOrderView")]
        public static void ShowWindow()
        {
            GetWindow<CanvasOrderView>("Canvas Order View");
        }

        /// <summary>
        /// ウィンドウに描画
        /// </summary>
        private void OnGUI()
        {
            if (GUILayout.Button("Show Canvas Tree", GUILayout.Width(150)))
            {
                if (_isView)
                {
                    _treeView.Reload();
                }
                else
                {
                    _treeViewState = new TreeViewState();
                    _treeView = new CanvasOrderTreeView(_treeViewState);
                    _isView = true;
                }
            }
            if (_isView)
            {
                // ヘッダー行を表示
                EditorGUILayout.BeginHorizontal();
                GUILayout.Space(20);
                EditorGUILayout.LabelField("Order", GUILayout.Width(250));
                EditorGUILayout.LabelField("Reference", GUILayout.Width(250));
                EditorGUILayout.EndHorizontal();

                GUILayout.Box("", GUILayout.Width(position.width), GUILayout.Height(1));

                // ツリービューの描画領域を設定
                Rect rect = new Rect(0, 50, position.width, position.height - 50);
                _treeView.OnGUI(rect);
            }
        }
    }

    public class CanvasOrderTreeView : TreeView
    {
        private List<Canvas> _rootCanvases;
        private HashSet<int> _displayedCanvasIds;

        /// <summary>
        /// コンストラクタ。
        /// </summary>
        public CanvasOrderTreeView(TreeViewState state) : base(state)
        {
            _displayedCanvasIds = new HashSet<int>();
            Reload();
        }

        /// <summary>
        /// ツリービューのルートを構築
        /// </summary>
        protected override TreeViewItem BuildRoot()
        {
            _displayedCanvasIds.Clear();

            // シーン内のすべてのキャンバスを取得
            var allCanvases = Object.FindObjectsOfType<Canvas>().ToList();

            // ルートキャンバス(親がない、または親がCanvasでないオブジェクト)をフィルタリング
            _rootCanvases = allCanvases.Where(c => c.transform.parent == null || c.transform.parent.GetComponentInParent<Canvas>() == null).ToList();

            var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };

            // カメラとオーバーレイのキャンバスを分ける
            var cameraCanvases = _rootCanvases.Where(c => c.renderMode == RenderMode.ScreenSpaceCamera).ToList();
            var overlayCanvases = _rootCanvases.Where(c => c.renderMode == RenderMode.ScreenSpaceOverlay).ToList();

            var cameraItem = new TreeViewItem { id = 1, depth = 0, displayName = "Camera" };
            root.AddChild(cameraItem);

            // Depthごとにカメラキャンバスをグループ化
            var groupedCameraCanvases = cameraCanvases.GroupBy(c => c.worldCamera.depth)
                .Select(g => new { Depth = g.Key, Canvases = g.OrderBy(c => c.sortingLayerName).ThenBy(c => c.sortingOrder).ToList() });

            foreach (var group in groupedCameraCanvases)
            {
                var depthItem = new TreeViewItem { id = group.Depth.GetHashCode(), depth = 1, displayName = $"Depth = {group.Depth}" };
                cameraItem.AddChild(depthItem);

                // Sorting Layerごとにさらにグループ化
                var groupedByLayer = group.Canvases.GroupBy(c => c.sortingLayerName)
                    .Select(g => new { Layer = g.Key, Canvases = g.OrderBy(c => c.sortingOrder).ToList() });

                foreach (var layerGroup in groupedByLayer)
                {
                    var layerItem = new TreeViewItem { id = layerGroup.Layer.GetHashCode(), depth = 2, displayName = $"Sorting Layer = {layerGroup.Layer}" };
                    depthItem.AddChild(layerItem);

                    foreach (var canvas in layerGroup.Canvases)
                    {
                        var canvasItem = new TreeViewItem { id = canvas.GetInstanceID(), depth = 3, displayName = $"Order in Layer = {canvas.sortingOrder}" };
                        layerItem.AddChild(canvasItem);

                        // 子キャンバスを追加
                        AddChildCanvases(canvasItem, canvas, "Order in Layer");
                    }
                }
            }

            var overlayItem = new TreeViewItem { id = 2, depth = 0, displayName = "Overlay" };
            root.AddChild(overlayItem);

            // Sort Orderでソート
            var sortedOverlayCanvases = overlayCanvases.OrderBy(c => c.sortingOrder).ToList();

            foreach (var canvas in sortedOverlayCanvases)
            {
                var canvasItem = new TreeViewItem { id = canvas.GetInstanceID(), depth = 1, displayName = $"Sort Order = {canvas.sortingOrder}" };
                overlayItem.AddChild(canvasItem);

                // 子キャンバスを追加
                AddChildCanvases(canvasItem, canvas, "Sort Order");
            }

            // ルートの深さを設定
            SetupDepthsFromParentsAndChildren(root);
            return root;
        }

        /// <summary>
        /// キャンバスの子キャンバスを追加
        /// </summary>
        private void AddChildCanvases(TreeViewItem parentItem, Canvas parentCanvas, string orderType)
        {
            var children = parentCanvas.GetComponentsInChildren<Canvas>(true).Where(c => c != parentCanvas).ToList();
            foreach (var childCanvas in children)
            {
                if (!_displayedCanvasIds.Contains(childCanvas.GetInstanceID()))
                {
                    var childItem = new TreeViewItem { id = childCanvas.GetInstanceID(), depth = parentItem.depth + 1, displayName = $"{orderType} = {childCanvas.sortingOrder}" };
                    parentItem.AddChild(childItem);
                    _displayedCanvasIds.Add(childCanvas.GetInstanceID());
                    AddChildCanvases(childItem, childCanvas, orderType);
                }
            }
        }

        /// <summary>
        /// ツリーの行を描画
        /// </summary>
        protected override void RowGUI(RowGUIArgs args)
        {
            var canvas = EditorUtility.InstanceIDToObject(args.item.id) as Canvas;

            Rect rect = args.rowRect;
            rect.x += GetContentIndent(args.item);
            rect.width -= GetContentIndent(args.item);

            EditorGUI.DrawRect(new Rect(rect.x, rect.y, rect.width, rect.height), new Color(0.1f, 0.1f, 0.1f, 0.2f));

            // キャンバスの情報を表示
            EditorGUI.LabelField(new Rect(rect.x, rect.y, 150, rect.height), args.item.displayName);

            // キャンバスの参照を表示
            if (canvas != null)
            {
                EditorGUI.ObjectField(new Rect(rect.x + 150, rect.y, rect.width - 150, rect.height), canvas.gameObject, typeof(GameObject), true);
            }
        }
    }
#endif
}

Discussion