qianzhuqing 3 年 前
コミット
7409cd9035
100 ファイル変更30149 行追加0 行削除
  1. 9 0
      .gitignore
  2. 8 0
      Assets/GameFramework.meta
  3. 1064 0
      Assets/GameFramework/GameFramework.prefab
  4. 7 0
      Assets/GameFramework/GameFramework.prefab.meta
  5. 21 0
      Assets/GameFramework/LICENSE.md
  6. 7 0
      Assets/GameFramework/LICENSE.md.meta
  7. 8 0
      Assets/GameFramework/Libraries.meta
  8. BIN
      Assets/GameFramework/Libraries/GameFramework.dll
  9. 22 0
      Assets/GameFramework/Libraries/GameFramework.dll.meta
  10. 23646 0
      Assets/GameFramework/Libraries/GameFramework.xml
  11. 7 0
      Assets/GameFramework/Libraries/GameFramework.xml.meta
  12. BIN
      Assets/GameFramework/Libraries/ICSharpCode.SharpZipLib.dll
  13. 22 0
      Assets/GameFramework/Libraries/ICSharpCode.SharpZipLib.dll.meta
  14. 41 0
      Assets/GameFramework/Libraries/link.xml
  15. 7 0
      Assets/GameFramework/Libraries/link.xml.meta
  16. 101 0
      Assets/GameFramework/README.md
  17. 7 0
      Assets/GameFramework/README.md.meta
  18. 8 0
      Assets/GameFramework/Scripts.meta
  19. 8 0
      Assets/GameFramework/Scripts/Editor.meta
  20. 8 0
      Assets/GameFramework/Scripts/Editor/Inspector.meta
  21. 290 0
      Assets/GameFramework/Scripts/Editor/Inspector/BaseComponentInspector.cs
  22. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/BaseComponentInspector.cs.meta
  23. 74 0
      Assets/GameFramework/Scripts/Editor/Inspector/ConfigComponentInspector.cs
  24. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/ConfigComponentInspector.cs.meta
  25. 51 0
      Assets/GameFramework/Scripts/Editor/Inspector/DataNodeComponentInspector.cs
  26. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/DataNodeComponentInspector.cs.meta
  27. 87 0
      Assets/GameFramework/Scripts/Editor/Inspector/DataTableComponentInspector.cs
  28. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/DataTableComponentInspector.cs.meta
  29. 68 0
      Assets/GameFramework/Scripts/Editor/Inspector/DebuggerComponentInspector.cs
  30. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/DebuggerComponentInspector.cs.meta
  31. 156 0
      Assets/GameFramework/Scripts/Editor/Inspector/DownloadComponentInspector.cs
  32. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/DownloadComponentInspector.cs.meta
  33. 52 0
      Assets/GameFramework/Scripts/Editor/Inspector/EditorResourceComponentInspector.cs
  34. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/EditorResourceComponentInspector.cs.meta
  35. 88 0
      Assets/GameFramework/Scripts/Editor/Inspector/EntityComponentInspector.cs
  36. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/EntityComponentInspector.cs.meta
  37. 41 0
      Assets/GameFramework/Scripts/Editor/Inspector/EventComponentInspector.cs
  38. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/EventComponentInspector.cs.meta
  39. 73 0
      Assets/GameFramework/Scripts/Editor/Inspector/FileSystemComponentInspector.cs
  40. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/FileSystemComponentInspector.cs.meta
  41. 53 0
      Assets/GameFramework/Scripts/Editor/Inspector/FsmComponentInspector.cs
  42. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/FsmComponentInspector.cs.meta
  43. 64 0
      Assets/GameFramework/Scripts/Editor/Inspector/GameFrameworkInspector.cs
  44. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/GameFrameworkInspector.cs.meta
  45. 76 0
      Assets/GameFramework/Scripts/Editor/Inspector/LocalizationComponentInspector.cs
  46. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/LocalizationComponentInspector.cs.meta
  47. 76 0
      Assets/GameFramework/Scripts/Editor/Inspector/NetworkComponentInspector.cs
  48. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/NetworkComponentInspector.cs.meta
  49. 138 0
      Assets/GameFramework/Scripts/Editor/Inspector/ObjectPoolComponentInspector.cs
  50. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/ObjectPoolComponentInspector.cs.meta
  51. 172 0
      Assets/GameFramework/Scripts/Editor/Inspector/ProcedureComponentInspector.cs
  52. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/ProcedureComponentInspector.cs.meta
  53. 152 0
      Assets/GameFramework/Scripts/Editor/Inspector/ReferencePoolComponentInspector.cs
  54. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/ReferencePoolComponentInspector.cs.meta
  55. 397 0
      Assets/GameFramework/Scripts/Editor/Inspector/ResourceComponentInspector.cs
  56. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/ResourceComponentInspector.cs.meta
  57. 75 0
      Assets/GameFramework/Scripts/Editor/Inspector/SceneComponentInspector.cs
  58. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/SceneComponentInspector.cs.meta
  59. 81 0
      Assets/GameFramework/Scripts/Editor/Inspector/SettingComponentInspector.cs
  60. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/SettingComponentInspector.cs.meta
  61. 87 0
      Assets/GameFramework/Scripts/Editor/Inspector/SoundComponentInspector.cs
  62. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/SoundComponentInspector.cs.meta
  63. 154 0
      Assets/GameFramework/Scripts/Editor/Inspector/UIComponentInspector.cs
  64. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/UIComponentInspector.cs.meta
  65. 141 0
      Assets/GameFramework/Scripts/Editor/Inspector/WebRequestComponentInspector.cs
  66. 11 0
      Assets/GameFramework/Scripts/Editor/Inspector/WebRequestComponentInspector.cs.meta
  67. 8 0
      Assets/GameFramework/Scripts/Editor/Misc.meta
  68. 133 0
      Assets/GameFramework/Scripts/Editor/Misc/BuildSettings.cs
  69. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/BuildSettings.cs.meta
  70. 16 0
      Assets/GameFramework/Scripts/Editor/Misc/BuildSettingsConfigPathAttribute.cs
  71. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/BuildSettingsConfigPathAttribute.cs.meta
  72. 18 0
      Assets/GameFramework/Scripts/Editor/Misc/ConfigPathAttribute.cs
  73. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/ConfigPathAttribute.cs.meta
  74. 35 0
      Assets/GameFramework/Scripts/Editor/Misc/Help.cs
  75. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/Help.cs.meta
  76. 97 0
      Assets/GameFramework/Scripts/Editor/Misc/HelperInfo.cs
  77. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/HelperInfo.cs.meta
  78. 122 0
      Assets/GameFramework/Scripts/Editor/Misc/LogRedirection.cs
  79. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/LogRedirection.cs.meta
  80. 179 0
      Assets/GameFramework/Scripts/Editor/Misc/LogScriptingDefineSymbols.cs
  81. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/LogScriptingDefineSymbols.cs.meta
  82. 91 0
      Assets/GameFramework/Scripts/Editor/Misc/OpenFolder.cs
  83. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/OpenFolder.cs.meta
  84. 157 0
      Assets/GameFramework/Scripts/Editor/Misc/ScriptingDefineSymbols.cs
  85. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/ScriptingDefineSymbols.cs.meta
  86. 127 0
      Assets/GameFramework/Scripts/Editor/Misc/Type.cs
  87. 11 0
      Assets/GameFramework/Scripts/Editor/Misc/Type.cs.meta
  88. 8 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer.meta
  89. 21 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/AssetsOrder.cs
  90. 11 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/AssetsOrder.cs.meta
  91. 96 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/DependencyData.cs
  92. 11 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/DependencyData.cs.meta
  93. 533 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzer.cs
  94. 11 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzer.cs.meta
  95. 76 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.CircularDependencyChecker.cs
  96. 11 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.CircularDependencyChecker.cs.meta
  97. 43 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.Stamp.cs
  98. 11 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.Stamp.cs.meta
  99. 324 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.cs
  100. 0 0
      Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.cs.meta

+ 9 - 0
.gitignore

