Jelajahi Sumber

Entity Tree Window (#393)

* 调整 Editor Window Dock 规则

* 增加 Entity Tree Window 具体实现,并适配 DebugDisplay 机制
L 3 tahun lalu
induk
melakukan
47acf5bc0f
22 mengubah file dengan 531 tambahan dan 6 penghapusan
  1. 5 1
      Codes/Model/Share/Module/Unit/Unit.cs
  2. 6 1
      Unity/Assets/Scripts/Core/Entity/Scene.cs
  3. 1 1
      Unity/Assets/Scripts/Editor/BuildEditor/BuildEditor.cs
  4. 8 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu.meta
  5. 9 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/AEntityMenuHandler.cs
  6. 11 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/AEntityMenuHandler.cs.meta
  7. 76 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/EntityContextMenu.cs
  8. 11 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/EntityContextMenu.cs.meta
  9. 16 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/EntityMenuAttribute.cs
  10. 11 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/EntityMenuAttribute.cs.meta
  11. 17 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/MenuExample.cs
  12. 11 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/MenuExample.cs.meta
  13. 8 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree.meta
  14. 101 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeView.cs
  15. 11 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeView.cs.meta
  16. 70 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeViewItem.cs
  17. 11 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeViewItem.cs.meta
  18. 107 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeWindow.cs
  19. 11 0
      Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeWindow.cs.meta
  20. 10 0
      Unity/Assets/Scripts/Editor/Helper/DockDefine.cs
  21. 11 0
      Unity/Assets/Scripts/Editor/Helper/DockDefine.cs.meta
  22. 9 3
      Unity/Assets/Scripts/Editor/ServerCommandLineEditor/ServerCommandLineEditor.cs

+ 5 - 1
Codes/Model/Share/Module/Unit/Unit.cs

@@ -1,9 +1,11 @@
-using MongoDB.Bson.Serialization.Attributes;
+using System.Diagnostics;
+using MongoDB.Bson.Serialization.Attributes;
 using UnityEngine;
 
 namespace ET
 {
     [ChildOf(typeof(UnitComponent))]
+    [DebuggerDisplay("DebuggerDisplay,nq")]
     public class Unit: Entity, IAwake<int>
     {
         public int ConfigId { get; set; } //配置表id
@@ -48,5 +50,7 @@ namespace ET
                 Game.EventSystem.Publish(this.DomainScene(), new EventType.ChangeRotation() { Unit = this });
             }
         }
+
+        private string DebuggerDisplay => this.Config.Name;
     }
 }

+ 6 - 1
Unity/Assets/Scripts/Core/Entity/Scene.cs

@@ -1,6 +1,9 @@
-namespace ET
+using System.Diagnostics;
+
+namespace ET
 {
     [EnableMethod]
+    [DebuggerDisplay("DebuggerDisplay,nq")]
     public sealed class Scene: Entity
     {
         public int Zone
@@ -95,5 +98,7 @@
                 this.parent.Children.Add(this.Id, this);
             }
         }
+
+        private string DebuggerDisplay => this.SceneType.ToString();
     }
 }

+ 1 - 1
Unity/Assets/Scripts/Editor/BuildEditor/BuildEditor.cs

@@ -41,7 +41,7 @@ namespace ET
 		[MenuItem("ET/Build Tool")]
 		public static void ShowWindow()
 		{
-			GetWindow(typeof (BuildEditor));
+			GetWindow<BuildEditor>(DockDefine.Types);
 		}
 
         private void OnEnable()

+ 8 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 169de76bd06b34fc088e863464d8416e
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/AEntityMenuHandler.cs

@@ -0,0 +1,9 @@
+namespace ET
+{
+    public abstract class AEntityMenuHandler
+    {
+        internal string menuName;
+
+        public abstract void OnClick(Entity entity);
+    }
+}

+ 11 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/AEntityMenuHandler.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c808d888c71fa42a58eee31bc99771dc
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 76 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/EntityContextMenu.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Reflection;
+using UnityEditor;
+using UnityEngine;
+
+namespace ET
+{
+    public static class EntityContextMenu
+    {
+        private static MultiMap<string, AEntityMenuHandler> ACTIONS = new();
+
+        private static GenericMenu menu;
+
+        static EntityContextMenu()
+        {
+            var types = TypeCache.GetTypesWithAttribute<EntityMenuAttribute>();
+
+            foreach(var type in types)
+            {
+                var menu = type.GetCustomAttribute<EntityMenuAttribute>();
+
+                if(menu is null)
+                {
+                    continue;
+                }
+
+                if(Activator.CreateInstance(type) is not AEntityMenuHandler action)
+                {
+                    continue;
+                }
+
+                action.menuName = menu.menu_name;
+                ACTIONS.Add(menu.bind_to.Name, action);
+            }
+        }
+
+        public static void Show(object entity)
+        {
+            if(entity is null)
+            {
+                return;
+            }
+
+            string name = entity.GetType().Name;
+
+            ACTIONS.TryGetValue(name, out var actions);
+
+            if(actions is null)
+            {
+                return;
+            }
+
+            menu = new GenericMenu();
+
+            foreach(var action in actions)
+            {
+                menu.AddItem(
+                    new GUIContent(action.menuName),
+                    false,
+                    delegate(object data)
+                    {
+                        if(data is not Entity callback_data)
+                        {
+                            return;
+                        }
+
+                        action.OnClick(callback_data);
+                    },
+                    entity
+                );
+            }
+
+            menu.ShowAsContext();
+        }
+    }
+}

+ 11 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/EntityContextMenu.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5772cbaee6225405983b3a40a4031a7c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 16 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/EntityMenuAttribute.cs

@@ -0,0 +1,16 @@
+using System;
+
+namespace ET
+{
+    public class EntityMenuAttribute : Attribute
+    {
+        public readonly Type   bind_to;
+        public readonly string menu_name;
+
+        public EntityMenuAttribute(Type bind_to, string menu_name)
+        {
+            this.bind_to   = bind_to;
+            this.menu_name = menu_name;
+        }
+    }
+}

+ 11 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/EntityMenuAttribute.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 27184ef12a8b34e6babdb7e9bdf6fcef
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 17 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/MenuExample.cs

@@ -0,0 +1,17 @@
+// #define ENABLE
+
+#if ENABLE
+namespace ET
+{
+    [EntityMenu(typeof (Unit), "打开属性菜单")]
+    public class UnitNumericWindowMenu: AEntityMenuHandler
+    {
+        public override void OnClick(Entity entity)
+        {
+            var unit = entity as Unit;
+
+            Log.Debug(unit.Config.Name);
+        }
+    }
+}
+#endif

+ 11 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/ContextMenu/MenuExample.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d55e90d5e4c8f435494683f0df653500
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e01001106eb09483d8d5eeb6bcba9a12
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 101 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeView.cs

@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEditor.IMGUI.Controls;
+using UnityEngine;
+
+namespace ET
+{
+    public class EntityTreeView : TreeView
+    {
+        private EntityTreeViewItem root;
+        private int                id;
+
+        public Dictionary<int, Entity> all = new();
+
+        public EntityTreeView(TreeViewState state) : base(state)
+        {
+            Reload();
+            useScrollView = true;
+        }
+
+        protected override TreeViewItem BuildRoot()
+        {
+            this.id = 0;
+
+            this.root       = PreOrder(Game.Scene);
+            this.root.depth = -1;
+
+            SetupDepthsFromParentsAndChildren(this.root);
+
+            return this.root;
+        }
+
+
+        private EntityTreeViewItem PreOrder(Entity root)
+        {
+            if(root is null)
+            {
+                return null;
+            }
+
+            this.id++;
+
+            var item = new EntityTreeViewItem(root, this.id);
+
+            all[this.id] = root;
+
+            if(root.Components.Count > 0)
+            {
+                foreach(var component in root.Components.Values)
+                {
+                    item.AddChild(PreOrder(component));
+                }
+            }
+
+            if(root.Children.Count > 0)
+            {
+                foreach(var child in root.Children.Values)
+                {
+                    item.AddChild(PreOrder(child));
+                }
+            }
+
+            return item;
+        }
+
+
+        /// <summary>
+        /// 处理右键内容
+        /// </summary>
+        /// <param name="id"></param>
+        protected override void ContextClickedItem(int id)
+        {
+            if(Event.current.button != 1)
+            {
+                return;
+            }
+
+            SingleClickedItem(id);
+
+            EntityContextMenu.Show(EntityTreeWindow.VIEW_MONO.Component);
+        }
+
+
+        /// <summary>
+        /// 处理左键内容
+        /// </summary>
+        /// <param name="id"></param>
+        protected override void SingleClickedItem(int id)
+        {
+            all.TryGetValue(id, out var entity);
+
+            if(entity is null)
+            {
+                return;
+            }
+
+            EntityTreeWindow.VIEW_MONO.Component = entity;
+            Selection.activeObject            = EntityTreeWindow.VIEW_MONO;
+        }
+    }
+}

+ 11 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeView.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e1fe41ca5c4554eceb23f9a7d6eeacf6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 70 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeViewItem.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Runtime.CompilerServices;
+using Microsoft.CSharp.RuntimeBinder;
+using UnityEditor.IMGUI.Controls;
+
+namespace ET
+{
+    public class EntityTreeViewItem : TreeViewItem
+    {
+        private Entity entity;
+
+        public EntityTreeViewItem(Entity entity, int id)
+        {
+            this.entity = entity;
+            base.id = id;
+        }
+
+        public override string displayName
+        {
+            get
+            {
+                if(!string.IsNullOrEmpty(_displayName))
+                {
+                    return _displayName;
+                }
+
+                string name = this.entity.GetType().Name;
+
+                string debugger_name = ReadDebuggerDisplay(entity);
+
+                _displayName = string.IsNullOrEmpty(debugger_name) ? name : $"{name}: ({debugger_name})";
+
+                return _displayName;
+            }
+        }
+
+        private string _displayName;
+        
+        // https://stackoverflow.com/a/13650728/37055
+        private static object ReadProperty(object target, string propertyName)
+        {
+            var args = new[] {CSharpArgumentInfo.Create(0, null)};
+            var binder = Binder.GetMember(
+                0,
+                propertyName,
+                target.GetType(),
+                args
+            );
+            var site = CallSite<Func<CallSite, object, object>>.Create(binder);
+            return site.Target(site, target);
+        }
+
+        private static string ReadDebuggerDisplay(object target, string propertyName = "DebuggerDisplay")
+        {
+            string debuggerDisplay = string.Empty;
+            try
+            {
+                var value = ReadProperty(target, propertyName) ?? string.Empty;
+
+                debuggerDisplay = value as string ?? value.ToString();
+            }
+            catch(Exception)
+            {
+                // ignored
+            }
+
+            return debuggerDisplay;
+        }
+    }
+}

+ 11 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeViewItem.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d8e39e236d1e54f468beb9e8a688df36
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 107 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeWindow.cs

@@ -0,0 +1,107 @@
+using UnityEditor;
+using UnityEditor.IMGUI.Controls;
+using UnityEngine;
+
+namespace ET
+{
+    public class EntityTreeWindow: EditorWindow
+    {
+        internal static ComponentView VIEW_MONO;
+
+        private static EntityTreeWindow WINDOW;
+
+        private EntityTreeView treeView;
+        private SearchField    searchField;
+
+        [MenuItem("ET/Entity Tree Window")]
+        private static void OpenWindow()
+        {
+            if(!Application.isPlaying)
+            {
+                EditorUtility.DisplayDialog("警告", "运行后才可使用", "确定");
+                return;
+            }
+
+            VIEW_MONO = new GameObject("View").AddComponent<ComponentView>();
+            DontDestroyOnLoad(VIEW_MONO);
+
+            WINDOW              = GetWindow<EntityTreeWindow>(DockDefine.Types);
+            WINDOW.titleContent = new GUIContent("Entity Tree Window");
+            WINDOW.Show();
+        }
+
+        private void OnEnable()
+        {
+            this.treeView                            =  new EntityTreeView(new TreeViewState());
+            this.searchField                         =  new SearchField();
+            this.searchField.downOrUpArrowKeyPressed += this.treeView.SetFocusAndEnsureSelectedItem;
+            EditorApplication.playModeStateChanged   += OnPlayModeStateChange;
+        }
+
+        private void OnPlayModeStateChange(PlayModeStateChange state)
+        {
+            if(state != PlayModeStateChange.ExitingPlayMode)
+            {
+                return;
+            }
+
+            WINDOW.Close();
+        }
+
+        private void OnDestroy()
+        {
+            EditorApplication.playModeStateChanged -= OnPlayModeStateChange;
+            DestroyImmediate(VIEW_MONO.gameObject);
+            VIEW_MONO = null;
+        }
+
+        private void OnGUI()
+        {
+            this.treeView.searchString = this.searchField.OnGUI(
+                new Rect(
+                    0,
+                    0,
+                    position.width - 40f,
+                    20f
+                ),
+                this.treeView.searchString
+            );
+
+            this.treeView.OnGUI(
+                new Rect(
+                    0,
+                    20f,
+                    position.width,
+                    position.height - 40f
+                )
+            );
+
+            this.treeView.Reload();
+
+            GUILayout.BeginArea(
+                new Rect(
+                    20f,
+                    position.height - 18f,
+                    position.width  - 40f,
+                    16f
+                )
+            );
+
+            using(new EditorGUILayout.HorizontalScope())
+            {
+                string style = "miniButton";
+                if(GUILayout.Button("Expand all", style))
+                {
+                    this.treeView.ExpandAll();
+                }
+
+                if(GUILayout.Button("Collapse all", style))
+                {
+                    this.treeView.CollapseAll();
+                }
+            }
+
+            GUILayout.EndArea();
+        }
+    }
+}

+ 11 - 0
Unity/Assets/Scripts/Editor/ComponentViewEditor/Tree/EntityTreeWindow.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 938ac429663ab420d97f87fea3317bff
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 10 - 0
Unity/Assets/Scripts/Editor/Helper/DockDefine.cs

@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+
+namespace ET
+{
+    public static class DockDefine
+    {
+        public static Type[] Types = { typeof (BuildEditor), typeof (ServerCommandLineEditor), typeof (EntityTreeWindow) };
+    }
+}

+ 11 - 0
Unity/Assets/Scripts/Editor/Helper/DockDefine.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cd787c944ffb94cb2a822db7f9f486b3
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 3
Unity/Assets/Scripts/Editor/ServerCommandLineEditor/ServerCommandLineEditor.cs

@@ -18,7 +18,7 @@ namespace ET
         [MenuItem("ET/ServerTools")]
         public static void ShowWindow()
         {
-            GetWindow(typeof (ServerCommandLineEditor));
+            GetWindow<ServerCommandLineEditor>(DockDefine.Types);
         }
         
         private int selectStartConfigIndex;
@@ -38,17 +38,23 @@ namespace ET
             this.startConfig = this.startConfigs[this.selectStartConfigIndex];
             this.developMode = (DevelopMode) EditorGUILayout.EnumPopup("起服模式:", this.developMode);
             int develop = (int) this.developMode;
+
+            string dotnet = "dotnet.exe";
+            
+#if UNITY_EDITOR_OSX
+            dotnet = "dotnet";
+#endif
             
             if (GUILayout.Button("Start Server(Single Srocess)"))
             {
                 string arguments = $"App.dll --Process=1 --StartConfig=StartConfig/{this.startConfig} --Console=1";
-                ProcessHelper.Run("dotnet.exe", arguments, "../Bin/");
+                ProcessHelper.Run(dotnet, arguments, "../Bin/");
             }
             
             if (GUILayout.Button("Start Watcher"))
             {
                 string arguments = $"App.dll --AppType=Watcher --StartConfig=StartConfig/{this.startConfig} --Console=1";
-                ProcessHelper.Run("dotnet.exe", arguments, "../Bin/");
+                ProcessHelper.Run(dotnet, arguments, "../Bin/");
             }
 
             if (GUILayout.Button("Start Mongo"))