@@ -0,0 +1,9 @@
+/Library
+/Logs
+/obj
+/Temp
+/Packages
+/.vs
+/*.vsconfig
+/*.csproj
+/*.sln

+ 8 - 0
Assets/GameFramework.meta

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

ファイルの差分が大きいため隠しています
+ 1064 - 0
Assets/GameFramework/GameFramework.prefab


+ 7 - 0
Assets/GameFramework/GameFramework.prefab.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: adb3eb1c35fcff14f89fba7b05c9d71c
+NativeFormatImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 21 - 0
Assets/GameFramework/LICENSE.md

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2013-2021 Jiang Yin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 7 - 0
Assets/GameFramework/LICENSE.md.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 198d8d1073615c84e88742210e6749d3
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/GameFramework/Libraries.meta

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

BIN
Assets/GameFramework/Libraries/GameFramework.dll


+ 22 - 0
Assets/GameFramework/Libraries/GameFramework.dll.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: 535b3169b00c6744aaef208fc713a195
+PluginImporter:
+  serializedVersion: 1
+  iconMap: {}
+  executionOrder: {}
+  isPreloaded: 0
+  platformData:
+    Any:
+      enabled: 1
+      settings: {}
+    Editor:
+      enabled: 0
+      settings:
+        DefaultValueInitialized: true
+    WindowsStoreApps:
+      enabled: 0
+      settings:
+        CPU: AnyCPU
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

ファイルの差分が大きいため隠しています
+ 23646 - 0
Assets/GameFramework/Libraries/GameFramework.xml


+ 7 - 0
Assets/GameFramework/Libraries/GameFramework.xml.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 46cbe7b3d65fad24ba53d57c6bca3740
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/GameFramework/Libraries/ICSharpCode.SharpZipLib.dll


+ 22 - 0
Assets/GameFramework/Libraries/ICSharpCode.SharpZipLib.dll.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: bc9c7e2be608ffb4f900d02be002262e
+PluginImporter:
+  serializedVersion: 1
+  iconMap: {}
+  executionOrder: {}
+  isPreloaded: 0
+  platformData:
+    Any:
+      enabled: 1
+      settings: {}
+    Editor:
+      enabled: 0
+      settings:
+        DefaultValueInitialized: true
+    WindowsStoreApps:
+      enabled: 0
+      settings:
+        CPU: AnyCPU
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 41 - 0
Assets/GameFramework/Libraries/link.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<linker>
+  <assembly fullname="GameFramework" preserve="all" />
+  <assembly fullname="UnityGameFramework.Runtime" preserve="all" />
+</linker>
+
+<!--
+
+Unity 2017.3(不含)以前的版本,请删除以上内容,并使用以下内容。
+For versions before Unity 2017.3 (excluded), please delete the above contents and use the following contents.
+
+<?xml version="1.0" encoding="UTF-8"?>
+<linker>
+  <assembly fullname="GameFramework">
+    <namespace fullname="GameFramework" preserve="all" />
+    <namespace fullname="GameFramework.Config" preserve="all" />
+    <namespace fullname="GameFramework.DataNode" preserve="all" />
+    <namespace fullname="GameFramework.DataTable" preserve="all" />
+    <namespace fullname="GameFramework.Debugger" preserve="all" />
+    <namespace fullname="GameFramework.Download" preserve="all" />
+    <namespace fullname="GameFramework.Entity" preserve="all" />
+    <namespace fullname="GameFramework.Event" preserve="all" />
+    <namespace fullname="GameFramework.FileSystem" preserve="all" />
+    <namespace fullname="GameFramework.Fsm" preserve="all" />
+    <namespace fullname="GameFramework.Localization" preserve="all" />
+    <namespace fullname="GameFramework.Network" preserve="all" />
+    <namespace fullname="GameFramework.ObjectPool" preserve="all" />
+    <namespace fullname="GameFramework.Procedure" preserve="all" />
+    <namespace fullname="GameFramework.Resource" preserve="all" />
+    <namespace fullname="GameFramework.Scene" preserve="all" />
+    <namespace fullname="GameFramework.Setting" preserve="all" />
+    <namespace fullname="GameFramework.Sound" preserve="all" />
+    <namespace fullname="GameFramework.UI" preserve="all" />
+    <namespace fullname="GameFramework.WebRequest" preserve="all" />
+  </assembly>
+  <assembly fullname="Assembly-CSharp">
+    <namespace fullname="UnityGameFramework.Runtime" preserve="all" />
+  </assembly>
+</linker>
+
+-->

+ 7 - 0
Assets/GameFramework/Libraries/link.xml.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 843cc923151568443b86e022f705a263
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

ファイルの差分が大きいため隠しています
+ 101 - 0
Assets/GameFramework/README.md


+ 7 - 0
Assets/GameFramework/README.md.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8262007ebaf1af74b8f1850468e2bd7d
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/GameFramework/Scripts.meta

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

+ 8 - 0
Assets/GameFramework/Scripts/Editor.meta

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

+ 8 - 0
Assets/GameFramework/Scripts/Editor/Inspector.meta

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

+ 290 - 0
Assets/GameFramework/Scripts/Editor/Inspector/BaseComponentInspector.cs

@@ -0,0 +1,290 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(BaseComponent))]
+    internal sealed class BaseComponentInspector : GameFrameworkInspector
+    {
+        private const string NoneOptionName = "<None>";
+        private static readonly float[] GameSpeed = new float[] { 0f, 0.01f, 0.1f, 0.25f, 0.5f, 1f, 1.5f, 2f, 4f, 8f };
+        private static readonly string[] GameSpeedForDisplay = new string[] { "0x", "0.01x", "0.1x", "0.25x", "0.5x", "1x", "1.5x", "2x", "4x", "8x" };
+
+        private SerializedProperty m_EditorResourceMode = null;
+        private SerializedProperty m_EditorLanguage = null;
+        private SerializedProperty m_VersionHelperTypeName = null;
+        private SerializedProperty m_LogHelperTypeName = null;
+        private SerializedProperty m_CompressionHelperTypeName = null;
+        private SerializedProperty m_JsonHelperTypeName = null;
+        private SerializedProperty m_FrameRate = null;
+        private SerializedProperty m_GameSpeed = null;
+        private SerializedProperty m_RunInBackground = null;
+        private SerializedProperty m_NeverSleep = null;
+
+        private string[] m_VersionHelperTypeNames = null;
+        private int m_VersionHelperTypeNameIndex = 0;
+        private string[] m_LogHelperTypeNames = null;
+        private int m_LogHelperTypeNameIndex = 0;
+        private string[] m_CompressionHelperTypeNames = null;
+        private int m_CompressionHelperTypeNameIndex = 0;
+        private string[] m_JsonHelperTypeNames = null;
+        private int m_JsonHelperTypeNameIndex = 0;
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            BaseComponent t = (BaseComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                m_EditorResourceMode.boolValue = EditorGUILayout.BeginToggleGroup("Editor Resource Mode", m_EditorResourceMode.boolValue);
+                {
+                    EditorGUILayout.HelpBox("Editor resource mode option is only for editor mode. Game Framework will use editor resource files, which you should validate first.", MessageType.Warning);
+                    EditorGUILayout.PropertyField(m_EditorLanguage);
+                    EditorGUILayout.HelpBox("Editor language option is only use for localization test in editor mode.", MessageType.Info);
+                }
+                EditorGUILayout.EndToggleGroup();
+
+                EditorGUILayout.BeginVertical("box");
+                {
+                    EditorGUILayout.LabelField("Global Helpers", EditorStyles.boldLabel);
+
+                    int versionHelperSelectedIndex = EditorGUILayout.Popup("Version Helper", m_VersionHelperTypeNameIndex, m_VersionHelperTypeNames);
+                    if (versionHelperSelectedIndex != m_VersionHelperTypeNameIndex)
+                    {
+                        m_VersionHelperTypeNameIndex = versionHelperSelectedIndex;
+                        m_VersionHelperTypeName.stringValue = versionHelperSelectedIndex <= 0 ? null : m_VersionHelperTypeNames[versionHelperSelectedIndex];
+                    }
+
+                    int logHelperSelectedIndex = EditorGUILayout.Popup("Log Helper", m_LogHelperTypeNameIndex, m_LogHelperTypeNames);
+                    if (logHelperSelectedIndex != m_LogHelperTypeNameIndex)
+                    {
+                        m_LogHelperTypeNameIndex = logHelperSelectedIndex;
+                        m_LogHelperTypeName.stringValue = logHelperSelectedIndex <= 0 ? null : m_LogHelperTypeNames[logHelperSelectedIndex];
+                    }
+
+                    int compressionHelperSelectedIndex = EditorGUILayout.Popup("Compression Helper", m_CompressionHelperTypeNameIndex, m_CompressionHelperTypeNames);
+                    if (compressionHelperSelectedIndex != m_CompressionHelperTypeNameIndex)
+                    {
+                        m_CompressionHelperTypeNameIndex = compressionHelperSelectedIndex;
+                        m_CompressionHelperTypeName.stringValue = compressionHelperSelectedIndex <= 0 ? null : m_CompressionHelperTypeNames[compressionHelperSelectedIndex];
+                    }
+
+                    int jsonHelperSelectedIndex = EditorGUILayout.Popup("JSON Helper", m_JsonHelperTypeNameIndex, m_JsonHelperTypeNames);
+                    if (jsonHelperSelectedIndex != m_JsonHelperTypeNameIndex)
+                    {
+                        m_JsonHelperTypeNameIndex = jsonHelperSelectedIndex;
+                        m_JsonHelperTypeName.stringValue = jsonHelperSelectedIndex <= 0 ? null : m_JsonHelperTypeNames[jsonHelperSelectedIndex];
+                    }
+                }
+                EditorGUILayout.EndVertical();
+            }
+            EditorGUI.EndDisabledGroup();
+
+            int frameRate = EditorGUILayout.IntSlider("Frame Rate", m_FrameRate.intValue, 1, 120);
+            if (frameRate != m_FrameRate.intValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.FrameRate = frameRate;
+                }
+                else
+                {
+                    m_FrameRate.intValue = frameRate;
+                }
+            }
+
+            EditorGUILayout.BeginVertical("box");
+            {
+                float gameSpeed = EditorGUILayout.Slider("Game Speed", m_GameSpeed.floatValue, 0f, 8f);
+                int selectedGameSpeed = GUILayout.SelectionGrid(GetSelectedGameSpeed(gameSpeed), GameSpeedForDisplay, 5);
+                if (selectedGameSpeed >= 0)
+                {
+                    gameSpeed = GetGameSpeed(selectedGameSpeed);
+                }
+
+                if (gameSpeed != m_GameSpeed.floatValue)
+                {
+                    if (EditorApplication.isPlaying)
+                    {
+                        t.GameSpeed = gameSpeed;
+                    }
+                    else
+                    {
+                        m_GameSpeed.floatValue = gameSpeed;
+                    }
+                }
+            }
+            EditorGUILayout.EndVertical();
+
+            bool runInBackground = EditorGUILayout.Toggle("Run in Background", m_RunInBackground.boolValue);
+            if (runInBackground != m_RunInBackground.boolValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.RunInBackground = runInBackground;
+                }
+                else
+                {
+                    m_RunInBackground.boolValue = runInBackground;
+                }
+            }
+
+            bool neverSleep = EditorGUILayout.Toggle("Never Sleep", m_NeverSleep.boolValue);
+            if (neverSleep != m_NeverSleep.boolValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.NeverSleep = neverSleep;
+                }
+                else
+                {
+                    m_NeverSleep.boolValue = neverSleep;
+                }
+            }
+
+            serializedObject.ApplyModifiedProperties();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_EditorResourceMode = serializedObject.FindProperty("m_EditorResourceMode");
+            m_EditorLanguage = serializedObject.FindProperty("m_EditorLanguage");
+            m_VersionHelperTypeName = serializedObject.FindProperty("m_VersionHelperTypeName");
+            m_LogHelperTypeName = serializedObject.FindProperty("m_LogHelperTypeName");
+            m_CompressionHelperTypeName = serializedObject.FindProperty("m_CompressionHelperTypeName");
+            m_JsonHelperTypeName = serializedObject.FindProperty("m_JsonHelperTypeName");
+            m_FrameRate = serializedObject.FindProperty("m_FrameRate");
+            m_GameSpeed = serializedObject.FindProperty("m_GameSpeed");
+            m_RunInBackground = serializedObject.FindProperty("m_RunInBackground");
+            m_NeverSleep = serializedObject.FindProperty("m_NeverSleep");
+
+            RefreshTypeNames();
+        }
+
+        private void RefreshTypeNames()
+        {
+            List<string> versionHelperTypeNames = new List<string>
+            {
+                NoneOptionName
+            };
+
+            versionHelperTypeNames.AddRange(Type.GetRuntimeTypeNames(typeof(Version.IVersionHelper)));
+            m_VersionHelperTypeNames = versionHelperTypeNames.ToArray();
+            m_VersionHelperTypeNameIndex = 0;
+            if (!string.IsNullOrEmpty(m_VersionHelperTypeName.stringValue))
+            {
+                m_VersionHelperTypeNameIndex = versionHelperTypeNames.IndexOf(m_VersionHelperTypeName.stringValue);
+                if (m_VersionHelperTypeNameIndex <= 0)
+                {
+                    m_VersionHelperTypeNameIndex = 0;
+                    m_VersionHelperTypeName.stringValue = null;
+                }
+            }
+
+            List<string> logHelperTypeNames = new List<string>
+            {
+                NoneOptionName
+            };
+
+            logHelperTypeNames.AddRange(Type.GetRuntimeTypeNames(typeof(GameFrameworkLog.ILogHelper)));
+            m_LogHelperTypeNames = logHelperTypeNames.ToArray();
+            m_LogHelperTypeNameIndex = 0;
+            if (!string.IsNullOrEmpty(m_LogHelperTypeName.stringValue))
+            {
+                m_LogHelperTypeNameIndex = logHelperTypeNames.IndexOf(m_LogHelperTypeName.stringValue);
+                if (m_LogHelperTypeNameIndex <= 0)
+                {
+                    m_LogHelperTypeNameIndex = 0;
+                    m_LogHelperTypeName.stringValue = null;
+                }
+            }
+
+            List<string> compressionHelperTypeNames = new List<string>
+            {
+                NoneOptionName
+            };
+
+            compressionHelperTypeNames.AddRange(Type.GetRuntimeTypeNames(typeof(Utility.Compression.ICompressionHelper)));
+            m_CompressionHelperTypeNames = compressionHelperTypeNames.ToArray();
+            m_CompressionHelperTypeNameIndex = 0;
+            if (!string.IsNullOrEmpty(m_CompressionHelperTypeName.stringValue))
+            {
+                m_CompressionHelperTypeNameIndex = compressionHelperTypeNames.IndexOf(m_CompressionHelperTypeName.stringValue);
+                if (m_CompressionHelperTypeNameIndex <= 0)
+                {
+                    m_CompressionHelperTypeNameIndex = 0;
+                    m_CompressionHelperTypeName.stringValue = null;
+                }
+            }
+
+            List<string> jsonHelperTypeNames = new List<string>
+            {
+                NoneOptionName
+            };
+
+            jsonHelperTypeNames.AddRange(Type.GetRuntimeTypeNames(typeof(Utility.Json.IJsonHelper)));
+            m_JsonHelperTypeNames = jsonHelperTypeNames.ToArray();
+            m_JsonHelperTypeNameIndex = 0;
+            if (!string.IsNullOrEmpty(m_JsonHelperTypeName.stringValue))
+            {
+                m_JsonHelperTypeNameIndex = jsonHelperTypeNames.IndexOf(m_JsonHelperTypeName.stringValue);
+                if (m_JsonHelperTypeNameIndex <= 0)
+                {
+                    m_JsonHelperTypeNameIndex = 0;
+                    m_JsonHelperTypeName.stringValue = null;
+                }
+            }
+
+            serializedObject.ApplyModifiedProperties();
+        }
+
+        private float GetGameSpeed(int selectedGameSpeed)
+        {
+            if (selectedGameSpeed < 0)
+            {
+                return GameSpeed[0];
+            }
+
+            if (selectedGameSpeed >= GameSpeed.Length)
+            {
+                return GameSpeed[GameSpeed.Length - 1];
+            }
+
+            return GameSpeed[selectedGameSpeed];
+        }
+
+        private int GetSelectedGameSpeed(float gameSpeed)
+        {
+            for (int i = 0; i < GameSpeed.Length; i++)
+            {
+                if (gameSpeed == GameSpeed[i])
+                {
+                    return i;
+                }
+            }
+
+            return -1;
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/BaseComponentInspector.cs.meta

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

+ 74 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ConfigComponentInspector.cs

@@ -0,0 +1,74 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(ConfigComponent))]
+    internal sealed class ConfigComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_EnableLoadConfigUpdateEvent = null;
+        private SerializedProperty m_EnableLoadConfigDependencyAssetEvent = null;
+        private SerializedProperty m_CachedBytesSize = null;
+
+        private HelperInfo<ConfigHelperBase> m_ConfigHelperInfo = new HelperInfo<ConfigHelperBase>("Config");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            ConfigComponent t = (ConfigComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_EnableLoadConfigUpdateEvent);
+                EditorGUILayout.PropertyField(m_EnableLoadConfigDependencyAssetEvent);
+                m_ConfigHelperInfo.Draw();
+                EditorGUILayout.PropertyField(m_CachedBytesSize);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Config Count", t.Count.ToString());
+                EditorGUILayout.LabelField("Cached Bytes Size", t.CachedBytesSize.ToString());
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_EnableLoadConfigUpdateEvent = serializedObject.FindProperty("m_EnableLoadConfigUpdateEvent");
+            m_EnableLoadConfigDependencyAssetEvent = serializedObject.FindProperty("m_EnableLoadConfigDependencyAssetEvent");
+            m_CachedBytesSize = serializedObject.FindProperty("m_CachedBytesSize");
+
+            m_ConfigHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_ConfigHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ConfigComponentInspector.cs.meta

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

+ 51 - 0
Assets/GameFramework/Scripts/Editor/Inspector/DataNodeComponentInspector.cs

@@ -0,0 +1,51 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework.DataNode;
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(DataNodeComponent))]
+    internal sealed class DataNodeComponentInspector : GameFrameworkInspector
+    {
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            if (!EditorApplication.isPlaying)
+            {
+                EditorGUILayout.HelpBox("Available during runtime only.", MessageType.Info);
+                return;
+            }
+
+            DataNodeComponent t = (DataNodeComponent)target;
+
+            if (IsPrefabInHierarchy(t.gameObject))
+            {
+                DrawDataNode(t.Root);
+            }
+
+            Repaint();
+        }
+
+        private void OnEnable()
+        {
+        }
+
+        private void DrawDataNode(IDataNode dataNode)
+        {
+            EditorGUILayout.LabelField(dataNode.FullName, dataNode.ToDataString());
+            IDataNode[] child = dataNode.GetAllChild();
+            foreach (IDataNode c in child)
+            {
+                DrawDataNode(c);
+            }
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/DataNodeComponentInspector.cs.meta

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

+ 87 - 0
Assets/GameFramework/Scripts/Editor/Inspector/DataTableComponentInspector.cs

@@ -0,0 +1,87 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using GameFramework.DataTable;
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(DataTableComponent))]
+    internal sealed class DataTableComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_EnableLoadDataTableUpdateEvent = null;
+        private SerializedProperty m_EnableLoadDataTableDependencyAssetEvent = null;
+        private SerializedProperty m_CachedBytesSize = null;
+
+        private HelperInfo<DataTableHelperBase> m_DataTableHelperInfo = new HelperInfo<DataTableHelperBase>("DataTable");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            DataTableComponent t = (DataTableComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_EnableLoadDataTableUpdateEvent);
+                EditorGUILayout.PropertyField(m_EnableLoadDataTableDependencyAssetEvent);
+                m_DataTableHelperInfo.Draw();
+                EditorGUILayout.PropertyField(m_CachedBytesSize);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Data Table Count", t.Count.ToString());
+                EditorGUILayout.LabelField("Cached Bytes Size", t.CachedBytesSize.ToString());
+
+                DataTableBase[] dataTables = t.GetAllDataTables();
+                foreach (DataTableBase dataTable in dataTables)
+                {
+                    DrawDataTable(dataTable);
+                }
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_EnableLoadDataTableUpdateEvent = serializedObject.FindProperty("m_EnableLoadDataTableUpdateEvent");
+            m_EnableLoadDataTableDependencyAssetEvent = serializedObject.FindProperty("m_EnableLoadDataTableDependencyAssetEvent");
+            m_CachedBytesSize = serializedObject.FindProperty("m_CachedBytesSize");
+
+            m_DataTableHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void DrawDataTable(DataTableBase dataTable)
+        {
+            EditorGUILayout.LabelField(dataTable.FullName, Utility.Text.Format("{0} Rows", dataTable.Count.ToString()));
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_DataTableHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/DataTableComponentInspector.cs.meta

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

+ 68 - 0
Assets/GameFramework/Scripts/Editor/Inspector/DebuggerComponentInspector.cs

@@ -0,0 +1,68 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(DebuggerComponent))]
+    internal sealed class DebuggerComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_Skin = null;
+        private SerializedProperty m_ActiveWindow = null;
+        private SerializedProperty m_ShowFullWindow = null;
+        private SerializedProperty m_ConsoleWindow = null;
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            DebuggerComponent t = (DebuggerComponent)target;
+
+            EditorGUILayout.PropertyField(m_Skin);
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                bool activeWindow = EditorGUILayout.Toggle("Active Window", t.ActiveWindow);
+                if (activeWindow != t.ActiveWindow)
+                {
+                    t.ActiveWindow = activeWindow;
+                }
+            }
+            else
+            {
+                EditorGUILayout.PropertyField(m_ActiveWindow);
+            }
+
+            EditorGUILayout.PropertyField(m_ShowFullWindow);
+
+            if (EditorApplication.isPlaying)
+            {
+                if (GUILayout.Button("Reset Layout"))
+                {
+                    t.ResetLayout();
+                }
+            }
+
+            EditorGUILayout.PropertyField(m_ConsoleWindow, true);
+
+            serializedObject.ApplyModifiedProperties();
+        }
+
+        private void OnEnable()
+        {
+            m_Skin = serializedObject.FindProperty("m_Skin");
+            m_ActiveWindow = serializedObject.FindProperty("m_ActiveWindow");
+            m_ShowFullWindow = serializedObject.FindProperty("m_ShowFullWindow");
+            m_ConsoleWindow = serializedObject.FindProperty("m_ConsoleWindow");
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/DebuggerComponentInspector.cs.meta

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

+ 156 - 0
Assets/GameFramework/Scripts/Editor/Inspector/DownloadComponentInspector.cs

@@ -0,0 +1,156 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System;
+using System.IO;
+using System.Text;
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(DownloadComponent))]
+    internal sealed class DownloadComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_InstanceRoot = null;
+        private SerializedProperty m_DownloadAgentHelperCount = null;
+        private SerializedProperty m_Timeout = null;
+        private SerializedProperty m_FlushSize = null;
+
+        private HelperInfo<DownloadAgentHelperBase> m_DownloadAgentHelperInfo = new HelperInfo<DownloadAgentHelperBase>("DownloadAgent");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            DownloadComponent t = (DownloadComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_InstanceRoot);
+                m_DownloadAgentHelperInfo.Draw();
+                m_DownloadAgentHelperCount.intValue = EditorGUILayout.IntSlider("Download Agent Helper Count", m_DownloadAgentHelperCount.intValue, 1, 16);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            float timeout = EditorGUILayout.Slider("Timeout", m_Timeout.floatValue, 0f, 120f);
+            if (timeout != m_Timeout.floatValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.Timeout = timeout;
+                }
+                else
+                {
+                    m_Timeout.floatValue = timeout;
+                }
+            }
+
+            int flushSize = EditorGUILayout.DelayedIntField("Flush Size", m_FlushSize.intValue);
+            if (flushSize != m_FlushSize.intValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.FlushSize = flushSize;
+                }
+                else
+                {
+                    m_FlushSize.intValue = flushSize;
+                }
+            }
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Paused", t.Paused.ToString());
+                EditorGUILayout.LabelField("Total Agent Count", t.TotalAgentCount.ToString());
+                EditorGUILayout.LabelField("Free Agent Count", t.FreeAgentCount.ToString());
+                EditorGUILayout.LabelField("Working Agent Count", t.WorkingAgentCount.ToString());
+                EditorGUILayout.LabelField("Waiting Agent Count", t.WaitingTaskCount.ToString());
+                EditorGUILayout.LabelField("Current Speed", t.CurrentSpeed.ToString());
+                EditorGUILayout.BeginVertical("box");
+                {
+                    TaskInfo[] downloadInfos = t.GetAllDownloadInfos();
+                    if (downloadInfos.Length > 0)
+                    {
+                        foreach (TaskInfo downloadInfo in downloadInfos)
+                        {
+                            DrawDownloadInfo(downloadInfo);
+                        }
+
+                        if (GUILayout.Button("Export CSV Data"))
+                        {
+                            string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, "Download Task Data.csv", string.Empty);
+                            if (!string.IsNullOrEmpty(exportFileName))
+                            {
+                                try
+                                {
+                                    int index = 0;
+                                    string[] data = new string[downloadInfos.Length + 1];
+                                    data[index++] = "Download Path,Serial Id,Tag,Priority,Status";
+                                    foreach (TaskInfo downloadInfo in downloadInfos)
+                                    {
+                                        data[index++] = Utility.Text.Format("{0},{1},{2},{3},{4}", downloadInfo.Description, downloadInfo.SerialId.ToString(), downloadInfo.Tag ?? string.Empty, downloadInfo.Priority.ToString(), downloadInfo.Status.ToString());
+                                    }
+
+                                    File.WriteAllLines(exportFileName, data, Encoding.UTF8);
+                                    Debug.Log(Utility.Text.Format("Export download task CSV data to '{0}' success.", exportFileName));
+                                }
+                                catch (Exception exception)
+                                {
+                                    Debug.LogError(Utility.Text.Format("Export download task CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception.ToString()));
+                                }
+                            }
+                        }
+                    }
+                    else
+                    {
+                        GUILayout.Label("Download Task is Empty ...");
+                    }
+                }
+                EditorGUILayout.EndVertical();
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_InstanceRoot = serializedObject.FindProperty("m_InstanceRoot");
+            m_DownloadAgentHelperCount = serializedObject.FindProperty("m_DownloadAgentHelperCount");
+            m_Timeout = serializedObject.FindProperty("m_Timeout");
+            m_FlushSize = serializedObject.FindProperty("m_FlushSize");
+
+            m_DownloadAgentHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void DrawDownloadInfo(TaskInfo downloadInfo)
+        {
+            EditorGUILayout.LabelField(downloadInfo.Description, Utility.Text.Format("[SerialId]{0} [Tag]{1} [Priority]{2} [Status]{3}", downloadInfo.SerialId.ToString(), downloadInfo.Tag ?? "<None>", downloadInfo.Priority.ToString(), downloadInfo.Status.ToString()));
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_DownloadAgentHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/DownloadComponentInspector.cs.meta

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

+ 52 - 0
Assets/GameFramework/Scripts/Editor/Inspector/EditorResourceComponentInspector.cs

@@ -0,0 +1,52 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(EditorResourceComponent))]
+    internal sealed class EditorResourceComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_EnableCachedAssets = null;
+        private SerializedProperty m_LoadAssetCountPerFrame = null;
+        private SerializedProperty m_MinLoadAssetRandomDelaySeconds = null;
+        private SerializedProperty m_MaxLoadAssetRandomDelaySeconds = null;
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            EditorResourceComponent t = (EditorResourceComponent)target;
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Load Waiting Asset Count", t.LoadWaitingAssetCount.ToString());
+            }
+
+            EditorGUILayout.PropertyField(m_EnableCachedAssets);
+            EditorGUILayout.PropertyField(m_LoadAssetCountPerFrame);
+            EditorGUILayout.PropertyField(m_MinLoadAssetRandomDelaySeconds);
+            EditorGUILayout.PropertyField(m_MaxLoadAssetRandomDelaySeconds);
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        private void OnEnable()
+        {
+            m_EnableCachedAssets = serializedObject.FindProperty("m_EnableCachedAssets");
+            m_LoadAssetCountPerFrame = serializedObject.FindProperty("m_LoadAssetCountPerFrame");
+            m_MinLoadAssetRandomDelaySeconds = serializedObject.FindProperty("m_MinLoadAssetRandomDelaySeconds");
+            m_MaxLoadAssetRandomDelaySeconds = serializedObject.FindProperty("m_MaxLoadAssetRandomDelaySeconds");
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/EditorResourceComponentInspector.cs.meta

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

+ 88 - 0
Assets/GameFramework/Scripts/Editor/Inspector/EntityComponentInspector.cs

@@ -0,0 +1,88 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using GameFramework.Entity;
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(EntityComponent))]
+    internal sealed class EntityComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_EnableShowEntityUpdateEvent = null;
+        private SerializedProperty m_EnableShowEntityDependencyAssetEvent = null;
+        private SerializedProperty m_InstanceRoot = null;
+        private SerializedProperty m_EntityGroups = null;
+
+        private HelperInfo<EntityHelperBase> m_EntityHelperInfo = new HelperInfo<EntityHelperBase>("Entity");
+        private HelperInfo<EntityGroupHelperBase> m_EntityGroupHelperInfo = new HelperInfo<EntityGroupHelperBase>("EntityGroup");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            EntityComponent t = (EntityComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_EnableShowEntityUpdateEvent);
+                EditorGUILayout.PropertyField(m_EnableShowEntityDependencyAssetEvent);
+                EditorGUILayout.PropertyField(m_InstanceRoot);
+                m_EntityHelperInfo.Draw();
+                m_EntityGroupHelperInfo.Draw();
+                EditorGUILayout.PropertyField(m_EntityGroups, true);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Entity Group Count", t.EntityGroupCount.ToString());
+                EditorGUILayout.LabelField("Entity Count (Total)", t.EntityCount.ToString());
+                IEntityGroup[] entityGroups = t.GetAllEntityGroups();
+                foreach (IEntityGroup entityGroup in entityGroups)
+                {
+                    EditorGUILayout.LabelField(Utility.Text.Format("Entity Count ({0})", entityGroup.Name), entityGroup.EntityCount.ToString());
+                }
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_EnableShowEntityUpdateEvent = serializedObject.FindProperty("m_EnableShowEntityUpdateEvent");
+            m_EnableShowEntityDependencyAssetEvent = serializedObject.FindProperty("m_EnableShowEntityDependencyAssetEvent");
+            m_InstanceRoot = serializedObject.FindProperty("m_InstanceRoot");
+            m_EntityGroups = serializedObject.FindProperty("m_EntityGroups");
+
+            m_EntityHelperInfo.Init(serializedObject);
+            m_EntityGroupHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_EntityHelperInfo.Refresh();
+            m_EntityGroupHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/EntityComponentInspector.cs.meta

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

+ 41 - 0
Assets/GameFramework/Scripts/Editor/Inspector/EventComponentInspector.cs

@@ -0,0 +1,41 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(EventComponent))]
+    internal sealed class EventComponentInspector : GameFrameworkInspector
+    {
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            if (!EditorApplication.isPlaying)
+            {
+                EditorGUILayout.HelpBox("Available during runtime only.", MessageType.Info);
+                return;
+            }
+
+            EventComponent t = (EventComponent)target;
+
+            if (IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Event Handler Count", t.EventHandlerCount.ToString());
+                EditorGUILayout.LabelField("Event Count", t.EventCount.ToString());
+            }
+
+            Repaint();
+        }
+
+        private void OnEnable()
+        {
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/EventComponentInspector.cs.meta

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

+ 73 - 0
Assets/GameFramework/Scripts/Editor/Inspector/FileSystemComponentInspector.cs

@@ -0,0 +1,73 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using GameFramework.FileSystem;
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(FileSystemComponent))]
+    internal sealed class FileSystemComponentInspector : GameFrameworkInspector
+    {
+        private HelperInfo<FileSystemHelperBase> m_FileSystemHelperInfo = new HelperInfo<FileSystemHelperBase>("FileSystem");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            FileSystemComponent t = (FileSystemComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                m_FileSystemHelperInfo.Draw();
+            }
+            EditorGUI.EndDisabledGroup();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("File System Count", t.Count.ToString());
+
+                IFileSystem[] fileSystems = t.GetAllFileSystems();
+                foreach (IFileSystem fileSystem in fileSystems)
+                {
+                    DrawFileSystem(fileSystem);
+                }
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_FileSystemHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_FileSystemHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+
+        private void DrawFileSystem(IFileSystem fileSystem)
+        {
+            EditorGUILayout.LabelField(fileSystem.FullPath, Utility.Text.Format("{0}, {1} / {2} Files", fileSystem.Access.ToString(), fileSystem.FileCount.ToString(), fileSystem.MaxFileCount.ToString()));
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/FileSystemComponentInspector.cs.meta

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

+ 53 - 0
Assets/GameFramework/Scripts/Editor/Inspector/FsmComponentInspector.cs

@@ -0,0 +1,53 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using GameFramework.Fsm;
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(FsmComponent))]
+    internal sealed class FsmComponentInspector : GameFrameworkInspector
+    {
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            if (!EditorApplication.isPlaying)
+            {
+                EditorGUILayout.HelpBox("Available during runtime only.", MessageType.Info);
+                return;
+            }
+
+            FsmComponent t = (FsmComponent)target;
+
+            if (IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("FSM Count", t.Count.ToString());
+
+                FsmBase[] fsms = t.GetAllFsms();
+                foreach (FsmBase fsm in fsms)
+                {
+                    DrawFsm(fsm);
+                }
+            }
+
+            Repaint();
+        }
+
+        private void OnEnable()
+        {
+        }
+
+        private void DrawFsm(FsmBase fsm)
+        {
+            EditorGUILayout.LabelField(fsm.FullName, fsm.IsRunning ? Utility.Text.Format("{0}, {1} s", fsm.CurrentStateName, fsm.CurrentStateTime.ToString("F1")) : (fsm.IsDestroyed ? "Destroyed" : "Not Running"));
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/FsmComponentInspector.cs.meta

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

+ 64 - 0
Assets/GameFramework/Scripts/Editor/Inspector/GameFrameworkInspector.cs

@@ -0,0 +1,64 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// 游戏框架 Inspector 抽象类。
+    /// </summary>
+    public abstract class GameFrameworkInspector : UnityEditor.Editor
+    {
+        private bool m_IsCompiling = false;
+
+        /// <summary>
+        /// 绘制事件。
+        /// </summary>
+        public override void OnInspectorGUI()
+        {
+            if (m_IsCompiling && !EditorApplication.isCompiling)
+            {
+                m_IsCompiling = false;
+                OnCompileComplete();
+            }
+            else if (!m_IsCompiling && EditorApplication.isCompiling)
+            {
+                m_IsCompiling = true;
+                OnCompileStart();
+            }
+        }
+
+        /// <summary>
+        /// 编译开始事件。
+        /// </summary>
+        protected virtual void OnCompileStart()
+        {
+        }
+
+        /// <summary>
+        /// 编译完成事件。
+        /// </summary>
+        protected virtual void OnCompileComplete()
+        {
+        }
+
+        protected bool IsPrefabInHierarchy(UnityEngine.Object obj)
+        {
+            if (obj == null)
+            {
+                return false;
+            }
+
+#if UNITY_2018_3_OR_NEWER
+            return PrefabUtility.GetPrefabAssetType(obj) != PrefabAssetType.Regular;
+#else
+            return PrefabUtility.GetPrefabType(obj) != PrefabType.Prefab;
+#endif
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/GameFrameworkInspector.cs.meta

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

+ 76 - 0
Assets/GameFramework/Scripts/Editor/Inspector/LocalizationComponentInspector.cs

@@ -0,0 +1,76 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(LocalizationComponent))]
+    internal sealed class LocalizationComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_EnableLoadDictionaryUpdateEvent = null;
+        private SerializedProperty m_EnableLoadDictionaryDependencyAssetEvent = null;
+        private SerializedProperty m_CachedBytesSize = null;
+
+        private HelperInfo<LocalizationHelperBase> m_LocalizationHelperInfo = new HelperInfo<LocalizationHelperBase>("Localization");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            LocalizationComponent t = (LocalizationComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_EnableLoadDictionaryUpdateEvent);
+                EditorGUILayout.PropertyField(m_EnableLoadDictionaryDependencyAssetEvent);
+                m_LocalizationHelperInfo.Draw();
+                EditorGUILayout.PropertyField(m_CachedBytesSize);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Language", t.Language.ToString());
+                EditorGUILayout.LabelField("System Language", t.SystemLanguage.ToString());
+                EditorGUILayout.LabelField("Dictionary Count", t.DictionaryCount.ToString());
+                EditorGUILayout.LabelField("Cached Bytes Size", t.CachedBytesSize.ToString());
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_EnableLoadDictionaryUpdateEvent = serializedObject.FindProperty("m_EnableLoadDictionaryUpdateEvent");
+            m_EnableLoadDictionaryDependencyAssetEvent = serializedObject.FindProperty("m_EnableLoadDictionaryDependencyAssetEvent");
+            m_CachedBytesSize = serializedObject.FindProperty("m_CachedBytesSize");
+
+            m_LocalizationHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_LocalizationHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/LocalizationComponentInspector.cs.meta

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

+ 76 - 0
Assets/GameFramework/Scripts/Editor/Inspector/NetworkComponentInspector.cs

@@ -0,0 +1,76 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using GameFramework.Network;
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(NetworkComponent))]
+    internal sealed class NetworkComponentInspector : GameFrameworkInspector
+    {
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            if (!EditorApplication.isPlaying)
+            {
+                EditorGUILayout.HelpBox("Available during runtime only.", MessageType.Info);
+                return;
+            }
+
+            NetworkComponent t = (NetworkComponent)target;
+
+            if (IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Network Channel Count", t.NetworkChannelCount.ToString());
+
+                INetworkChannel[] networkChannels = t.GetAllNetworkChannels();
+                foreach (INetworkChannel networkChannel in networkChannels)
+                {
+                    DrawNetworkChannel(networkChannel);
+                }
+            }
+
+            Repaint();
+        }
+
+        private void OnEnable()
+        {
+        }
+
+        private void DrawNetworkChannel(INetworkChannel networkChannel)
+        {
+            EditorGUILayout.BeginVertical("box");
+            {
+                EditorGUILayout.LabelField(networkChannel.Name, networkChannel.Connected ? "Connected" : "Disconnected");
+                EditorGUILayout.LabelField("Service Type", networkChannel.ServiceType.ToString());
+                EditorGUILayout.LabelField("Address Family", networkChannel.AddressFamily.ToString());
+                EditorGUILayout.LabelField("Local Address", networkChannel.Connected ? networkChannel.Socket.LocalEndPoint.ToString() : "Unavailable");
+                EditorGUILayout.LabelField("Remote Address", networkChannel.Connected ? networkChannel.Socket.RemoteEndPoint.ToString() : "Unavailable");
+                EditorGUILayout.LabelField("Send Packet", Utility.Text.Format("{0} / {1}", networkChannel.SendPacketCount.ToString(), networkChannel.SentPacketCount.ToString()));
+                EditorGUILayout.LabelField("Receive Packet", Utility.Text.Format("{0} / {1}", networkChannel.ReceivePacketCount.ToString(), networkChannel.ReceivedPacketCount.ToString()));
+                EditorGUILayout.LabelField("Miss Heart Beat Count", networkChannel.MissHeartBeatCount.ToString());
+                EditorGUILayout.LabelField("Heart Beat", Utility.Text.Format("{0} / {1}", networkChannel.HeartBeatElapseSeconds.ToString("F2"), networkChannel.HeartBeatInterval.ToString("F2")));
+                EditorGUI.BeginDisabledGroup(!networkChannel.Connected);
+                {
+                    if (GUILayout.Button("Disconnect"))
+                    {
+                        networkChannel.Close();
+                    }
+                }
+                EditorGUI.EndDisabledGroup();
+            }
+            EditorGUILayout.EndVertical();
+
+            EditorGUILayout.Separator();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/NetworkComponentInspector.cs.meta

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

+ 138 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ObjectPoolComponentInspector.cs

@@ -0,0 +1,138 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using GameFramework.ObjectPool;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(ObjectPoolComponent))]
+    internal sealed class ObjectPoolComponentInspector : GameFrameworkInspector
+    {
+        private readonly HashSet<string> m_OpenedItems = new HashSet<string>();
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            if (!EditorApplication.isPlaying)
+            {
+                EditorGUILayout.HelpBox("Available during runtime only.", MessageType.Info);
+                return;
+            }
+
+            ObjectPoolComponent t = (ObjectPoolComponent)target;
+
+            if (IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Object Pool Count", t.Count.ToString());
+
+                ObjectPoolBase[] objectPools = t.GetAllObjectPools(true);
+                foreach (ObjectPoolBase objectPool in objectPools)
+                {
+                    DrawObjectPool(objectPool);
+                }
+            }
+
+            Repaint();
+        }
+
+        private void OnEnable()
+        {
+        }
+
+        private void DrawObjectPool(ObjectPoolBase objectPool)
+        {
+            bool lastState = m_OpenedItems.Contains(objectPool.FullName);
+            bool currentState = EditorGUILayout.Foldout(lastState, objectPool.FullName);
+            if (currentState != lastState)
+            {
+                if (currentState)
+                {
+                    m_OpenedItems.Add(objectPool.FullName);
+                }
+                else
+                {
+                    m_OpenedItems.Remove(objectPool.FullName);
+                }
+            }
+
+            if (currentState)
+            {
+                EditorGUILayout.BeginVertical("box");
+                {
+                    EditorGUILayout.LabelField("Name", objectPool.Name);
+                    EditorGUILayout.LabelField("Type", objectPool.ObjectType.FullName);
+                    EditorGUILayout.LabelField("Auto Release Interval", objectPool.AutoReleaseInterval.ToString());
+                    EditorGUILayout.LabelField("Capacity", objectPool.Capacity.ToString());
+                    EditorGUILayout.LabelField("Used Count", objectPool.Count.ToString());
+                    EditorGUILayout.LabelField("Can Release Count", objectPool.CanReleaseCount.ToString());
+                    EditorGUILayout.LabelField("Expire Time", objectPool.ExpireTime.ToString());
+                    EditorGUILayout.LabelField("Priority", objectPool.Priority.ToString());
+                    ObjectInfo[] objectInfos = objectPool.GetAllObjectInfos();
+                    if (objectInfos.Length > 0)
+                    {
+                        EditorGUILayout.LabelField("Name", objectPool.AllowMultiSpawn ? "Locked\tCount\tFlag\tPriority\tLast Use Time" : "Locked\tIn Use\tFlag\tPriority\tLast Use Time");
+                        foreach (ObjectInfo objectInfo in objectInfos)
+                        {
+                            EditorGUILayout.LabelField(string.IsNullOrEmpty(objectInfo.Name) ? "<None>" : objectInfo.Name, Utility.Text.Format("{0}\t{1}\t{2}\t{3}\t{4}", objectInfo.Locked.ToString(), objectPool.AllowMultiSpawn ? objectInfo.SpawnCount.ToString() : objectInfo.IsInUse.ToString(), objectInfo.CustomCanReleaseFlag.ToString(), objectInfo.Priority.ToString(), objectInfo.LastUseTime.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")));
+                        }
+
+                        if (GUILayout.Button("Release"))
+                        {
+                            objectPool.Release();
+                        }
+
+                        if (GUILayout.Button("Release All Unused"))
+                        {
+                            objectPool.ReleaseAllUnused();
+                        }
+
+                        if (GUILayout.Button("Export CSV Data"))
+                        {
+                            string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, Utility.Text.Format("Object Pool Data - {0}.csv", objectPool.Name), string.Empty);
+                            if (!string.IsNullOrEmpty(exportFileName))
+                            {
+                                try
+                                {
+                                    int index = 0;
+                                    string[] data = new string[objectInfos.Length + 1];
+                                    data[index++] = Utility.Text.Format("Name,Locked,{0},Custom Can Release Flag,Priority,Last Use Time", objectPool.AllowMultiSpawn ? "Count" : "In Use");
+                                    foreach (ObjectInfo objectInfo in objectInfos)
+                                    {
+                                        data[index++] = Utility.Text.Format("{0},{1},{2},{3},{4},{5}", objectInfo.Name, objectInfo.Locked.ToString(), objectPool.AllowMultiSpawn ? objectInfo.SpawnCount.ToString() : objectInfo.IsInUse.ToString(), objectInfo.CustomCanReleaseFlag.ToString(), objectInfo.Priority.ToString(), objectInfo.LastUseTime.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"));
+                                    }
+
+                                    File.WriteAllLines(exportFileName, data, Encoding.UTF8);
+                                    Debug.Log(Utility.Text.Format("Export object pool CSV data to '{0}' success.", exportFileName));
+                                }
+                                catch (Exception exception)
+                                {
+                                    Debug.LogError(Utility.Text.Format("Export object pool CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception.ToString()));
+                                }
+                            }
+                        }
+                    }
+                    else
+                    {
+                        GUILayout.Label("Object Pool is Empty ...");
+                    }
+                }
+                EditorGUILayout.EndVertical();
+
+                EditorGUILayout.Separator();
+            }
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ObjectPoolComponentInspector.cs.meta

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

+ 172 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ProcedureComponentInspector.cs

@@ -0,0 +1,172 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework.Procedure;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(ProcedureComponent))]
+    internal sealed class ProcedureComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_AvailableProcedureTypeNames = null;
+        private SerializedProperty m_EntranceProcedureTypeName = null;
+
+        private string[] m_ProcedureTypeNames = null;
+        private List<string> m_CurrentAvailableProcedureTypeNames = null;
+        private int m_EntranceProcedureIndex = -1;
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            ProcedureComponent t = (ProcedureComponent)target;
+
+            if (string.IsNullOrEmpty(m_EntranceProcedureTypeName.stringValue))
+            {
+                EditorGUILayout.HelpBox("Entrance procedure is invalid.", MessageType.Error);
+            }
+            else if (EditorApplication.isPlaying)
+            {
+                EditorGUILayout.LabelField("Current Procedure", t.CurrentProcedure == null ? "None" : t.CurrentProcedure.GetType().ToString());
+            }
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                GUILayout.Label("Available Procedures", EditorStyles.boldLabel);
+                if (m_ProcedureTypeNames.Length > 0)
+                {
+                    EditorGUILayout.BeginVertical("box");
+                    {
+                        foreach (string procedureTypeName in m_ProcedureTypeNames)
+                        {
+                            bool selected = m_CurrentAvailableProcedureTypeNames.Contains(procedureTypeName);
+                            if (selected != EditorGUILayout.ToggleLeft(procedureTypeName, selected))
+                            {
+                                if (!selected)
+                                {
+                                    m_CurrentAvailableProcedureTypeNames.Add(procedureTypeName);
+                                    WriteAvailableProcedureTypeNames();
+                                }
+                                else if (procedureTypeName != m_EntranceProcedureTypeName.stringValue)
+                                {
+                                    m_CurrentAvailableProcedureTypeNames.Remove(procedureTypeName);
+                                    WriteAvailableProcedureTypeNames();
+                                }
+                            }
+                        }
+                    }
+                    EditorGUILayout.EndVertical();
+                }
+                else
+                {
+                    EditorGUILayout.HelpBox("There is no available procedure.", MessageType.Warning);
+                }
+
+                if (m_CurrentAvailableProcedureTypeNames.Count > 0)
+                {
+                    EditorGUILayout.Separator();
+
+                    int selectedIndex = EditorGUILayout.Popup("Entrance Procedure", m_EntranceProcedureIndex, m_CurrentAvailableProcedureTypeNames.ToArray());
+                    if (selectedIndex != m_EntranceProcedureIndex)
+                    {
+                        m_EntranceProcedureIndex = selectedIndex;
+                        m_EntranceProcedureTypeName.stringValue = m_CurrentAvailableProcedureTypeNames[selectedIndex];
+                    }
+                }
+                else
+                {
+                    EditorGUILayout.HelpBox("Select available procedures first.", MessageType.Info);
+                }
+            }
+            EditorGUI.EndDisabledGroup();
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_AvailableProcedureTypeNames = serializedObject.FindProperty("m_AvailableProcedureTypeNames");
+            m_EntranceProcedureTypeName = serializedObject.FindProperty("m_EntranceProcedureTypeName");
+
+            RefreshTypeNames();
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_ProcedureTypeNames = Type.GetRuntimeTypeNames(typeof(ProcedureBase));
+            ReadAvailableProcedureTypeNames();
+            int oldCount = m_CurrentAvailableProcedureTypeNames.Count;
+            m_CurrentAvailableProcedureTypeNames = m_CurrentAvailableProcedureTypeNames.Where(x => m_ProcedureTypeNames.Contains(x)).ToList();
+            if (m_CurrentAvailableProcedureTypeNames.Count != oldCount)
+            {
+                WriteAvailableProcedureTypeNames();
+            }
+            else if (!string.IsNullOrEmpty(m_EntranceProcedureTypeName.stringValue))
+            {
+                m_EntranceProcedureIndex = m_CurrentAvailableProcedureTypeNames.IndexOf(m_EntranceProcedureTypeName.stringValue);
+                if (m_EntranceProcedureIndex < 0)
+                {
+                    m_EntranceProcedureTypeName.stringValue = null;
+                }
+            }
+
+            serializedObject.ApplyModifiedProperties();
+        }
+
+        private void ReadAvailableProcedureTypeNames()
+        {
+            m_CurrentAvailableProcedureTypeNames = new List<string>();
+            int count = m_AvailableProcedureTypeNames.arraySize;
+            for (int i = 0; i < count; i++)
+            {
+                m_CurrentAvailableProcedureTypeNames.Add(m_AvailableProcedureTypeNames.GetArrayElementAtIndex(i).stringValue);
+            }
+        }
+
+        private void WriteAvailableProcedureTypeNames()
+        {
+            m_AvailableProcedureTypeNames.ClearArray();
+            if (m_CurrentAvailableProcedureTypeNames == null)
+            {
+                return;
+            }
+
+            m_CurrentAvailableProcedureTypeNames.Sort();
+            int count = m_CurrentAvailableProcedureTypeNames.Count;
+            for (int i = 0; i < count; i++)
+            {
+                m_AvailableProcedureTypeNames.InsertArrayElementAtIndex(i);
+                m_AvailableProcedureTypeNames.GetArrayElementAtIndex(i).stringValue = m_CurrentAvailableProcedureTypeNames[i];
+            }
+
+            if (!string.IsNullOrEmpty(m_EntranceProcedureTypeName.stringValue))
+            {
+                m_EntranceProcedureIndex = m_CurrentAvailableProcedureTypeNames.IndexOf(m_EntranceProcedureTypeName.stringValue);
+                if (m_EntranceProcedureIndex < 0)
+                {
+                    m_EntranceProcedureTypeName.stringValue = null;
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ProcedureComponentInspector.cs.meta

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

+ 152 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ReferencePoolComponentInspector.cs

@@ -0,0 +1,152 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(ReferencePoolComponent))]
+    internal sealed class ReferencePoolComponentInspector : GameFrameworkInspector
+    {
+        private readonly Dictionary<string, List<ReferencePoolInfo>> m_ReferencePoolInfos = new Dictionary<string, List<ReferencePoolInfo>>(StringComparer.Ordinal);
+        private readonly HashSet<string> m_OpenedItems = new HashSet<string>();
+
+        private SerializedProperty m_EnableStrictCheck = null;
+
+        private bool m_ShowFullClassName = false;
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            ReferencePoolComponent t = (ReferencePoolComponent)target;
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                bool enableStrictCheck = EditorGUILayout.Toggle("Enable Strict Check", t.EnableStrictCheck);
+                if (enableStrictCheck != t.EnableStrictCheck)
+                {
+                    t.EnableStrictCheck = enableStrictCheck;
+                }
+
+                EditorGUILayout.LabelField("Reference Pool Count", ReferencePool.Count.ToString());
+                m_ShowFullClassName = EditorGUILayout.Toggle("Show Full Class Name", m_ShowFullClassName);
+                m_ReferencePoolInfos.Clear();
+                ReferencePoolInfo[] referencePoolInfos = ReferencePool.GetAllReferencePoolInfos();
+                foreach (ReferencePoolInfo referencePoolInfo in referencePoolInfos)
+                {
+                    string assemblyName = referencePoolInfo.Type.Assembly.GetName().Name;
+                    List<ReferencePoolInfo> results = null;
+                    if (!m_ReferencePoolInfos.TryGetValue(assemblyName, out results))
+                    {
+                        results = new List<ReferencePoolInfo>();
+                        m_ReferencePoolInfos.Add(assemblyName, results);
+                    }
+
+                    results.Add(referencePoolInfo);
+                }
+
+                foreach (KeyValuePair<string, List<ReferencePoolInfo>> assemblyReferencePoolInfo in m_ReferencePoolInfos)
+                {
+                    bool lastState = m_OpenedItems.Contains(assemblyReferencePoolInfo.Key);
+                    bool currentState = EditorGUILayout.Foldout(lastState, assemblyReferencePoolInfo.Key);
+                    if (currentState != lastState)
+                    {
+                        if (currentState)
+                        {
+                            m_OpenedItems.Add(assemblyReferencePoolInfo.Key);
+                        }
+                        else
+                        {
+                            m_OpenedItems.Remove(assemblyReferencePoolInfo.Key);
+                        }
+                    }
+
+                    if (currentState)
+                    {
+                        EditorGUILayout.BeginVertical("box");
+                        {
+                            EditorGUILayout.LabelField(m_ShowFullClassName ? "Full Class Name" : "Class Name", "Unused\tUsing\tAcquire\tRelease\tAdd\tRemove");
+                            assemblyReferencePoolInfo.Value.Sort(Comparison);
+                            foreach (ReferencePoolInfo referencePoolInfo in assemblyReferencePoolInfo.Value)
+                            {
+                                DrawReferencePoolInfo(referencePoolInfo);
+                            }
+
+                            if (GUILayout.Button("Export CSV Data"))
+                            {
+                                string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, Utility.Text.Format("Reference Pool Data - {0}.csv", assemblyReferencePoolInfo.Key), string.Empty);
+                                if (!string.IsNullOrEmpty(exportFileName))
+                                {
+                                    try
+                                    {
+                                        int index = 0;
+                                        string[] data = new string[assemblyReferencePoolInfo.Value.Count + 1];
+                                        data[index++] = "Class Name,Full Class Name,Unused,Using,Acquire,Release,Add,Remove";
+                                        foreach (ReferencePoolInfo referencePoolInfo in assemblyReferencePoolInfo.Value)
+                                        {
+                                            data[index++] = Utility.Text.Format("{0},{1},{2},{3},{4},{5},{6},{7}", referencePoolInfo.Type.Name, referencePoolInfo.Type.FullName, referencePoolInfo.UnusedReferenceCount.ToString(), referencePoolInfo.UsingReferenceCount.ToString(), referencePoolInfo.AcquireReferenceCount.ToString(), referencePoolInfo.ReleaseReferenceCount.ToString(), referencePoolInfo.AddReferenceCount.ToString(), referencePoolInfo.RemoveReferenceCount.ToString());
+                                        }
+
+                                        File.WriteAllLines(exportFileName, data, Encoding.UTF8);
+                                        Debug.Log(Utility.Text.Format("Export reference pool CSV data to '{0}' success.", exportFileName));
+                                    }
+                                    catch (Exception exception)
+                                    {
+                                        Debug.LogError(Utility.Text.Format("Export reference pool CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception.ToString()));
+                                    }
+                                }
+                            }
+                        }
+                        EditorGUILayout.EndVertical();
+
+                        EditorGUILayout.Separator();
+                    }
+                }
+            }
+            else
+            {
+                EditorGUILayout.PropertyField(m_EnableStrictCheck);
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        private void OnEnable()
+        {
+            m_EnableStrictCheck = serializedObject.FindProperty("m_EnableStrictCheck");
+        }
+
+        private void DrawReferencePoolInfo(ReferencePoolInfo referencePoolInfo)
+        {
+            EditorGUILayout.LabelField(m_ShowFullClassName ? referencePoolInfo.Type.FullName : referencePoolInfo.Type.Name, Utility.Text.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}", referencePoolInfo.UnusedReferenceCount.ToString(), referencePoolInfo.UsingReferenceCount.ToString(), referencePoolInfo.AcquireReferenceCount.ToString(), referencePoolInfo.ReleaseReferenceCount.ToString(), referencePoolInfo.AddReferenceCount.ToString(), referencePoolInfo.RemoveReferenceCount.ToString()));
+        }
+
+        private int Comparison(ReferencePoolInfo a, ReferencePoolInfo b)
+        {
+            if (m_ShowFullClassName)
+            {
+                return a.Type.FullName.CompareTo(b.Type.FullName);
+            }
+            else
+            {
+                return a.Type.Name.CompareTo(b.Type.Name);
+            }
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ReferencePoolComponentInspector.cs.meta

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

+ 397 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ResourceComponentInspector.cs

@@ -0,0 +1,397 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(ResourceComponent))]
+    internal sealed class ResourceComponentInspector : GameFrameworkInspector
+    {
+        private static readonly string[] ResourceModeNames = new string[] { "Package", "Updatable", "Updatable While Playing" };
+
+        private SerializedProperty m_ResourceMode = null;
+        private SerializedProperty m_ReadWritePathType = null;
+        private SerializedProperty m_MinUnloadUnusedAssetsInterval = null;
+        private SerializedProperty m_MaxUnloadUnusedAssetsInterval = null;
+        private SerializedProperty m_AssetAutoReleaseInterval = null;
+        private SerializedProperty m_AssetCapacity = null;
+        private SerializedProperty m_AssetExpireTime = null;
+        private SerializedProperty m_AssetPriority = null;
+        private SerializedProperty m_ResourceAutoReleaseInterval = null;
+        private SerializedProperty m_ResourceCapacity = null;
+        private SerializedProperty m_ResourceExpireTime = null;
+        private SerializedProperty m_ResourcePriority = null;
+        private SerializedProperty m_UpdatePrefixUri = null;
+        private SerializedProperty m_GenerateReadWriteVersionListLength = null;
+        private SerializedProperty m_UpdateRetryCount = null;
+        private SerializedProperty m_InstanceRoot = null;
+        private SerializedProperty m_LoadResourceAgentHelperCount = null;
+
+        private FieldInfo m_EditorResourceModeFieldInfo = null;
+
+        private int m_ResourceModeIndex = 0;
+        private HelperInfo<ResourceHelperBase> m_ResourceHelperInfo = new HelperInfo<ResourceHelperBase>("Resource");
+        private HelperInfo<LoadResourceAgentHelperBase> m_LoadResourceAgentHelperInfo = new HelperInfo<LoadResourceAgentHelperBase>("LoadResourceAgent");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            ResourceComponent t = (ResourceComponent)target;
+
+            bool isEditorResourceMode = (bool)m_EditorResourceModeFieldInfo.GetValue(target);
+
+            if (isEditorResourceMode)
+            {
+                EditorGUILayout.HelpBox("Editor resource mode is enabled. Some options are disabled.", MessageType.Warning);
+            }
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+                {
+                    EditorGUILayout.EnumPopup("Resource Mode", t.ResourceMode);
+                }
+                else
+                {
+                    int selectedIndex = EditorGUILayout.Popup("Resource Mode", m_ResourceModeIndex, ResourceModeNames);
+                    if (selectedIndex != m_ResourceModeIndex)
+                    {
+                        m_ResourceModeIndex = selectedIndex;
+                        m_ResourceMode.enumValueIndex = selectedIndex + 1;
+                    }
+                }
+
+                m_ReadWritePathType.enumValueIndex = (int)(ReadWritePathType)EditorGUILayout.EnumPopup("Read-Write Path Type", t.ReadWritePathType);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            float minUnloadUnusedAssetsInterval = EditorGUILayout.Slider("Min Unload Unused Assets Interval", m_MinUnloadUnusedAssetsInterval.floatValue, 0f, 3600f);
+            if (minUnloadUnusedAssetsInterval != m_MinUnloadUnusedAssetsInterval.floatValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.MinUnloadUnusedAssetsInterval = minUnloadUnusedAssetsInterval;
+                }
+                else
+                {
+                    m_MinUnloadUnusedAssetsInterval.floatValue = minUnloadUnusedAssetsInterval;
+                }
+            }
+
+            float maxUnloadUnusedAssetsInterval = EditorGUILayout.Slider("Max Unload Unused Assets Interval", m_MaxUnloadUnusedAssetsInterval.floatValue, 0f, 3600f);
+            if (maxUnloadUnusedAssetsInterval != m_MaxUnloadUnusedAssetsInterval.floatValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.MaxUnloadUnusedAssetsInterval = maxUnloadUnusedAssetsInterval;
+                }
+                else
+                {
+                    m_MaxUnloadUnusedAssetsInterval.floatValue = maxUnloadUnusedAssetsInterval;
+                }
+            }
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying && isEditorResourceMode);
+            {
+                float assetAutoReleaseInterval = EditorGUILayout.DelayedFloatField("Asset Auto Release Interval", m_AssetAutoReleaseInterval.floatValue);
+                if (assetAutoReleaseInterval != m_AssetAutoReleaseInterval.floatValue)
+                {
+                    if (EditorApplication.isPlaying)
+                    {
+                        t.AssetAutoReleaseInterval = assetAutoReleaseInterval;
+                    }
+                    else
+                    {
+                        m_AssetAutoReleaseInterval.floatValue = assetAutoReleaseInterval;
+                    }
+                }
+
+                int assetCapacity = EditorGUILayout.DelayedIntField("Asset Capacity", m_AssetCapacity.intValue);
+                if (assetCapacity != m_AssetCapacity.intValue)
+                {
+                    if (EditorApplication.isPlaying)
+                    {
+                        t.AssetCapacity = assetCapacity;
+                    }
+                    else
+                    {
+                        m_AssetCapacity.intValue = assetCapacity;
+                    }
+                }
+
+                float assetExpireTime = EditorGUILayout.DelayedFloatField("Asset Expire Time", m_AssetExpireTime.floatValue);
+                if (assetExpireTime != m_AssetExpireTime.floatValue)
+                {
+                    if (EditorApplication.isPlaying)
+                    {
+                        t.AssetExpireTime = assetExpireTime;
+                    }
+                    else
+                    {
+                        m_AssetExpireTime.floatValue = assetExpireTime;
+                    }
+                }
+
+                int assetPriority = EditorGUILayout.DelayedIntField("Asset Priority", m_AssetPriority.intValue);
+                if (assetPriority != m_AssetPriority.intValue)
+                {
+                    if (EditorApplication.isPlaying)
+                    {
+                        t.AssetPriority = assetPriority;
+                    }
+                    else
+                    {
+                        m_AssetPriority.intValue = assetPriority;
+                    }
+                }
+
+                float resourceAutoReleaseInterval = EditorGUILayout.DelayedFloatField("Resource Auto Release Interval", m_ResourceAutoReleaseInterval.floatValue);
+                if (resourceAutoReleaseInterval != m_ResourceAutoReleaseInterval.floatValue)
+                {
+                    if (EditorApplication.isPlaying)
+                    {
+                        t.ResourceAutoReleaseInterval = resourceAutoReleaseInterval;
+                    }
+                    else
+                    {
+                        m_ResourceAutoReleaseInterval.floatValue = resourceAutoReleaseInterval;
+                    }
+                }
+
+                int resourceCapacity = EditorGUILayout.DelayedIntField("Resource Capacity", m_ResourceCapacity.intValue);
+                if (resourceCapacity != m_ResourceCapacity.intValue)
+                {
+                    if (EditorApplication.isPlaying)
+                    {
+                        t.ResourceCapacity = resourceCapacity;
+                    }
+                    else
+                    {
+                        m_ResourceCapacity.intValue = resourceCapacity;
+                    }
+                }
+
+                float resourceExpireTime = EditorGUILayout.DelayedFloatField("Resource Expire Time", m_ResourceExpireTime.floatValue);
+                if (resourceExpireTime != m_ResourceExpireTime.floatValue)
+                {
+                    if (EditorApplication.isPlaying)
+                    {
+                        t.ResourceExpireTime = resourceExpireTime;
+                    }
+                    else
+                    {
+                        m_ResourceExpireTime.floatValue = resourceExpireTime;
+                    }
+                }
+
+                int resourcePriority = EditorGUILayout.DelayedIntField("Resource Priority", m_ResourcePriority.intValue);
+                if (resourcePriority != m_ResourcePriority.intValue)
+                {
+                    if (EditorApplication.isPlaying)
+                    {
+                        t.ResourcePriority = resourcePriority;
+                    }
+                    else
+                    {
+                        m_ResourcePriority.intValue = resourcePriority;
+                    }
+                }
+
+                if (m_ResourceModeIndex > 0)
+                {
+                    string updatePrefixUri = EditorGUILayout.DelayedTextField("Update Prefix Uri", m_UpdatePrefixUri.stringValue);
+                    if (updatePrefixUri != m_UpdatePrefixUri.stringValue)
+                    {
+                        if (EditorApplication.isPlaying)
+                        {
+                            t.UpdatePrefixUri = updatePrefixUri;
+                        }
+                        else
+                        {
+                            m_UpdatePrefixUri.stringValue = updatePrefixUri;
+                        }
+                    }
+
+                    int generateReadWriteVersionListLength = EditorGUILayout.DelayedIntField("Generate Read-Write Version List Length", m_GenerateReadWriteVersionListLength.intValue);
+                    if (generateReadWriteVersionListLength != m_GenerateReadWriteVersionListLength.intValue)
+                    {
+                        if (EditorApplication.isPlaying)
+                        {
+                            t.GenerateReadWriteVersionListLength = generateReadWriteVersionListLength;
+                        }
+                        else
+                        {
+                            m_GenerateReadWriteVersionListLength.intValue = generateReadWriteVersionListLength;
+                        }
+                    }
+
+                    int updateRetryCount = EditorGUILayout.DelayedIntField("Update Retry Count", m_UpdateRetryCount.intValue);
+                    if (updateRetryCount != m_UpdateRetryCount.intValue)
+                    {
+                        if (EditorApplication.isPlaying)
+                        {
+                            t.UpdateRetryCount = updateRetryCount;
+                        }
+                        else
+                        {
+                            m_UpdateRetryCount.intValue = updateRetryCount;
+                        }
+                    }
+                }
+            }
+            EditorGUI.EndDisabledGroup();
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_InstanceRoot);
+
+                m_ResourceHelperInfo.Draw();
+                m_LoadResourceAgentHelperInfo.Draw();
+                m_LoadResourceAgentHelperCount.intValue = EditorGUILayout.IntSlider("Load Resource Agent Helper Count", m_LoadResourceAgentHelperCount.intValue, 1, 128);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Unload Unused Assets", Utility.Text.Format("{0} / {1}", t.LastUnloadUnusedAssetsOperationElapseSeconds.ToString("F2"), t.MaxUnloadUnusedAssetsInterval.ToString("F2")));
+                EditorGUILayout.LabelField("Read-Only Path", t.ReadOnlyPath.ToString());
+                EditorGUILayout.LabelField("Read-Write Path", t.ReadWritePath.ToString());
+                EditorGUILayout.LabelField("Current Variant", t.CurrentVariant ?? "<Unknwon>");
+                EditorGUILayout.LabelField("Applicable Game Version", isEditorResourceMode ? "N/A" : t.ApplicableGameVersion ?? "<Unknwon>");
+                EditorGUILayout.LabelField("Internal Resource Version", isEditorResourceMode ? "N/A" : t.InternalResourceVersion.ToString());
+                EditorGUILayout.LabelField("Asset Count", isEditorResourceMode ? "N/A" : t.AssetCount.ToString());
+                EditorGUILayout.LabelField("Resource Count", isEditorResourceMode ? "N/A" : t.ResourceCount.ToString());
+                EditorGUILayout.LabelField("Resource Group Count", isEditorResourceMode ? "N/A" : t.ResourceGroupCount.ToString());
+                if (m_ResourceModeIndex > 0)
+                {
+                    EditorGUILayout.LabelField("Applying Resource Pack Path", isEditorResourceMode ? "N/A" : t.ApplyingResourcePackPath ?? "<Unknwon>");
+                    EditorGUILayout.LabelField("Apply Waiting Count", isEditorResourceMode ? "N/A" : t.ApplyWaitingCount.ToString());
+                    EditorGUILayout.LabelField("Updating Resource Group", isEditorResourceMode ? "N/A" : t.UpdatingResourceGroup != null ? t.UpdatingResourceGroup.Name : "<Unknwon>");
+                    EditorGUILayout.LabelField("Update Waiting Count", isEditorResourceMode ? "N/A" : t.UpdateWaitingCount.ToString());
+                    EditorGUILayout.LabelField("Update Waiting While Playing Count", isEditorResourceMode ? "N/A" : t.UpdateWaitingWhilePlayingCount.ToString());
+                    EditorGUILayout.LabelField("Update Candidate Count", isEditorResourceMode ? "N/A" : t.UpdateCandidateCount.ToString());
+                }
+                EditorGUILayout.LabelField("Load Total Agent Count", isEditorResourceMode ? "N/A" : t.LoadTotalAgentCount.ToString());
+                EditorGUILayout.LabelField("Load Free Agent Count", isEditorResourceMode ? "N/A" : t.LoadFreeAgentCount.ToString());
+                EditorGUILayout.LabelField("Load Working Agent Count", isEditorResourceMode ? "N/A" : t.LoadWorkingAgentCount.ToString());
+                EditorGUILayout.LabelField("Load Waiting Task Count", isEditorResourceMode ? "N/A" : t.LoadWaitingTaskCount.ToString());
+                if (!isEditorResourceMode)
+                {
+                    EditorGUILayout.BeginVertical("box");
+                    {
+                        TaskInfo[] loadAssetInfos = t.GetAllLoadAssetInfos();
+                        if (loadAssetInfos.Length > 0)
+                        {
+                            foreach (TaskInfo loadAssetInfo in loadAssetInfos)
+                            {
+                                DrawLoadAssetInfo(loadAssetInfo);
+                            }
+
+                            if (GUILayout.Button("Export CSV Data"))
+                            {
+                                string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, "Load Asset Task Data.csv", string.Empty);
+                                if (!string.IsNullOrEmpty(exportFileName))
+                                {
+                                    try
+                                    {
+                                        int index = 0;
+                                        string[] data = new string[loadAssetInfos.Length + 1];
+                                        data[index++] = "Load Asset Name,Serial Id,Priority,Status";
+                                        foreach (TaskInfo loadAssetInfo in loadAssetInfos)
+                                        {
+                                            data[index++] = Utility.Text.Format("{0},{1},{2},{3}", loadAssetInfo.Description, loadAssetInfo.SerialId.ToString(), loadAssetInfo.Priority.ToString(), loadAssetInfo.Status.ToString());
+                                        }
+
+                                        File.WriteAllLines(exportFileName, data, Encoding.UTF8);
+                                        Debug.Log(Utility.Text.Format("Export load asset task CSV data to '{0}' success.", exportFileName));
+                                    }
+                                    catch (Exception exception)
+                                    {
+                                        Debug.LogError(Utility.Text.Format("Export load asset task CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception.ToString()));
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+                            GUILayout.Label("Load Asset Task is Empty ...");
+                        }
+                    }
+                    EditorGUILayout.EndVertical();
+                }
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_ResourceMode = serializedObject.FindProperty("m_ResourceMode");
+            m_ReadWritePathType = serializedObject.FindProperty("m_ReadWritePathType");
+            m_MinUnloadUnusedAssetsInterval = serializedObject.FindProperty("m_MinUnloadUnusedAssetsInterval");
+            m_MaxUnloadUnusedAssetsInterval = serializedObject.FindProperty("m_MaxUnloadUnusedAssetsInterval");
+            m_AssetAutoReleaseInterval = serializedObject.FindProperty("m_AssetAutoReleaseInterval");
+            m_AssetCapacity = serializedObject.FindProperty("m_AssetCapacity");
+            m_AssetExpireTime = serializedObject.FindProperty("m_AssetExpireTime");
+            m_AssetPriority = serializedObject.FindProperty("m_AssetPriority");
+            m_ResourceAutoReleaseInterval = serializedObject.FindProperty("m_ResourceAutoReleaseInterval");
+            m_ResourceCapacity = serializedObject.FindProperty("m_ResourceCapacity");
+            m_ResourceExpireTime = serializedObject.FindProperty("m_ResourceExpireTime");
+            m_ResourcePriority = serializedObject.FindProperty("m_ResourcePriority");
+            m_UpdatePrefixUri = serializedObject.FindProperty("m_UpdatePrefixUri");
+            m_GenerateReadWriteVersionListLength = serializedObject.FindProperty("m_GenerateReadWriteVersionListLength");
+            m_UpdateRetryCount = serializedObject.FindProperty("m_UpdateRetryCount");
+            m_InstanceRoot = serializedObject.FindProperty("m_InstanceRoot");
+            m_LoadResourceAgentHelperCount = serializedObject.FindProperty("m_LoadResourceAgentHelperCount");
+
+            m_EditorResourceModeFieldInfo = target.GetType().GetField("m_EditorResourceMode", BindingFlags.NonPublic | BindingFlags.Instance);
+
+            m_ResourceHelperInfo.Init(serializedObject);
+            m_LoadResourceAgentHelperInfo.Init(serializedObject);
+
+            RefreshModes();
+            RefreshTypeNames();
+        }
+
+        private void DrawLoadAssetInfo(TaskInfo loadAssetInfo)
+        {
+            EditorGUILayout.LabelField(loadAssetInfo.Description, Utility.Text.Format("[SerialId]{0} [Priority]{1} [Status]{2}", loadAssetInfo.SerialId.ToString(), loadAssetInfo.Priority.ToString(), loadAssetInfo.Status.ToString()));
+        }
+
+        private void RefreshModes()
+        {
+            m_ResourceModeIndex = m_ResourceMode.enumValueIndex > 0 ? m_ResourceMode.enumValueIndex - 1 : 0;
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_ResourceHelperInfo.Refresh();
+            m_LoadResourceAgentHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/ResourceComponentInspector.cs.meta

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

+ 75 - 0
Assets/GameFramework/Scripts/Editor/Inspector/SceneComponentInspector.cs

@@ -0,0 +1,75 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(SceneComponent))]
+    internal sealed class SceneComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_EnableLoadSceneUpdateEvent = null;
+        private SerializedProperty m_EnableLoadSceneDependencyAssetEvent = null;
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            SceneComponent t = (SceneComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_EnableLoadSceneUpdateEvent);
+                EditorGUILayout.PropertyField(m_EnableLoadSceneDependencyAssetEvent);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            serializedObject.ApplyModifiedProperties();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Loaded Scene Asset Names", GetSceneNameString(t.GetLoadedSceneAssetNames()));
+                EditorGUILayout.LabelField("Loading Scene Asset Names", GetSceneNameString(t.GetLoadingSceneAssetNames()));
+                EditorGUILayout.LabelField("Unloading Scene Asset Names", GetSceneNameString(t.GetUnloadingSceneAssetNames()));
+                EditorGUILayout.ObjectField("Main Camera", t.MainCamera, typeof(Camera), true);
+
+                Repaint();
+            }
+        }
+
+        private void OnEnable()
+        {
+            m_EnableLoadSceneUpdateEvent = serializedObject.FindProperty("m_EnableLoadSceneUpdateEvent");
+            m_EnableLoadSceneDependencyAssetEvent = serializedObject.FindProperty("m_EnableLoadSceneDependencyAssetEvent");
+        }
+
+        private string GetSceneNameString(string[] sceneAssetNames)
+        {
+            if (sceneAssetNames == null || sceneAssetNames.Length <= 0)
+            {
+                return "<Empty>";
+            }
+
+            string sceneNameString = string.Empty;
+            foreach (string sceneAssetName in sceneAssetNames)
+            {
+                if (!string.IsNullOrEmpty(sceneNameString))
+                {
+                    sceneNameString += ", ";
+                }
+
+                sceneNameString += SceneComponent.GetSceneName(sceneAssetName);
+            }
+
+            return sceneNameString;
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/SceneComponentInspector.cs.meta

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

+ 81 - 0
Assets/GameFramework/Scripts/Editor/Inspector/SettingComponentInspector.cs

@@ -0,0 +1,81 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(SettingComponent))]
+    internal sealed class SettingComponentInspector : GameFrameworkInspector
+    {
+        private HelperInfo<SettingHelperBase> m_SettingHelperInfo = new HelperInfo<SettingHelperBase>("Setting");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            SettingComponent t = (SettingComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                m_SettingHelperInfo.Draw();
+            }
+            EditorGUI.EndDisabledGroup();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Setting Count", t.Count >= 0 ? t.Count.ToString() : "<Unknown>");
+                if (t.Count > 0)
+                {
+                    string[] settingNames = t.GetAllSettingNames();
+                    foreach (string settingName in settingNames)
+                    {
+                        EditorGUILayout.LabelField(settingName, t.GetString(settingName));
+                    }
+                }
+            }
+
+            if (EditorApplication.isPlaying)
+            {
+                if (GUILayout.Button("Save Settings"))
+                {
+                    t.Save();
+                }
+                if (GUILayout.Button("Remove All Settings"))
+                {
+                    t.RemoveAllSettings();
+                }
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_SettingHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_SettingHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/SettingComponentInspector.cs.meta

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

+ 87 - 0
Assets/GameFramework/Scripts/Editor/Inspector/SoundComponentInspector.cs

@@ -0,0 +1,87 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(SoundComponent))]
+    internal sealed class SoundComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_EnablePlaySoundUpdateEvent = null;
+        private SerializedProperty m_EnablePlaySoundDependencyAssetEvent = null;
+        private SerializedProperty m_InstanceRoot = null;
+        private SerializedProperty m_AudioMixer = null;
+        private SerializedProperty m_SoundGroups = null;
+
+        private HelperInfo<SoundHelperBase> m_SoundHelperInfo = new HelperInfo<SoundHelperBase>("Sound");
+        private HelperInfo<SoundGroupHelperBase> m_SoundGroupHelperInfo = new HelperInfo<SoundGroupHelperBase>("SoundGroup");
+        private HelperInfo<SoundAgentHelperBase> m_SoundAgentHelperInfo = new HelperInfo<SoundAgentHelperBase>("SoundAgent");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            SoundComponent t = (SoundComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_EnablePlaySoundUpdateEvent);
+                EditorGUILayout.PropertyField(m_EnablePlaySoundDependencyAssetEvent);
+                EditorGUILayout.PropertyField(m_InstanceRoot);
+                EditorGUILayout.PropertyField(m_AudioMixer);
+                m_SoundHelperInfo.Draw();
+                m_SoundGroupHelperInfo.Draw();
+                m_SoundAgentHelperInfo.Draw();
+                EditorGUILayout.PropertyField(m_SoundGroups, true);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Sound Group Count", t.SoundGroupCount.ToString());
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_EnablePlaySoundUpdateEvent = serializedObject.FindProperty("m_EnablePlaySoundUpdateEvent");
+            m_EnablePlaySoundDependencyAssetEvent = serializedObject.FindProperty("m_EnablePlaySoundDependencyAssetEvent");
+            m_InstanceRoot = serializedObject.FindProperty("m_InstanceRoot");
+            m_AudioMixer = serializedObject.FindProperty("m_AudioMixer");
+            m_SoundGroups = serializedObject.FindProperty("m_SoundGroups");
+
+            m_SoundHelperInfo.Init(serializedObject);
+            m_SoundGroupHelperInfo.Init(serializedObject);
+            m_SoundAgentHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_SoundHelperInfo.Refresh();
+            m_SoundGroupHelperInfo.Refresh();
+            m_SoundAgentHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/SoundComponentInspector.cs.meta

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

+ 154 - 0
Assets/GameFramework/Scripts/Editor/Inspector/UIComponentInspector.cs

@@ -0,0 +1,154 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(UIComponent))]
+    internal sealed class UIComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_EnableOpenUIFormSuccessEvent = null;
+        private SerializedProperty m_EnableOpenUIFormFailureEvent = null;
+        private SerializedProperty m_EnableOpenUIFormUpdateEvent = null;
+        private SerializedProperty m_EnableOpenUIFormDependencyAssetEvent = null;
+        private SerializedProperty m_EnableCloseUIFormCompleteEvent = null;
+        private SerializedProperty m_InstanceAutoReleaseInterval = null;
+        private SerializedProperty m_InstanceCapacity = null;
+        private SerializedProperty m_InstanceExpireTime = null;
+        private SerializedProperty m_InstancePriority = null;
+        private SerializedProperty m_InstanceRoot = null;
+        private SerializedProperty m_UIGroups = null;
+
+        private HelperInfo<UIFormHelperBase> m_UIFormHelperInfo = new HelperInfo<UIFormHelperBase>("UIForm");
+        private HelperInfo<UIGroupHelperBase> m_UIGroupHelperInfo = new HelperInfo<UIGroupHelperBase>("UIGroup");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            UIComponent t = (UIComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_EnableOpenUIFormSuccessEvent);
+                EditorGUILayout.PropertyField(m_EnableOpenUIFormFailureEvent);
+                EditorGUILayout.PropertyField(m_EnableOpenUIFormUpdateEvent);
+                EditorGUILayout.PropertyField(m_EnableOpenUIFormDependencyAssetEvent);
+                EditorGUILayout.PropertyField(m_EnableCloseUIFormCompleteEvent);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            float instanceAutoReleaseInterval = EditorGUILayout.DelayedFloatField("Instance Auto Release Interval", m_InstanceAutoReleaseInterval.floatValue);
+            if (instanceAutoReleaseInterval != m_InstanceAutoReleaseInterval.floatValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.InstanceAutoReleaseInterval = instanceAutoReleaseInterval;
+                }
+                else
+                {
+                    m_InstanceAutoReleaseInterval.floatValue = instanceAutoReleaseInterval;
+                }
+            }
+
+            int instanceCapacity = EditorGUILayout.DelayedIntField("Instance Capacity", m_InstanceCapacity.intValue);
+            if (instanceCapacity != m_InstanceCapacity.intValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.InstanceCapacity = instanceCapacity;
+                }
+                else
+                {
+                    m_InstanceCapacity.intValue = instanceCapacity;
+                }
+            }
+
+            float instanceExpireTime = EditorGUILayout.DelayedFloatField("Instance Expire Time", m_InstanceExpireTime.floatValue);
+            if (instanceExpireTime != m_InstanceExpireTime.floatValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.InstanceExpireTime = instanceExpireTime;
+                }
+                else
+                {
+                    m_InstanceExpireTime.floatValue = instanceExpireTime;
+                }
+            }
+
+            int instancePriority = EditorGUILayout.DelayedIntField("Instance Priority", m_InstancePriority.intValue);
+            if (instancePriority != m_InstancePriority.intValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.InstancePriority = instancePriority;
+                }
+                else
+                {
+                    m_InstancePriority.intValue = instancePriority;
+                }
+            }
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_InstanceRoot);
+                m_UIFormHelperInfo.Draw();
+                m_UIGroupHelperInfo.Draw();
+                EditorGUILayout.PropertyField(m_UIGroups, true);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("UI Group Count", t.UIGroupCount.ToString());
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_EnableOpenUIFormSuccessEvent = serializedObject.FindProperty("m_EnableOpenUIFormSuccessEvent");
+            m_EnableOpenUIFormFailureEvent = serializedObject.FindProperty("m_EnableOpenUIFormFailureEvent");
+            m_EnableOpenUIFormUpdateEvent = serializedObject.FindProperty("m_EnableOpenUIFormUpdateEvent");
+            m_EnableOpenUIFormDependencyAssetEvent = serializedObject.FindProperty("m_EnableOpenUIFormDependencyAssetEvent");
+            m_EnableCloseUIFormCompleteEvent = serializedObject.FindProperty("m_EnableCloseUIFormCompleteEvent");
+            m_InstanceAutoReleaseInterval = serializedObject.FindProperty("m_InstanceAutoReleaseInterval");
+            m_InstanceCapacity = serializedObject.FindProperty("m_InstanceCapacity");
+            m_InstanceExpireTime = serializedObject.FindProperty("m_InstanceExpireTime");
+            m_InstancePriority = serializedObject.FindProperty("m_InstancePriority");
+            m_InstanceRoot = serializedObject.FindProperty("m_InstanceRoot");
+            m_UIGroups = serializedObject.FindProperty("m_UIGroups");
+
+            m_UIFormHelperInfo.Init(serializedObject);
+            m_UIGroupHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_UIFormHelperInfo.Refresh();
+            m_UIGroupHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/UIComponentInspector.cs.meta

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

+ 141 - 0
Assets/GameFramework/Scripts/Editor/Inspector/WebRequestComponentInspector.cs

@@ -0,0 +1,141 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System;
+using System.IO;
+using System.Text;
+using UnityEditor;
+using UnityEngine;
+using UnityGameFramework.Runtime;
+
+namespace UnityGameFramework.Editor
+{
+    [CustomEditor(typeof(WebRequestComponent))]
+    internal sealed class WebRequestComponentInspector : GameFrameworkInspector
+    {
+        private SerializedProperty m_InstanceRoot = null;
+        private SerializedProperty m_WebRequestAgentHelperCount = null;
+        private SerializedProperty m_Timeout = null;
+
+        private HelperInfo<WebRequestAgentHelperBase> m_WebRequestAgentHelperInfo = new HelperInfo<WebRequestAgentHelperBase>("WebRequestAgent");
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            serializedObject.Update();
+
+            WebRequestComponent t = (WebRequestComponent)target;
+
+            EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
+            {
+                EditorGUILayout.PropertyField(m_InstanceRoot);
+
+                m_WebRequestAgentHelperInfo.Draw();
+
+                m_WebRequestAgentHelperCount.intValue = EditorGUILayout.IntSlider("Web Request Agent Helper Count", m_WebRequestAgentHelperCount.intValue, 1, 16);
+            }
+            EditorGUI.EndDisabledGroup();
+
+            float timeout = EditorGUILayout.Slider("Timeout", m_Timeout.floatValue, 0f, 120f);
+            if (timeout != m_Timeout.floatValue)
+            {
+                if (EditorApplication.isPlaying)
+                {
+                    t.Timeout = timeout;
+                }
+                else
+                {
+                    m_Timeout.floatValue = timeout;
+                }
+            }
+
+            if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
+            {
+                EditorGUILayout.LabelField("Total Agent Count", t.TotalAgentCount.ToString());
+                EditorGUILayout.LabelField("Free Agent Count", t.FreeAgentCount.ToString());
+                EditorGUILayout.LabelField("Working Agent Count", t.WorkingAgentCount.ToString());
+                EditorGUILayout.LabelField("Waiting Agent Count", t.WaitingTaskCount.ToString());
+                EditorGUILayout.BeginVertical("box");
+                {
+                    TaskInfo[] webRequestInfos = t.GetAllWebRequestInfos();
+                    if (webRequestInfos.Length > 0)
+                    {
+                        foreach (TaskInfo webRequestInfo in webRequestInfos)
+                        {
+                            DrawWebRequestInfo(webRequestInfo);
+                        }
+
+                        if (GUILayout.Button("Export CSV Data"))
+                        {
+                            string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, "WebRequest Task Data.csv", string.Empty);
+                            if (!string.IsNullOrEmpty(exportFileName))
+                            {
+                                try
+                                {
+                                    int index = 0;
+                                    string[] data = new string[webRequestInfos.Length + 1];
+                                    data[index++] = "WebRequest Uri,Serial Id,Tag,Priority,Status";
+                                    foreach (TaskInfo webRequestInfo in webRequestInfos)
+                                    {
+                                        data[index++] = Utility.Text.Format("{0},{1},{2},{3},{4}", webRequestInfo.Description, webRequestInfo.SerialId.ToString(), webRequestInfo.Tag ?? string.Empty, webRequestInfo.Priority.ToString(), webRequestInfo.Status.ToString());
+                                    }
+
+                                    File.WriteAllLines(exportFileName, data, Encoding.UTF8);
+                                    Debug.Log(Utility.Text.Format("Export web request task CSV data to '{0}' success.", exportFileName));
+                                }
+                                catch (Exception exception)
+                                {
+                                    Debug.LogError(Utility.Text.Format("Export web request task CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception.ToString()));
+                                }
+                            }
+                        }
+                    }
+                    else
+                    {
+                        GUILayout.Label("WebRequset Task is Empty ...");
+                    }
+                }
+                EditorGUILayout.EndVertical();
+            }
+
+            serializedObject.ApplyModifiedProperties();
+
+            Repaint();
+        }
+
+        protected override void OnCompileComplete()
+        {
+            base.OnCompileComplete();
+
+            RefreshTypeNames();
+        }
+
+        private void OnEnable()
+        {
+            m_InstanceRoot = serializedObject.FindProperty("m_InstanceRoot");
+            m_WebRequestAgentHelperCount = serializedObject.FindProperty("m_WebRequestAgentHelperCount");
+            m_Timeout = serializedObject.FindProperty("m_Timeout");
+
+            m_WebRequestAgentHelperInfo.Init(serializedObject);
+
+            RefreshTypeNames();
+        }
+
+        private void DrawWebRequestInfo(TaskInfo webRequestInfo)
+        {
+            EditorGUILayout.LabelField(webRequestInfo.Description, Utility.Text.Format("[SerialId]{0} [Tag]{1} [Priority]{2} [Status]{3}", webRequestInfo.SerialId.ToString(), webRequestInfo.Tag ?? "<None>", webRequestInfo.Priority.ToString(), webRequestInfo.Status.ToString()));
+        }
+
+        private void RefreshTypeNames()
+        {
+            m_WebRequestAgentHelperInfo.Refresh();
+            serializedObject.ApplyModifiedProperties();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Inspector/WebRequestComponentInspector.cs.meta

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

+ 8 - 0
Assets/GameFramework/Scripts/Editor/Misc.meta

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

+ 133 - 0
Assets/GameFramework/Scripts/Editor/Misc/BuildSettings.cs

@@ -0,0 +1,133 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using UnityEditor;
+using UnityEngine;
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// 构建配置相关的实用函数。
+    /// </summary>
+    internal static class BuildSettings
+    {
+        private static readonly string s_ConfigurationPath = null;
+        private static readonly List<string> s_DefaultSceneNames = new List<string>();
+        private static readonly List<string> s_SearchScenePaths = new List<string>();
+
+        static BuildSettings()
+        {
+            s_ConfigurationPath = Type.GetConfigurationPath<BuildSettingsConfigPathAttribute>() ?? Utility.Path.GetRegularPath(Path.Combine(Application.dataPath, "GameFramework/Configs/BuildSettings.xml"));
+            s_DefaultSceneNames.Clear();
+            s_SearchScenePaths.Clear();
+
+            if (!File.Exists(s_ConfigurationPath))
+            {
+                return;
+            }
+
+            try
+            {
+                XmlDocument xmlDocument = new XmlDocument();
+                xmlDocument.Load(s_ConfigurationPath);
+                XmlNode xmlRoot = xmlDocument.SelectSingleNode("UnityGameFramework");
+                XmlNode xmlBuildSettings = xmlRoot.SelectSingleNode("BuildSettings");
+                XmlNode xmlDefaultScenes = xmlBuildSettings.SelectSingleNode("DefaultScenes");
+                XmlNode xmlSearchScenePaths = xmlBuildSettings.SelectSingleNode("SearchScenePaths");
+
+                XmlNodeList xmlNodeList = null;
+                XmlNode xmlNode = null;
+
+                xmlNodeList = xmlDefaultScenes.ChildNodes;
+                for (int i = 0; i < xmlNodeList.Count; i++)
+                {
+                    xmlNode = xmlNodeList.Item(i);
+                    if (xmlNode.Name != "DefaultScene")
+                    {
+                        continue;
+                    }
+
+                    string defaultSceneName = xmlNode.Attributes.GetNamedItem("Name").Value;
+                    s_DefaultSceneNames.Add(defaultSceneName);
+                }
+
+                xmlNodeList = xmlSearchScenePaths.ChildNodes;
+                for (int i = 0; i < xmlNodeList.Count; i++)
+                {
+                    xmlNode = xmlNodeList.Item(i);
+                    if (xmlNode.Name != "SearchScenePath")
+                    {
+                        continue;
+                    }
+
+                    string searchScenePath = xmlNode.Attributes.GetNamedItem("Path").Value;
+                    s_SearchScenePaths.Add(searchScenePath);
+                }
+            }
+            catch
+            {
+            }
+        }
+
+        /// <summary>
+        /// 将构建场景设置为默认。
+        /// </summary>
+        [MenuItem("Game Framework/Scenes in Build Settings/Default Scenes", false, 20)]
+        public static void DefaultScenes()
+        {
+            HashSet<string> sceneNames = new HashSet<string>();
+            foreach (string sceneName in s_DefaultSceneNames)
+            {
+                sceneNames.Add(sceneName);
+            }
+
+            List<EditorBuildSettingsScene> scenes = new List<EditorBuildSettingsScene>();
+            foreach (string sceneName in sceneNames)
+            {
+                scenes.Add(new EditorBuildSettingsScene(sceneName, true));
+            }
+
+            EditorBuildSettings.scenes = scenes.ToArray();
+
+            Debug.Log("Set scenes of build settings to default scenes.");
+        }
+
+        /// <summary>
+        /// 将构建场景设置为所有。
+        /// </summary>
+        [MenuItem("Game Framework/Scenes in Build Settings/All Scenes", false, 21)]
+        public static void AllScenes()
+        {
+            HashSet<string> sceneNames = new HashSet<string>();
+            foreach (string sceneName in s_DefaultSceneNames)
+            {
+                sceneNames.Add(sceneName);
+            }
+
+            string[] sceneGuids = AssetDatabase.FindAssets("t:Scene", s_SearchScenePaths.ToArray());
+            foreach (string sceneGuid in sceneGuids)
+            {
+                string sceneName = AssetDatabase.GUIDToAssetPath(sceneGuid);
+                sceneNames.Add(sceneName);
+            }
+
+            List<EditorBuildSettingsScene> scenes = new List<EditorBuildSettingsScene>();
+            foreach (string sceneName in sceneNames)
+            {
+                scenes.Add(new EditorBuildSettingsScene(sceneName, true));
+            }
+
+            EditorBuildSettings.scenes = scenes.ToArray();
+
+            Debug.Log("Set scenes of build settings to all scenes.");
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/BuildSettings.cs.meta

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

+ 16 - 0
Assets/GameFramework/Scripts/Editor/Misc/BuildSettingsConfigPathAttribute.cs

@@ -0,0 +1,16 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// BuildSettings 配置路径属性。
+    /// </summary>
+    public sealed class BuildSettingsConfigPathAttribute : ConfigPathAttribute
+    {
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/BuildSettingsConfigPathAttribute.cs.meta

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

+ 18 - 0
Assets/GameFramework/Scripts/Editor/Misc/ConfigPathAttribute.cs

@@ -0,0 +1,18 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using System;
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// 配置路径属性。
+    /// </summary>
+    public abstract class ConfigPathAttribute : Attribute
+    {
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/ConfigPathAttribute.cs.meta

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

+ 35 - 0
Assets/GameFramework/Scripts/Editor/Misc/Help.cs

@@ -0,0 +1,35 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+using UnityEngine;
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// 帮助相关的实用函数。
+    /// </summary>
+    public static class Help
+    {
+        [MenuItem("Game Framework/Documentation", false, 90)]
+        public static void ShowDocumentation()
+        {
+            ShowHelp("https://gameframework.cn/document/");
+        }
+
+        [MenuItem("Game Framework/API Reference", false, 91)]
+        public static void ShowApiReference()
+        {
+            ShowHelp("https://gameframework.cn/api/");
+        }
+
+        private static void ShowHelp(string uri)
+        {
+            Application.OpenURL(uri);
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/Help.cs.meta

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

+ 97 - 0
Assets/GameFramework/Scripts/Editor/Misc/HelperInfo.cs

@@ -0,0 +1,97 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using UnityEditor;
+using UnityEngine;
+
+namespace UnityGameFramework.Editor
+{
+    internal sealed class HelperInfo<T> where T : MonoBehaviour
+    {
+        private const string CustomOptionName = "<Custom>";
+
+        private readonly string m_Name;
+
+        private SerializedProperty m_HelperTypeName;
+        private SerializedProperty m_CustomHelper;
+        private string[] m_HelperTypeNames;
+        private int m_HelperTypeNameIndex;
+
+        public HelperInfo(string name)
+        {
+            m_Name = name;
+
+            m_HelperTypeName = null;
+            m_CustomHelper = null;
+            m_HelperTypeNames = null;
+            m_HelperTypeNameIndex = 0;
+        }
+
+        public void Init(SerializedObject serializedObject)
+        {
+            m_HelperTypeName = serializedObject.FindProperty(Utility.Text.Format("m_{0}HelperTypeName", m_Name));
+            m_CustomHelper = serializedObject.FindProperty(Utility.Text.Format("m_Custom{0}Helper", m_Name));
+        }
+
+        public void Draw()
+        {
+            string displayName = FieldNameForDisplay(m_Name);
+            int selectedIndex = EditorGUILayout.Popup(Utility.Text.Format("{0} Helper", displayName), m_HelperTypeNameIndex, m_HelperTypeNames);
+            if (selectedIndex != m_HelperTypeNameIndex)
+            {
+                m_HelperTypeNameIndex = selectedIndex;
+                m_HelperTypeName.stringValue = selectedIndex <= 0 ? null : m_HelperTypeNames[selectedIndex];
+            }
+
+            if (m_HelperTypeNameIndex <= 0)
+            {
+                EditorGUILayout.PropertyField(m_CustomHelper);
+                if (m_CustomHelper.objectReferenceValue == null)
+                {
+                    EditorGUILayout.HelpBox(Utility.Text.Format("You must set Custom {0} Helper.", displayName), MessageType.Error);
+                }
+            }
+        }
+
+        public void Refresh()
+        {
+            List<string> helperTypeNameList = new List<string>
+            {
+                CustomOptionName
+            };
+
+            helperTypeNameList.AddRange(Type.GetRuntimeTypeNames(typeof(T)));
+            m_HelperTypeNames = helperTypeNameList.ToArray();
+
+            m_HelperTypeNameIndex = 0;
+            if (!string.IsNullOrEmpty(m_HelperTypeName.stringValue))
+            {
+                m_HelperTypeNameIndex = helperTypeNameList.IndexOf(m_HelperTypeName.stringValue);
+                if (m_HelperTypeNameIndex <= 0)
+                {
+                    m_HelperTypeNameIndex = 0;
+                    m_HelperTypeName.stringValue = null;
+                }
+            }
+        }
+
+        private string FieldNameForDisplay(string fieldName)
+        {
+            if (string.IsNullOrEmpty(fieldName))
+            {
+                return string.Empty;
+            }
+
+            string str = Regex.Replace(fieldName, @"^m_", string.Empty);
+            str = Regex.Replace(str, @"((?<=[a-z])[A-Z]|[A-Z](?=[a-z]))", @" $1").TrimStart();
+            return str;
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/HelperInfo.cs.meta

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

+ 122 - 0
Assets/GameFramework/Scripts/Editor/Misc/LogRedirection.cs

@@ -0,0 +1,122 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+#if !UNITY_2019_1_OR_NEWER
+
+using System.IO;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using UnityEditor;
+using UnityEditor.Callbacks;
+using UnityEditorInternal;
+using UnityEngine;
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// 日志重定向相关的实用函数。
+    /// </summary>
+    internal static class LogRedirection
+    {
+        private static readonly Regex LogRegex = new Regex(@" \(at (.+)\:(\d+)\)\r?\n");
+
+        [OnOpenAsset(0)]
+        private static bool OnOpenAsset(int instanceId, int line)
+        {
+            string selectedStackTrace = GetSelectedStackTrace();
+            if (string.IsNullOrEmpty(selectedStackTrace))
+            {
+                return false;
+            }
+
+            if (!selectedStackTrace.Contains("UnityGameFramework.Runtime.DefaultLogHelper:Log"))
+            {
+                return false;
+            }
+
+            Match match = LogRegex.Match(selectedStackTrace);
+            if (!match.Success)
+            {
+                return false;
+            }
+
+            if (!match.Groups[1].Value.Contains("DefaultLogHelper.cs"))
+            {
+                return false;
+            }
+
+            match = match.NextMatch();
+            if (!match.Success)
+            {
+                return false;
+            }
+
+            if (match.Groups[1].Value.Contains("GameFrameworkLog.cs"))
+            {
+                match = match.NextMatch();
+                if (!match.Success)
+                {
+                    return false;
+                }
+            }
+
+            if (match.Groups[1].Value.Contains("Log.cs"))
+            {
+                match = match.NextMatch();
+                if (!match.Success)
+                {
+                    return false;
+                }
+            }
+
+            InternalEditorUtility.OpenFileAtLineExternal(Path.Combine(Application.dataPath, match.Groups[1].Value.Substring(7)), int.Parse(match.Groups[2].Value));
+            return true;
+        }
+
+        private static string GetSelectedStackTrace()
+        {
+            Assembly editorWindowAssembly = typeof(EditorWindow).Assembly;
+            if (editorWindowAssembly == null)
+            {
+                return null;
+            }
+
+            System.Type consoleWindowType = editorWindowAssembly.GetType("UnityEditor.ConsoleWindow");
+            if (consoleWindowType == null)
+            {
+                return null;
+            }
+
+            FieldInfo consoleWindowFieldInfo = consoleWindowType.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);
+            if (consoleWindowFieldInfo == null)
+            {
+                return null;
+            }
+
+            EditorWindow consoleWindow = consoleWindowFieldInfo.GetValue(null) as EditorWindow;
+            if (consoleWindow == null)
+            {
+                return null;
+            }
+
+            if (consoleWindow != EditorWindow.focusedWindow)
+            {
+                return null;
+            }
+
+            FieldInfo activeTextFieldInfo = consoleWindowType.GetField("m_ActiveText", BindingFlags.Instance | BindingFlags.NonPublic);
+            if (activeTextFieldInfo == null)
+            {
+                return null;
+            }
+
+            return (string)activeTextFieldInfo.GetValue(consoleWindow);
+        }
+    }
+}
+
+#endif

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/LogRedirection.cs.meta

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

+ 179 - 0
Assets/GameFramework/Scripts/Editor/Misc/LogScriptingDefineSymbols.cs

@@ -0,0 +1,179 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using UnityEditor;
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// 日志脚本宏定义。
+    /// </summary>
+    public static class LogScriptingDefineSymbols
+    {
+        private const string EnableLogScriptingDefineSymbol = "ENABLE_LOG";
+        private const string EnableDebugAndAboveLogScriptingDefineSymbol = "ENABLE_DEBUG_AND_ABOVE_LOG";
+        private const string EnableInfoAndAboveLogScriptingDefineSymbol = "ENABLE_INFO_AND_ABOVE_LOG";
+        private const string EnableWarningAndAboveLogScriptingDefineSymbol = "ENABLE_WARNING_AND_ABOVE_LOG";
+        private const string EnableErrorAndAboveLogScriptingDefineSymbol = "ENABLE_ERROR_AND_ABOVE_LOG";
+        private const string EnableFatalAndAboveLogScriptingDefineSymbol = "ENABLE_FATAL_AND_ABOVE_LOG";
+        private const string EnableDebugLogScriptingDefineSymbol = "ENABLE_DEBUG_LOG";
+        private const string EnableInfoLogScriptingDefineSymbol = "ENABLE_INFO_LOG";
+        private const string EnableWarningLogScriptingDefineSymbol = "ENABLE_WARNING_LOG";
+        private const string EnableErrorLogScriptingDefineSymbol = "ENABLE_ERROR_LOG";
+        private const string EnableFatalLogScriptingDefineSymbol = "ENABLE_FATAL_LOG";
+
+        private static readonly string[] AboveLogScriptingDefineSymbols = new string[]
+        {
+            EnableDebugAndAboveLogScriptingDefineSymbol,
+            EnableInfoAndAboveLogScriptingDefineSymbol,
+            EnableWarningAndAboveLogScriptingDefineSymbol,
+            EnableErrorAndAboveLogScriptingDefineSymbol,
+            EnableFatalAndAboveLogScriptingDefineSymbol
+        };
+
+        private static readonly string[] SpecifyLogScriptingDefineSymbols = new string[]
+        {
+            EnableDebugLogScriptingDefineSymbol,
+            EnableInfoLogScriptingDefineSymbol,
+            EnableWarningLogScriptingDefineSymbol,
+            EnableErrorLogScriptingDefineSymbol,
+            EnableFatalLogScriptingDefineSymbol
+        };
+
+        /// <summary>
+        /// 禁用所有日志脚本宏定义。
+        /// </summary>
+        [MenuItem("Game Framework/Log Scripting Define Symbols/Disable All Logs", false, 30)]
+        public static void DisableAllLogs()
+        {
+            ScriptingDefineSymbols.RemoveScriptingDefineSymbol(EnableLogScriptingDefineSymbol);
+
+            foreach (string specifyLogScriptingDefineSymbol in SpecifyLogScriptingDefineSymbols)
+            {
+                ScriptingDefineSymbols.RemoveScriptingDefineSymbol(specifyLogScriptingDefineSymbol);
+            }
+
+            foreach (string aboveLogScriptingDefineSymbol in AboveLogScriptingDefineSymbols)
+            {
+                ScriptingDefineSymbols.RemoveScriptingDefineSymbol(aboveLogScriptingDefineSymbol);
+            }
+        }
+
+        /// <summary>
+        /// 开启所有日志脚本宏定义。
+        /// </summary>
+        [MenuItem("Game Framework/Log Scripting Define Symbols/Enable All Logs", false, 31)]
+        public static void EnableAllLogs()
+        {
+            DisableAllLogs();
+            ScriptingDefineSymbols.AddScriptingDefineSymbol(EnableLogScriptingDefineSymbol);
+        }
+
+        /// <summary>
+        /// 开启调试及以上级别的日志脚本宏定义。
+        /// </summary>
+        [MenuItem("Game Framework/Log Scripting Define Symbols/Enable Debug And Above Logs", false, 32)]
+        public static void EnableDebugAndAboveLogs()
+        {
+            SetAboveLogScriptingDefineSymbol(EnableDebugAndAboveLogScriptingDefineSymbol);
+        }
+
+        /// <summary>
+        /// 开启信息及以上级别的日志脚本宏定义。
+        /// </summary>
+        [MenuItem("Game Framework/Log Scripting Define Symbols/Enable Info And Above Logs", false, 33)]
+        public static void EnableInfoAndAboveLogs()
+        {
+            SetAboveLogScriptingDefineSymbol(EnableInfoAndAboveLogScriptingDefineSymbol);
+        }
+
+        /// <summary>
+        /// 开启警告及以上级别的日志脚本宏定义。
+        /// </summary>
+        [MenuItem("Game Framework/Log Scripting Define Symbols/Enable Warning And Above Logs", false, 34)]
+        public static void EnableWarningAndAboveLogs()
+        {
+            SetAboveLogScriptingDefineSymbol(EnableWarningAndAboveLogScriptingDefineSymbol);
+        }
+
+        /// <summary>
+        /// 开启错误及以上级别的日志脚本宏定义。
+        /// </summary>
+        [MenuItem("Game Framework/Log Scripting Define Symbols/Enable Error And Above Logs", false, 35)]
+        public static void EnableErrorAndAboveLogs()
+        {
+            SetAboveLogScriptingDefineSymbol(EnableErrorAndAboveLogScriptingDefineSymbol);
+        }
+
+        /// <summary>
+        /// 开启严重错误及以上级别的日志脚本宏定义。
+        /// </summary>
+        [MenuItem("Game Framework/Log Scripting Define Symbols/Enable Fatal And Above Logs", false, 36)]
+        public static void EnableFatalAndAboveLogs()
+        {
+            SetAboveLogScriptingDefineSymbol(EnableFatalAndAboveLogScriptingDefineSymbol);
+        }
+
+        /// <summary>
+        /// 设置日志脚本宏定义。
+        /// </summary>
+        /// <param name="aboveLogScriptingDefineSymbol">要设置的日志脚本宏定义。</param>
+        public static void SetAboveLogScriptingDefineSymbol(string aboveLogScriptingDefineSymbol)
+        {
+            if (string.IsNullOrEmpty(aboveLogScriptingDefineSymbol))
+            {
+                return;
+            }
+
+            foreach (string i in AboveLogScriptingDefineSymbols)
+            {
+                if (i == aboveLogScriptingDefineSymbol)
+                {
+                    DisableAllLogs();
+                    ScriptingDefineSymbols.AddScriptingDefineSymbol(aboveLogScriptingDefineSymbol);
+                    return;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 设置日志脚本宏定义。
+        /// </summary>
+        /// <param name="specifyLogScriptingDefineSymbols">要设置的日志脚本宏定义。</param>
+        public static void SetSpecifyLogScriptingDefineSymbols(string[] specifyLogScriptingDefineSymbols)
+        {
+            if (specifyLogScriptingDefineSymbols == null || specifyLogScriptingDefineSymbols.Length <= 0)
+            {
+                return;
+            }
+
+            bool removed = false;
+            foreach (string specifyLogScriptingDefineSymbol in specifyLogScriptingDefineSymbols)
+            {
+                if (string.IsNullOrEmpty(specifyLogScriptingDefineSymbol))
+                {
+                    continue;
+                }
+
+                foreach (string i in SpecifyLogScriptingDefineSymbols)
+                {
+                    if (i == specifyLogScriptingDefineSymbol)
+                    {
+                        if (!removed)
+                        {
+                            removed = true;
+                            DisableAllLogs();
+                        }
+
+                        ScriptingDefineSymbols.AddScriptingDefineSymbol(specifyLogScriptingDefineSymbol);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/LogScriptingDefineSymbols.cs.meta

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

+ 91 - 0
Assets/GameFramework/Scripts/Editor/Misc/OpenFolder.cs

@@ -0,0 +1,91 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System.Diagnostics;
+using UnityEditor;
+using UnityEngine;
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// 打开文件夹相关的实用函数。
+    /// </summary>
+    public static class OpenFolder
+    {
+        /// <summary>
+        /// 打开 Data Path 文件夹。
+        /// </summary>
+        [MenuItem("Game Framework/Open Folder/Data Path", false, 10)]
+        public static void OpenFolderDataPath()
+        {
+            Execute(Application.dataPath);
+        }
+
+        /// <summary>
+        /// 打开 Persistent Data Path 文件夹。
+        /// </summary>
+        [MenuItem("Game Framework/Open Folder/Persistent Data Path", false, 11)]
+        public static void OpenFolderPersistentDataPath()
+        {
+            Execute(Application.persistentDataPath);
+        }
+
+        /// <summary>
+        /// 打开 Streaming Assets Path 文件夹。
+        /// </summary>
+        [MenuItem("Game Framework/Open Folder/Streaming Assets Path", false, 12)]
+        public static void OpenFolderStreamingAssetsPath()
+        {
+            Execute(Application.streamingAssetsPath);
+        }
+
+        /// <summary>
+        /// 打开 Temporary Cache Path 文件夹。
+        /// </summary>
+        [MenuItem("Game Framework/Open Folder/Temporary Cache Path", false, 13)]
+        public static void OpenFolderTemporaryCachePath()
+        {
+            Execute(Application.temporaryCachePath);
+        }
+
+#if UNITY_2018_3_OR_NEWER
+
+        /// <summary>
+        /// 打开 Console Log Path 文件夹。
+        /// </summary>
+        [MenuItem("Game Framework/Open Folder/Console Log Path", false, 14)]
+        public static void OpenFolderConsoleLogPath()
+        {
+            Execute(System.IO.Path.GetDirectoryName(Application.consoleLogPath));
+        }
+
+#endif
+
+        /// <summary>
+        /// 打开指定路径的文件夹。
+        /// </summary>
+        /// <param name="folder">要打开的文件夹的路径。</param>
+        public static void Execute(string folder)
+        {
+            folder = Utility.Text.Format("\"{0}\"", folder);
+            switch (Application.platform)
+            {
+                case RuntimePlatform.WindowsEditor:
+                    Process.Start("Explorer.exe", folder.Replace('/', '\\'));
+                    break;
+
+                case RuntimePlatform.OSXEditor:
+                    Process.Start("open", folder);
+                    break;
+
+                default:
+                    throw new GameFrameworkException(Utility.Text.Format("Not support open folder on '{0}' platform.", Application.platform.ToString()));
+            }
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/OpenFolder.cs.meta

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

+ 157 - 0
Assets/GameFramework/Scripts/Editor/Misc/ScriptingDefineSymbols.cs

@@ -0,0 +1,157 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// 脚本宏定义。
+    /// </summary>
+    public static class ScriptingDefineSymbols
+    {
+        private static readonly BuildTargetGroup[] BuildTargetGroups = new BuildTargetGroup[]
+        {
+            BuildTargetGroup.Standalone,
+            BuildTargetGroup.iOS,
+            BuildTargetGroup.Android,
+            BuildTargetGroup.WSA,
+            BuildTargetGroup.WebGL
+        };
+
+        /// <summary>
+        /// 检查指定平台是否存在指定的脚本宏定义。
+        /// </summary>
+        /// <param name="buildTargetGroup">要检查脚本宏定义的平台。</param>
+        /// <param name="scriptingDefineSymbol">要检查的脚本宏定义。</param>
+        /// <returns>指定平台是否存在指定的脚本宏定义。</returns>
+        public static bool HasScriptingDefineSymbol(BuildTargetGroup buildTargetGroup, string scriptingDefineSymbol)
+        {
+            if (string.IsNullOrEmpty(scriptingDefineSymbol))
+            {
+                return false;
+            }
+
+            string[] scriptingDefineSymbols = GetScriptingDefineSymbols(buildTargetGroup);
+            foreach (string i in scriptingDefineSymbols)
+            {
+                if (i == scriptingDefineSymbol)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// 为指定平台增加指定的脚本宏定义。
+        /// </summary>
+        /// <param name="buildTargetGroup">要增加脚本宏定义的平台。</param>
+        /// <param name="scriptingDefineSymbol">要增加的脚本宏定义。</param>
+        public static void AddScriptingDefineSymbol(BuildTargetGroup buildTargetGroup, string scriptingDefineSymbol)
+        {
+            if (string.IsNullOrEmpty(scriptingDefineSymbol))
+            {
+                return;
+            }
+
+            if (HasScriptingDefineSymbol(buildTargetGroup, scriptingDefineSymbol))
+            {
+                return;
+            }
+
+            List<string> scriptingDefineSymbols = new List<string>(GetScriptingDefineSymbols(buildTargetGroup))
+            {
+                scriptingDefineSymbol
+            };
+
+            SetScriptingDefineSymbols(buildTargetGroup, scriptingDefineSymbols.ToArray());
+        }
+
+        /// <summary>
+        /// 为指定平台移除指定的脚本宏定义。
+        /// </summary>
+        /// <param name="buildTargetGroup">要移除脚本宏定义的平台。</param>
+        /// <param name="scriptingDefineSymbol">要移除的脚本宏定义。</param>
+        public static void RemoveScriptingDefineSymbol(BuildTargetGroup buildTargetGroup, string scriptingDefineSymbol)
+        {
+            if (string.IsNullOrEmpty(scriptingDefineSymbol))
+            {
+                return;
+            }
+
+            if (!HasScriptingDefineSymbol(buildTargetGroup, scriptingDefineSymbol))
+            {
+                return;
+            }
+
+            List<string> scriptingDefineSymbols = new List<string>(GetScriptingDefineSymbols(buildTargetGroup));
+            while (scriptingDefineSymbols.Contains(scriptingDefineSymbol))
+            {
+                scriptingDefineSymbols.Remove(scriptingDefineSymbol);
+            }
+
+            SetScriptingDefineSymbols(buildTargetGroup, scriptingDefineSymbols.ToArray());
+        }
+
+        /// <summary>
+        /// 为所有平台增加指定的脚本宏定义。
+        /// </summary>
+        /// <param name="scriptingDefineSymbol">要增加的脚本宏定义。</param>
+        public static void AddScriptingDefineSymbol(string scriptingDefineSymbol)
+        {
+            if (string.IsNullOrEmpty(scriptingDefineSymbol))
+            {
+                return;
+            }
+
+            foreach (BuildTargetGroup buildTargetGroup in BuildTargetGroups)
+            {
+                AddScriptingDefineSymbol(buildTargetGroup, scriptingDefineSymbol);
+            }
+        }
+
+        /// <summary>
+        /// 为所有平台移除指定的脚本宏定义。
+        /// </summary>
+        /// <param name="scriptingDefineSymbol">要移除的脚本宏定义。</param>
+        public static void RemoveScriptingDefineSymbol(string scriptingDefineSymbol)
+        {
+            if (string.IsNullOrEmpty(scriptingDefineSymbol))
+            {
+                return;
+            }
+
+            foreach (BuildTargetGroup buildTargetGroup in BuildTargetGroups)
+            {
+                RemoveScriptingDefineSymbol(buildTargetGroup, scriptingDefineSymbol);
+            }
+        }
+
+        /// <summary>
+        /// 获取指定平台的脚本宏定义。
+        /// </summary>
+        /// <param name="buildTargetGroup">要获取脚本宏定义的平台。</param>
+        /// <returns>平台的脚本宏定义。</returns>
+        public static string[] GetScriptingDefineSymbols(BuildTargetGroup buildTargetGroup)
+        {
+            return PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup).Split(';');
+        }
+
+        /// <summary>
+        /// 设置指定平台的脚本宏定义。
+        /// </summary>
+        /// <param name="buildTargetGroup">要设置脚本宏定义的平台。</param>
+        /// <param name="scriptingDefineSymbols">要设置的脚本宏定义。</param>
+        public static void SetScriptingDefineSymbols(BuildTargetGroup buildTargetGroup, string[] scriptingDefineSymbols)
+        {
+            PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, string.Join(";", scriptingDefineSymbols));
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/ScriptingDefineSymbols.cs.meta

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

+ 127 - 0
Assets/GameFramework/Scripts/Editor/Misc/Type.cs

@@ -0,0 +1,127 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace UnityGameFramework.Editor
+{
+    /// <summary>
+    /// 类型相关的实用函数。
+    /// </summary>
+    internal static class Type
+    {
+        private static readonly string[] RuntimeAssemblyNames =
+        {
+#if UNITY_2017_3_OR_NEWER
+            "UnityGameFramework.Runtime",
+#endif
+            "Assembly-CSharp",
+        };
+
+        private static readonly string[] RuntimeOrEditorAssemblyNames =
+        {
+#if UNITY_2017_3_OR_NEWER
+            "UnityGameFramework.Runtime",
+#endif
+            "Assembly-CSharp",
+#if UNITY_2017_3_OR_NEWER
+            "UnityGameFramework.Editor",
+#endif
+            "Assembly-CSharp-Editor",
+        };
+
+        /// <summary>
+        /// 获取配置路径。
+        /// </summary>
+        /// <typeparam name="T">配置类型。</typeparam>
+        /// <returns>配置路径。</returns>
+        internal static string GetConfigurationPath<T>() where T : ConfigPathAttribute
+        {
+            foreach (System.Type type in Utility.Assembly.GetTypes())
+            {
+                if (!type.IsAbstract || !type.IsSealed)
+                {
+                    continue;
+                }
+
+                foreach (FieldInfo fieldInfo in type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
+                {
+                    if (fieldInfo.FieldType == typeof(string) && fieldInfo.IsDefined(typeof(T), false))
+                    {
+                        return (string)fieldInfo.GetValue(null);
+                    }
+                }
+
+                foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
+                {
+                    if (propertyInfo.PropertyType == typeof(string) && propertyInfo.IsDefined(typeof(T), false))
+                    {
+                        return (string)propertyInfo.GetValue(null, null);
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// 在运行时程序集中获取指定基类的所有子类的名称。
+        /// </summary>
+        /// <param name="typeBase">基类类型。</param>
+        /// <returns>指定基类的所有子类的名称。</returns>
+        internal static string[] GetRuntimeTypeNames(System.Type typeBase)
+        {
+            return GetTypeNames(typeBase, RuntimeAssemblyNames);
+        }
+
+        /// <summary>
+        /// 在运行时或编辑器程序集中获取指定基类的所有子类的名称。
+        /// </summary>
+        /// <param name="typeBase">基类类型。</param>
+        /// <returns>指定基类的所有子类的名称。</returns>
+        internal static string[] GetRuntimeOrEditorTypeNames(System.Type typeBase)
+        {
+            return GetTypeNames(typeBase, RuntimeOrEditorAssemblyNames);
+        }
+
+        private static string[] GetTypeNames(System.Type typeBase, string[] assemblyNames)
+        {
+            List<string> typeNames = new List<string>();
+            foreach (string assemblyName in assemblyNames)
+            {
+                Assembly assembly = null;
+                try
+                {
+                    assembly = Assembly.Load(assemblyName);
+                }
+                catch
+                {
+                    continue;
+                }
+
+                if (assembly == null)
+                {
+                    continue;
+                }
+
+                System.Type[] types = assembly.GetTypes();
+                foreach (System.Type type in types)
+                {
+                    if (type.IsClass && !type.IsAbstract && typeBase.IsAssignableFrom(type))
+                    {
+                        typeNames.Add(type.FullName);
+                    }
+                }
+            }
+
+            typeNames.Sort();
+            return typeNames.ToArray();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/Misc/Type.cs.meta

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

+ 8 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer.meta

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

+ 21 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/AssetsOrder.cs

@@ -0,0 +1,21 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+namespace UnityGameFramework.Editor.ResourceTools
+{
+    public enum AssetsOrder : byte
+    {
+        AssetNameAsc,
+        AssetNameDesc,
+        DependencyResourceCountAsc,
+        DependencyResourceCountDesc,
+        DependencyAssetCountAsc,
+        DependencyAssetCountDesc,
+        ScatteredDependencyAssetCountAsc,
+        ScatteredDependencyAssetCountDesc,
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/AssetsOrder.cs.meta

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

+ 96 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/DependencyData.cs

@@ -0,0 +1,96 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using System.Collections.Generic;
+
+namespace UnityGameFramework.Editor.ResourceTools
+{
+    public sealed class DependencyData
+    {
+        private List<Resource> m_DependencyResources;
+        private List<Asset> m_DependencyAssets;
+        private List<string> m_ScatteredDependencyAssetNames;
+
+        public DependencyData()
+        {
+            m_DependencyResources = new List<Resource>();
+            m_DependencyAssets = new List<Asset>();
+            m_ScatteredDependencyAssetNames = new List<string>();
+        }
+
+        public int DependencyResourceCount
+        {
+            get
+            {
+                return m_DependencyResources.Count;
+            }
+        }
+
+        public int DependencyAssetCount
+        {
+            get
+            {
+                return m_DependencyAssets.Count;
+            }
+        }
+
+        public int ScatteredDependencyAssetCount
+        {
+            get
+            {
+                return m_ScatteredDependencyAssetNames.Count;
+            }
+        }
+
+        public void AddDependencyAsset(Asset asset)
+        {
+            if (!m_DependencyResources.Contains(asset.Resource))
+            {
+                m_DependencyResources.Add(asset.Resource);
+            }
+
+            m_DependencyAssets.Add(asset);
+        }
+
+        public void AddScatteredDependencyAsset(string dependencyAssetName)
+        {
+            m_ScatteredDependencyAssetNames.Add(dependencyAssetName);
+        }
+
+        public Resource[] GetDependencyResources()
+        {
+            return m_DependencyResources.ToArray();
+        }
+
+        public Asset[] GetDependencyAssets()
+        {
+            return m_DependencyAssets.ToArray();
+        }
+
+        public string[] GetScatteredDependencyAssetNames()
+        {
+            return m_ScatteredDependencyAssetNames.ToArray();
+        }
+
+        public void RefreshData()
+        {
+            m_DependencyResources.Sort(DependencyResourcesComparer);
+            m_DependencyAssets.Sort(DependencyAssetsComparer);
+            m_ScatteredDependencyAssetNames.Sort();
+        }
+
+        private int DependencyResourcesComparer(Resource a, Resource b)
+        {
+            return a.FullName.CompareTo(b.FullName);
+        }
+
+        private int DependencyAssetsComparer(Asset a, Asset b)
+        {
+            return a.Name.CompareTo(b.Name);
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/DependencyData.cs.meta

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

+ 533 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzer.cs

@@ -0,0 +1,533 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+
+namespace UnityGameFramework.Editor.ResourceTools
+{
+    /// <summary>
+    /// 资源分析器。
+    /// </summary>
+    internal sealed class ResourceAnalyzer : EditorWindow
+    {
+        private ResourceAnalyzerController m_Controller = null;
+        private bool m_Analyzed = false;
+        private int m_ToolbarIndex = 0;
+
+        private int m_AssetCount = 0;
+        private string[] m_CachedAssetNames = null;
+        private int m_SelectedAssetIndex = -1;
+        private string m_SelectedAssetName = null;
+        private DependencyData m_SelectedDependencyData = null;
+        private AssetsOrder m_AssetsOrder = AssetsOrder.AssetNameAsc;
+        private string m_AssetsFilter = null;
+        private Vector2 m_AssetsScroll = Vector2.zero;
+        private Vector2 m_DependencyResourcesScroll = Vector2.zero;
+        private Vector2 m_DependencyAssetsScroll = Vector2.zero;
+        private Vector2 m_ScatteredDependencyAssetsScroll = Vector2.zero;
+
+        private int m_ScatteredAssetCount = 0;
+        private string[] m_CachedScatteredAssetNames = null;
+        private int m_SelectedScatteredAssetIndex = -1;
+        private string m_SelectedScatteredAssetName = null;
+        private Asset[] m_SelectedHostAssets = null;
+        private ScatteredAssetsOrder m_ScatteredAssetsOrder = ScatteredAssetsOrder.AssetNameAsc;
+        private string m_ScatteredAssetsFilter = null;
+        private Vector2 m_ScatteredAssetsScroll = Vector2.zero;
+        private Vector2 m_HostAssetsScroll = Vector2.zero;
+
+        private int m_CircularDependencyCount = 0;
+        private string[][] m_CachedCircularDependencyDatas = null;
+        private Vector2 m_CircularDependencyScroll = Vector2.zero;
+
+        [MenuItem("Game Framework/Resource Tools/Resource Analyzer", false, 42)]
+        private static void Open()
+        {
+            ResourceAnalyzer window = GetWindow<ResourceAnalyzer>("Resource Analyzer", true);
+            window.minSize = new Vector2(800f, 600f);
+        }
+
+        private void OnEnable()
+        {
+            m_Controller = new ResourceAnalyzerController();
+            m_Controller.OnLoadingResource += OnLoadingResource;
+            m_Controller.OnLoadingAsset += OnLoadingAsset;
+            m_Controller.OnLoadCompleted += OnLoadCompleted;
+            m_Controller.OnAnalyzingAsset += OnAnalyzingAsset;
+            m_Controller.OnAnalyzeCompleted += OnAnalyzeCompleted;
+
+            m_Analyzed = false;
+            m_ToolbarIndex = 0;
+
+            m_AssetCount = 0;
+            m_CachedAssetNames = null;
+            m_SelectedAssetIndex = -1;
+            m_SelectedAssetName = null;
+            m_SelectedDependencyData = new DependencyData();
+            m_AssetsOrder = AssetsOrder.ScatteredDependencyAssetCountDesc;
+            m_AssetsFilter = null;
+            m_AssetsScroll = Vector2.zero;
+            m_DependencyResourcesScroll = Vector2.zero;
+            m_DependencyAssetsScroll = Vector2.zero;
+            m_ScatteredDependencyAssetsScroll = Vector2.zero;
+
+            m_ScatteredAssetCount = 0;
+            m_CachedScatteredAssetNames = null;
+            m_SelectedScatteredAssetIndex = -1;
+            m_SelectedScatteredAssetName = null;
+            m_SelectedHostAssets = new Asset[] { };
+            m_ScatteredAssetsOrder = ScatteredAssetsOrder.HostAssetCountDesc;
+            m_ScatteredAssetsFilter = null;
+            m_ScatteredAssetsScroll = Vector2.zero;
+            m_HostAssetsScroll = Vector2.zero;
+
+            m_CircularDependencyCount = 0;
+            m_CachedCircularDependencyDatas = null;
+            m_CircularDependencyScroll = Vector2.zero;
+        }
+
+        private void OnGUI()
+        {
+            EditorGUILayout.BeginVertical(GUILayout.Width(position.width), GUILayout.Height(position.height));
+            {
+                GUILayout.Space(5f);
+                int toolbarIndex = GUILayout.Toolbar(m_ToolbarIndex, new string[] { "Summary", "Asset Dependency Viewer", "Scattered Asset Viewer", "Circular Dependency Viewer" }, GUILayout.Height(30f));
+                if (toolbarIndex != m_ToolbarIndex)
+                {
+                    m_ToolbarIndex = toolbarIndex;
+                    GUI.FocusControl(null);
+                }
+
+                switch (m_ToolbarIndex)
+                {
+                    case 0:
+                        DrawSummary();
+                        break;
+
+                    case 1:
+                        DrawAssetDependencyViewer();
+                        break;
+
+                    case 2:
+                        DrawScatteredAssetViewer();
+                        break;
+
+                    case 3:
+                        DrawCircularDependencyViewer();
+                        break;
+                }
+            }
+            EditorGUILayout.EndVertical();
+        }
+
+        private void DrawAnalyzeButton()
+        {
+            if (!m_Analyzed)
+            {
+                EditorGUILayout.HelpBox("Please analyze first.", MessageType.Info);
+            }
+
+            if (GUILayout.Button("Analyze", GUILayout.Height(30f)))
+            {
+                m_Controller.Clear();
+
+                m_SelectedAssetIndex = -1;
+                m_SelectedAssetName = null;
+                m_SelectedDependencyData = new DependencyData();
+
+                m_SelectedScatteredAssetIndex = -1;
+                m_SelectedScatteredAssetName = null;
+                m_SelectedHostAssets = new Asset[] { };
+
+                if (m_Controller.Prepare())
+                {
+                    m_Controller.Analyze();
+                    m_Analyzed = true;
+                    m_AssetCount = m_Controller.GetAssetNames().Length;
+                    m_ScatteredAssetCount = m_Controller.GetScatteredAssetNames().Length;
+                    m_CachedCircularDependencyDatas = m_Controller.GetCircularDependencyDatas();
+                    m_CircularDependencyCount = m_CachedCircularDependencyDatas.Length;
+                    OnAssetsOrderOrFilterChanged();
+                    OnScatteredAssetsOrderOrFilterChanged();
+                }
+                else
+                {
+                    EditorUtility.DisplayDialog("Resource Analyze", "Can not parse 'ResourceCollection.xml', please use 'Resource Editor' tool first.", "OK");
+                }
+            }
+        }
+
+        private void DrawSummary()
+        {
+            DrawAnalyzeButton();
+        }
+
+        private void DrawAssetDependencyViewer()
+        {
+            if (!m_Analyzed)
+            {
+                DrawAnalyzeButton();
+                return;
+            }
+
+            EditorGUILayout.BeginHorizontal();
+            {
+                GUILayout.Space(5f);
+                EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.4f));
+                {
+                    GUILayout.Space(5f);
+                    string title = null;
+                    if (string.IsNullOrEmpty(m_AssetsFilter))
+                    {
+                        title = Utility.Text.Format("Assets In Resources ({0})", m_AssetCount.ToString());
+                    }
+                    else
+                    {
+                        title = Utility.Text.Format("Assets In Resources ({0}/{1})", m_CachedAssetNames.Length.ToString(), m_AssetCount.ToString());
+                    }
+                    EditorGUILayout.LabelField(title, EditorStyles.boldLabel);
+                    EditorGUILayout.BeginVertical("box", GUILayout.Height(position.height - 150f));
+                    {
+                        m_AssetsScroll = EditorGUILayout.BeginScrollView(m_AssetsScroll);
+                        {
+                            int selectedIndex = GUILayout.SelectionGrid(m_SelectedAssetIndex, m_CachedAssetNames, 1, "toggle");
+                            if (selectedIndex != m_SelectedAssetIndex)
+                            {
+                                m_SelectedAssetIndex = selectedIndex;
+                                m_SelectedAssetName = m_CachedAssetNames[selectedIndex];
+                                m_SelectedDependencyData = m_Controller.GetDependencyData(m_SelectedAssetName);
+                            }
+                        }
+                        EditorGUILayout.EndScrollView();
+                    }
+                    EditorGUILayout.EndVertical();
+                    EditorGUILayout.BeginVertical("box");
+                    {
+                        EditorGUILayout.LabelField("Asset Name", m_SelectedAssetName ?? "<None>");
+                        EditorGUILayout.LabelField("Resource Name", m_SelectedAssetName == null ? "<None>" : m_Controller.GetAsset(m_SelectedAssetName).Resource.FullName);
+                        EditorGUILayout.BeginHorizontal();
+                        {
+                            AssetsOrder assetsOrder = (AssetsOrder)EditorGUILayout.EnumPopup("Order by", m_AssetsOrder);
+                            if (assetsOrder != m_AssetsOrder)
+                            {
+                                m_AssetsOrder = assetsOrder;
+                                OnAssetsOrderOrFilterChanged();
+                            }
+                        }
+                        EditorGUILayout.EndHorizontal();
+                        EditorGUILayout.BeginHorizontal();
+                        {
+                            string assetsFilter = EditorGUILayout.TextField("Assets Filter", m_AssetsFilter);
+                            if (assetsFilter != m_AssetsFilter)
+                            {
+                                m_AssetsFilter = assetsFilter;
+                                OnAssetsOrderOrFilterChanged();
+                            }
+                            EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(m_AssetsFilter));
+                            {
+                                if (GUILayout.Button("x", GUILayout.Width(20f)))
+                                {
+                                    m_AssetsFilter = null;
+                                    GUI.FocusControl(null);
+                                    OnAssetsOrderOrFilterChanged();
+                                }
+                            }
+                            EditorGUI.EndDisabledGroup();
+                        }
+                        EditorGUILayout.EndHorizontal();
+                    }
+                    EditorGUILayout.EndVertical();
+                }
+                EditorGUILayout.EndVertical();
+                EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.6f - 14f));
+                {
+                    GUILayout.Space(5f);
+                    EditorGUILayout.LabelField(Utility.Text.Format("Dependency Resources ({0})", m_SelectedDependencyData.DependencyResourceCount.ToString()), EditorStyles.boldLabel);
+                    EditorGUILayout.BeginVertical("box", GUILayout.Height(position.height * 0.2f));
+                    {
+                        m_DependencyResourcesScroll = EditorGUILayout.BeginScrollView(m_DependencyResourcesScroll);
+                        {
+                            Resource[] dependencyResources = m_SelectedDependencyData.GetDependencyResources();
+                            foreach (Resource dependencyResource in dependencyResources)
+                            {
+                                GUILayout.Label(dependencyResource.FullName);
+                            }
+                        }
+                        EditorGUILayout.EndScrollView();
+                    }
+                    EditorGUILayout.EndVertical();
+                    EditorGUILayout.LabelField(Utility.Text.Format("Dependency Assets ({0})", m_SelectedDependencyData.DependencyAssetCount.ToString()), EditorStyles.boldLabel);
+                    EditorGUILayout.BeginVertical("box", GUILayout.Height(position.height * 0.3f));
+                    {
+                        m_DependencyAssetsScroll = EditorGUILayout.BeginScrollView(m_DependencyAssetsScroll);
+                        {
+                            Asset[] dependencyAssets = m_SelectedDependencyData.GetDependencyAssets();
+                            foreach (Asset dependencyAsset in dependencyAssets)
+                            {
+                                EditorGUILayout.BeginHorizontal();
+                                {
+                                    if (GUILayout.Button("GO", GUILayout.Width(30f)))
+                                    {
+                                        m_SelectedAssetName = dependencyAsset.Name;
+                                        m_SelectedAssetIndex = new List<string>(m_CachedAssetNames).IndexOf(m_SelectedAssetName);
+                                        m_SelectedDependencyData = m_Controller.GetDependencyData(m_SelectedAssetName);
+                                    }
+
+                                    GUILayout.Label(dependencyAsset.Name);
+                                }
+                                EditorGUILayout.EndHorizontal();
+                            }
+                        }
+                        EditorGUILayout.EndScrollView();
+                    }
+                    EditorGUILayout.EndVertical();
+                    EditorGUILayout.LabelField(Utility.Text.Format("Scattered Dependency Assets ({0})", m_SelectedDependencyData.ScatteredDependencyAssetCount.ToString()), EditorStyles.boldLabel);
+                    EditorGUILayout.BeginVertical("box", GUILayout.Height(position.height * 0.5f - 116f));
+                    {
+                        m_ScatteredDependencyAssetsScroll = EditorGUILayout.BeginScrollView(m_ScatteredDependencyAssetsScroll);
+                        {
+                            string[] scatteredDependencyAssetNames = m_SelectedDependencyData.GetScatteredDependencyAssetNames();
+                            foreach (string scatteredDependencyAssetName in scatteredDependencyAssetNames)
+                            {
+                                EditorGUILayout.BeginHorizontal();
+                                {
+                                    int count = m_Controller.GetHostAssets(scatteredDependencyAssetName).Length;
+                                    EditorGUI.BeginDisabledGroup(count < 2);
+                                    {
+                                        if (GUILayout.Button("GO", GUILayout.Width(30f)))
+                                        {
+                                            m_SelectedScatteredAssetName = scatteredDependencyAssetName;
+                                            m_SelectedScatteredAssetIndex = new List<string>(m_CachedScatteredAssetNames).IndexOf(m_SelectedScatteredAssetName);
+                                            m_SelectedHostAssets = m_Controller.GetHostAssets(m_SelectedScatteredAssetName);
+                                            m_ToolbarIndex = 2;
+                                            GUI.FocusControl(null);
+                                        }
+                                    }
+                                    EditorGUI.EndDisabledGroup();
+                                    GUILayout.Label(count > 1 ? Utility.Text.Format("{0} ({1})", scatteredDependencyAssetName, count.ToString()) : scatteredDependencyAssetName);
+                                }
+                                EditorGUILayout.EndHorizontal();
+                            }
+                        }
+                        EditorGUILayout.EndScrollView();
+                    }
+                    EditorGUILayout.EndVertical();
+                }
+                EditorGUILayout.EndVertical();
+            }
+            EditorGUILayout.EndHorizontal();
+        }
+
+        private void DrawScatteredAssetViewer()
+        {
+            if (!m_Analyzed)
+            {
+                DrawAnalyzeButton();
+                return;
+            }
+
+            EditorGUILayout.BeginHorizontal();
+            {
+                GUILayout.Space(5f);
+                EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.4f));
+                {
+                    GUILayout.Space(5f);
+                    string title = null;
+                    if (string.IsNullOrEmpty(m_ScatteredAssetsFilter))
+                    {
+                        title = Utility.Text.Format("Scattered Assets ({0})", m_ScatteredAssetCount.ToString());
+                    }
+                    else
+                    {
+                        title = Utility.Text.Format("Scattered Assets ({0}/{1})", m_CachedScatteredAssetNames.Length.ToString(), m_ScatteredAssetCount.ToString());
+                    }
+                    EditorGUILayout.LabelField(title, EditorStyles.boldLabel);
+                    EditorGUILayout.BeginVertical("box", GUILayout.Height(position.height - 132f));
+                    {
+                        m_ScatteredAssetsScroll = EditorGUILayout.BeginScrollView(m_ScatteredAssetsScroll);
+                        {
+                            int selectedIndex = GUILayout.SelectionGrid(m_SelectedScatteredAssetIndex, m_CachedScatteredAssetNames, 1, "toggle");
+                            if (selectedIndex != m_SelectedScatteredAssetIndex)
+                            {
+                                m_SelectedScatteredAssetIndex = selectedIndex;
+                                m_SelectedScatteredAssetName = m_CachedScatteredAssetNames[selectedIndex];
+                                m_SelectedHostAssets = m_Controller.GetHostAssets(m_SelectedScatteredAssetName);
+                            }
+                        }
+                        EditorGUILayout.EndScrollView();
+                    }
+                    EditorGUILayout.EndVertical();
+                    EditorGUILayout.BeginVertical("box");
+                    {
+                        EditorGUILayout.LabelField("Scattered Asset Name", m_SelectedScatteredAssetName ?? "<None>");
+                        EditorGUILayout.BeginHorizontal();
+                        {
+                            ScatteredAssetsOrder scatteredAssetsOrder = (ScatteredAssetsOrder)EditorGUILayout.EnumPopup("Order by", m_ScatteredAssetsOrder);
+                            if (scatteredAssetsOrder != m_ScatteredAssetsOrder)
+                            {
+                                m_ScatteredAssetsOrder = scatteredAssetsOrder;
+                                OnScatteredAssetsOrderOrFilterChanged();
+                            }
+                        }
+                        EditorGUILayout.EndHorizontal();
+                        EditorGUILayout.BeginHorizontal();
+                        {
+                            string scatteredAssetsFilter = EditorGUILayout.TextField("Assets Filter", m_ScatteredAssetsFilter);
+                            if (scatteredAssetsFilter != m_ScatteredAssetsFilter)
+                            {
+                                m_ScatteredAssetsFilter = scatteredAssetsFilter;
+                                OnScatteredAssetsOrderOrFilterChanged();
+                            }
+                            EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(m_ScatteredAssetsFilter));
+                            {
+                                if (GUILayout.Button("x", GUILayout.Width(20f)))
+                                {
+                                    m_ScatteredAssetsFilter = null;
+                                    GUI.FocusControl(null);
+                                    OnScatteredAssetsOrderOrFilterChanged();
+                                }
+                            }
+                            EditorGUI.EndDisabledGroup();
+                        }
+                        EditorGUILayout.EndHorizontal();
+                    }
+                    EditorGUILayout.EndVertical();
+                }
+                EditorGUILayout.EndVertical();
+                EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.6f - 14f));
+                {
+                    GUILayout.Space(5f);
+                    EditorGUILayout.LabelField(Utility.Text.Format("Host Assets ({0})", m_SelectedHostAssets.Length.ToString()), EditorStyles.boldLabel);
+                    EditorGUILayout.BeginVertical("box", GUILayout.Height(position.height - 68f));
+                    {
+                        m_HostAssetsScroll = EditorGUILayout.BeginScrollView(m_HostAssetsScroll);
+                        {
+                            foreach (Asset hostAsset in m_SelectedHostAssets)
+                            {
+                                EditorGUILayout.BeginHorizontal();
+                                {
+                                    if (GUILayout.Button("GO", GUILayout.Width(30f)))
+                                    {
+                                        m_SelectedAssetName = hostAsset.Name;
+                                        m_SelectedAssetIndex = new List<string>(m_CachedAssetNames).IndexOf(m_SelectedAssetName);
+                                        m_SelectedDependencyData = m_Controller.GetDependencyData(m_SelectedAssetName);
+                                        m_ToolbarIndex = 1;
+                                        GUI.FocusControl(null);
+                                    }
+
+                                    GUILayout.Label(Utility.Text.Format("{0} [{1}]", hostAsset.Name, hostAsset.Resource.FullName));
+                                }
+                                EditorGUILayout.EndHorizontal();
+                            }
+                        }
+                        EditorGUILayout.EndScrollView();
+                    }
+                    EditorGUILayout.EndVertical();
+                }
+                EditorGUILayout.EndVertical();
+            }
+            EditorGUILayout.EndHorizontal();
+        }
+
+        private void DrawCircularDependencyViewer()
+        {
+            if (!m_Analyzed)
+            {
+                DrawAnalyzeButton();
+                return;
+            }
+
+            EditorGUILayout.BeginHorizontal();
+            {
+                GUILayout.Space(5f);
+                EditorGUILayout.BeginVertical();
+                {
+                    GUILayout.Space(5f);
+                    EditorGUILayout.LabelField(Utility.Text.Format("Circular Dependency ({0})", m_CircularDependencyCount.ToString()), EditorStyles.boldLabel);
+                    m_CircularDependencyScroll = EditorGUILayout.BeginScrollView(m_CircularDependencyScroll);
+                    {
+                        int count = 0;
+                        foreach (string[] circularDependencyData in m_CachedCircularDependencyDatas)
+                        {
+                            GUILayout.Label(Utility.Text.Format("{0}) {1}", (++count).ToString(), circularDependencyData[circularDependencyData.Length - 1]), EditorStyles.boldLabel);
+                            EditorGUILayout.BeginVertical("box");
+                            {
+                                foreach (string circularDependency in circularDependencyData)
+                                {
+                                    EditorGUILayout.BeginHorizontal();
+                                    {
+                                        GUILayout.Label(circularDependency);
+                                        if (GUILayout.Button("GO", GUILayout.Width(30f)))
+                                        {
+                                            m_SelectedAssetName = circularDependency;
+                                            m_SelectedAssetIndex = new List<string>(m_CachedAssetNames).IndexOf(m_SelectedAssetName);
+                                            m_SelectedDependencyData = m_Controller.GetDependencyData(m_SelectedAssetName);
+                                            m_ToolbarIndex = 1;
+                                            GUI.FocusControl(null);
+                                        }
+                                    }
+                                    EditorGUILayout.EndHorizontal();
+                                }
+                            }
+                            EditorGUILayout.EndVertical();
+                            GUILayout.Space(5f);
+                        }
+                    }
+                    EditorGUILayout.EndScrollView();
+                }
+                EditorGUILayout.EndVertical();
+            }
+            EditorGUILayout.EndHorizontal();
+        }
+
+        private void OnAssetsOrderOrFilterChanged()
+        {
+            m_CachedAssetNames = m_Controller.GetAssetNames(m_AssetsOrder, m_AssetsFilter);
+            if (!string.IsNullOrEmpty(m_SelectedAssetName))
+            {
+                m_SelectedAssetIndex = new List<string>(m_CachedAssetNames).IndexOf(m_SelectedAssetName);
+            }
+        }
+
+        private void OnScatteredAssetsOrderOrFilterChanged()
+        {
+            m_CachedScatteredAssetNames = m_Controller.GetScatteredAssetNames(m_ScatteredAssetsOrder, m_ScatteredAssetsFilter);
+            if (!string.IsNullOrEmpty(m_SelectedScatteredAssetName))
+            {
+                m_SelectedScatteredAssetIndex = new List<string>(m_CachedScatteredAssetNames).IndexOf(m_SelectedScatteredAssetName);
+            }
+        }
+
+        private void OnLoadingResource(int index, int count)
+        {
+            EditorUtility.DisplayProgressBar("Loading Resources", Utility.Text.Format("Loading resources, {0}/{1} loaded.", index.ToString(), count.ToString()), (float)index / count);
+        }
+
+        private void OnLoadingAsset(int index, int count)
+        {
+            EditorUtility.DisplayProgressBar("Loading Assets", Utility.Text.Format("Loading assets, {0}/{1} loaded.", index.ToString(), count.ToString()), (float)index / count);
+        }
+
+        private void OnLoadCompleted()
+        {
+            EditorUtility.ClearProgressBar();
+        }
+
+        private void OnAnalyzingAsset(int index, int count)
+        {
+            EditorUtility.DisplayProgressBar("Analyzing Assets", Utility.Text.Format("Analyzing assets, {0}/{1} analyzed.", index.ToString(), count.ToString()), (float)index / count);
+        }
+
+        private void OnAnalyzeCompleted()
+        {
+            EditorUtility.ClearProgressBar();
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzer.cs.meta

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

+ 76 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.CircularDependencyChecker.cs

@@ -0,0 +1,76 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace UnityGameFramework.Editor.ResourceTools
+{
+    public sealed partial class ResourceAnalyzerController
+    {
+        private sealed class CircularDependencyChecker
+        {
+            private readonly Stamp[] m_Stamps;
+
+            public CircularDependencyChecker(Stamp[] stamps)
+            {
+                m_Stamps = stamps;
+            }
+
+            public string[][] Check()
+            {
+                HashSet<string> hosts = new HashSet<string>();
+                foreach (Stamp stamp in m_Stamps)
+                {
+                    hosts.Add(stamp.HostAssetName);
+                }
+
+                List<string[]> results = new List<string[]>();
+                foreach (string host in hosts)
+                {
+                    LinkedList<string> route = new LinkedList<string>();
+                    HashSet<string> visited = new HashSet<string>();
+                    if (Check(host, route, visited))
+                    {
+                        results.Add(route.ToArray());
+                    }
+                }
+
+                return results.ToArray();
+            }
+
+            private bool Check(string host, LinkedList<string> route, HashSet<string> visited)
+            {
+                visited.Add(host);
+                route.AddLast(host);
+
+                foreach (Stamp stamp in m_Stamps)
+                {
+                    if (host != stamp.HostAssetName)
+                    {
+                        continue;
+                    }
+
+                    if (visited.Contains(stamp.DependencyAssetName))
+                    {
+                        route.AddLast(stamp.DependencyAssetName);
+                        return true;
+                    }
+
+                    if (Check(stamp.DependencyAssetName, route, visited))
+                    {
+                        return true;
+                    }
+                }
+
+                route.RemoveLast();
+                visited.Remove(host);
+                return false;
+            }
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.CircularDependencyChecker.cs.meta

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

+ 43 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.Stamp.cs

@@ -0,0 +1,43 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using System.Runtime.InteropServices;
+
+namespace UnityGameFramework.Editor.ResourceTools
+{
+    public sealed partial class ResourceAnalyzerController
+    {
+        [StructLayout(LayoutKind.Auto)]
+        private struct Stamp
+        {
+            private readonly string m_HostAssetName;
+            private readonly string m_DependencyAssetName;
+
+            public Stamp(string hostAssetName, string dependencyAssetName)
+            {
+                m_HostAssetName = hostAssetName;
+                m_DependencyAssetName = dependencyAssetName;
+            }
+
+            public string HostAssetName
+            {
+                get
+                {
+                    return m_HostAssetName;
+                }
+            }
+
+            public string DependencyAssetName
+            {
+                get
+                {
+                    return m_DependencyAssetName;
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.Stamp.cs.meta

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

+ 324 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.cs

@@ -0,0 +1,324 @@
+//------------------------------------------------------------
+// Game Framework
+// Copyright © 2013-2021 Jiang Yin. All rights reserved.
+// Homepage: https://gameframework.cn/
+// Feedback: mailto:ellan@gameframework.cn
+//------------------------------------------------------------
+
+using GameFramework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+
+namespace UnityGameFramework.Editor.ResourceTools
+{
+    public sealed partial class ResourceAnalyzerController
+    {
+        private readonly ResourceCollection m_ResourceCollection;
+
+        private readonly Dictionary<string, DependencyData> m_DependencyDatas;
+        private readonly Dictionary<string, List<Asset>> m_ScatteredAssets;
+        private readonly List<string[]> m_CircularDependencyDatas;
+        private readonly HashSet<Stamp> m_AnalyzedStamps;
+
+        public ResourceAnalyzerController()
+            : this(null)
+        {
+        }
+
+        public ResourceAnalyzerController(ResourceCollection resourceCollection)
+        {
+            m_ResourceCollection = resourceCollection != null ? resourceCollection : new ResourceCollection();
+
+            m_ResourceCollection.OnLoadingResource += delegate (int index, int count)
+            {
+                if (OnLoadingResource != null)
+                {
+                    OnLoadingResource(index, count);
+                }
+            };
+
+            m_ResourceCollection.OnLoadingAsset += delegate (int index, int count)
+            {
+                if (OnLoadingAsset != null)
+                {
+                    OnLoadingAsset(index, count);
+                }
+            };
+
+            m_ResourceCollection.OnLoadCompleted += delegate ()
+            {
+                if (OnLoadCompleted != null)
+                {
+                    OnLoadCompleted();
+                }
+            };
+
+            m_DependencyDatas = new Dictionary<string, DependencyData>(StringComparer.Ordinal);
+            m_ScatteredAssets = new Dictionary<string, List<Asset>>(StringComparer.Ordinal);
+            m_AnalyzedStamps = new HashSet<Stamp>();
+            m_CircularDependencyDatas = new List<string[]>();
+        }
+
+        public event GameFrameworkAction<int, int> OnLoadingResource = null;
+
+        public event GameFrameworkAction<int, int> OnLoadingAsset = null;
+
+        public event GameFrameworkAction OnLoadCompleted = null;
+
+        public event GameFrameworkAction<int, int> OnAnalyzingAsset = null;
+
+        public event GameFrameworkAction OnAnalyzeCompleted = null;
+
+        public void Clear()
+        {
+            m_ResourceCollection.Clear();
+            m_DependencyDatas.Clear();
+            m_ScatteredAssets.Clear();
+            m_CircularDependencyDatas.Clear();
+            m_AnalyzedStamps.Clear();
+        }
+
+        public bool Prepare()
+        {
+            m_ResourceCollection.Clear();
+            return m_ResourceCollection.Load();
+        }
+
+        public void Analyze()
+        {
+            m_DependencyDatas.Clear();
+            m_ScatteredAssets.Clear();
+            m_CircularDependencyDatas.Clear();
+            m_AnalyzedStamps.Clear();
+
+            HashSet<string> scriptAssetNames = GetFilteredAssetNames("t:Script");
+            Asset[] assets = m_ResourceCollection.GetAssets();
+            int count = assets.Length;
+            for (int i = 0; i < count; i++)
+            {
+                if (OnAnalyzingAsset != null)
+                {
+                    OnAnalyzingAsset(i, count);
+                }
+
+                string assetName = assets[i].Name;
+                if (string.IsNullOrEmpty(assetName))
+                {
+                    Debug.LogWarning(Utility.Text.Format("Can not find asset by guid '{0}'.", assets[i].Guid));
+                    continue;
+                }
+
+                DependencyData dependencyData = new DependencyData();
+                AnalyzeAsset(assetName, assets[i], dependencyData, scriptAssetNames);
+                dependencyData.RefreshData();
+                m_DependencyDatas.Add(assetName, dependencyData);
+            }
+
+            foreach (List<Asset> scatteredAsset in m_ScatteredAssets.Values)
+            {
+                scatteredAsset.Sort((a, b) => a.Name.CompareTo(b.Name));
+            }
+
+            m_CircularDependencyDatas.AddRange(new CircularDependencyChecker(m_AnalyzedStamps.ToArray()).Check());
+
+            if (OnAnalyzeCompleted != null)
+            {
+                OnAnalyzeCompleted();
+            }
+        }
+
+        private void AnalyzeAsset(string assetName, Asset hostAsset, DependencyData dependencyData, HashSet<string> scriptAssetNames)
+        {
+            string[] dependencyAssetNames = AssetDatabase.GetDependencies(assetName, false);
+            foreach (string dependencyAssetName in dependencyAssetNames)
+            {
+                if (scriptAssetNames.Contains(dependencyAssetName))
+                {
+                    continue;
+                }
+
+                if (dependencyAssetName == assetName)
+                {
+                    continue;
+                }
+
+                if (dependencyAssetName.EndsWith(".unity", StringComparison.Ordinal))
+                {
+                    // 忽略对场景的依赖
+                    continue;
+                }
+
+                Stamp stamp = new Stamp(hostAsset.Name, dependencyAssetName);
+                if (m_AnalyzedStamps.Contains(stamp))
+                {
+                    continue;
+                }
+
+                m_AnalyzedStamps.Add(stamp);
+
+                string guid = AssetDatabase.AssetPathToGUID(dependencyAssetName);
+                if (string.IsNullOrEmpty(guid))
+                {
+                    Debug.LogWarning(Utility.Text.Format("Can not find guid by asset '{0}'.", dependencyAssetName));
+                    continue;
+                }
+
+                Asset asset = m_ResourceCollection.GetAsset(guid);
+                if (asset != null)
+                {
+                    dependencyData.AddDependencyAsset(asset);
+                }
+                else
+                {
+                    dependencyData.AddScatteredDependencyAsset(dependencyAssetName);
+
+                    List<Asset> scatteredAssets = null;
+                    if (!m_ScatteredAssets.TryGetValue(dependencyAssetName, out scatteredAssets))
+                    {
+                        scatteredAssets = new List<Asset>();
+                        m_ScatteredAssets.Add(dependencyAssetName, scatteredAssets);
+                    }
+
+                    scatteredAssets.Add(hostAsset);
+
+                    AnalyzeAsset(dependencyAssetName, hostAsset, dependencyData, scriptAssetNames);
+                }
+            }
+        }
+
+        public Asset GetAsset(string assetName)
+        {
+            return m_ResourceCollection.GetAsset(AssetDatabase.AssetPathToGUID(assetName));
+        }
+
+        public string[] GetAssetNames()
+        {
+            return GetAssetNames(AssetsOrder.AssetNameAsc, null);
+        }
+
+        public string[] GetAssetNames(AssetsOrder order, string filter)
+        {
+            HashSet<string> filteredAssetNames = GetFilteredAssetNames(filter);
+            IEnumerable<KeyValuePair<string, DependencyData>> filteredResult = m_DependencyDatas.Where(pair => filteredAssetNames.Contains(pair.Key));
+            IEnumerable<KeyValuePair<string, DependencyData>> orderedResult = null;
+            switch (order)
+            {
+                case AssetsOrder.AssetNameAsc:
+                    orderedResult = filteredResult.OrderBy(pair => pair.Key);
+                    break;
+
+                case AssetsOrder.AssetNameDesc:
+                    orderedResult = filteredResult.OrderByDescending(pair => pair.Key);
+                    break;
+
+                case AssetsOrder.DependencyResourceCountAsc:
+                    orderedResult = filteredResult.OrderBy(pair => pair.Value.DependencyResourceCount);
+                    break;
+
+                case AssetsOrder.DependencyResourceCountDesc:
+                    orderedResult = filteredResult.OrderByDescending(pair => pair.Value.DependencyResourceCount);
+                    break;
+
+                case AssetsOrder.DependencyAssetCountAsc:
+                    orderedResult = filteredResult.OrderBy(pair => pair.Value.DependencyAssetCount);
+                    break;
+
+                case AssetsOrder.DependencyAssetCountDesc:
+                    orderedResult = filteredResult.OrderByDescending(pair => pair.Value.DependencyAssetCount);
+                    break;
+
+                case AssetsOrder.ScatteredDependencyAssetCountAsc:
+                    orderedResult = filteredResult.OrderBy(pair => pair.Value.ScatteredDependencyAssetCount);
+                    break;
+
+                case AssetsOrder.ScatteredDependencyAssetCountDesc:
+                    orderedResult = filteredResult.OrderByDescending(pair => pair.Value.ScatteredDependencyAssetCount);
+                    break;
+
+                default:
+                    orderedResult = filteredResult;
+                    break;
+            }
+
+            return orderedResult.Select(pair => pair.Key).ToArray();
+        }
+
+        public DependencyData GetDependencyData(string assetName)
+        {
+            DependencyData dependencyData = null;
+            if (m_DependencyDatas.TryGetValue(assetName, out dependencyData))
+            {
+                return dependencyData;
+            }
+
+            return dependencyData;
+        }
+
+        public string[] GetScatteredAssetNames()
+        {
+            return GetScatteredAssetNames(ScatteredAssetsOrder.HostAssetCountDesc, null);
+        }
+
+        public string[] GetScatteredAssetNames(ScatteredAssetsOrder order, string filter)
+        {
+            HashSet<string> filterAssetNames = GetFilteredAssetNames(filter);
+            IEnumerable<KeyValuePair<string, List<Asset>>> filteredResult = m_ScatteredAssets.Where(pair => filterAssetNames.Contains(pair.Key) && pair.Value.Count > 1);
+            IEnumerable<KeyValuePair<string, List<Asset>>> orderedResult = null;
+            switch (order)
+            {
+                case ScatteredAssetsOrder.AssetNameAsc:
+                    orderedResult = filteredResult.OrderBy(pair => pair.Key);
+                    break;
+
+                case ScatteredAssetsOrder.AssetNameDesc:
+                    orderedResult = filteredResult.OrderByDescending(pair => pair.Key);
+                    break;
+
+                case ScatteredAssetsOrder.HostAssetCountAsc:
+                    orderedResult = filteredResult.OrderBy(pair => pair.Value.Count);
+                    break;
+
+                case ScatteredAssetsOrder.HostAssetCountDesc:
+                    orderedResult = filteredResult.OrderByDescending(pair => pair.Value.Count);
+                    break;
+
+                default:
+                    orderedResult = filteredResult;
+                    break;
+            }
+
+            return orderedResult.Select(pair => pair.Key).ToArray();
+        }
+
+        public Asset[] GetHostAssets(string scatteredAssetName)
+        {
+            List<Asset> assets = null;
+            if (m_ScatteredAssets.TryGetValue(scatteredAssetName, out assets))
+            {
+                return assets.ToArray();
+            }
+
+            return null;
+        }
+
+        public string[][] GetCircularDependencyDatas()
+        {
+            return m_CircularDependencyDatas.ToArray();
+        }
+
+        private HashSet<string> GetFilteredAssetNames(string filter)
+        {
+            string[] filterAssetGuids = AssetDatabase.FindAssets(filter);
+            HashSet<string> filterAssetNames = new HashSet<string>();
+            foreach (string filterAssetGuid in filterAssetGuids)
+            {
+                filterAssetNames.Add(AssetDatabase.GUIDToAssetPath(filterAssetGuid));
+            }
+
+            return filterAssetNames;
+        }
+    }
+}

+ 0 - 0
Assets/GameFramework/Scripts/Editor/ResourceAnalyzer/ResourceAnalyzerController.cs.meta


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません