guodong 1 жил өмнө
parent
commit
5cea5a65e6
70 өөрчлөгдсөн 2379 нэмэгдсэн , 75 устгасан
  1. 2 2
      GameClient/Assets/Game/HotUpdate/Views/Card/CardMoodView.cs
  2. 1 1
      GameClient/Assets/Game/HotUpdate/Views/Login/LoginView.cs
  3. 1 1
      GameClient/Assets/Game/HotUpdate/Views/MainStory/ArenaFightResultView.cs
  4. 1 1
      GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryChapterView.cs
  5. 2 2
      GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryDialogView.cs
  6. 1 1
      GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryFightSingleView.cs
  7. 2 2
      GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryFightTargetView.cs
  8. 1 1
      GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryLookBackView.cs
  9. 1 1
      GameClient/Assets/Game/HotUpdate/Views/MainStory/StroyFightResultView.cs
  10. 0 0
      GameClient/Assets/Game/Launcher/GFGAsset.meta
  11. 0 0
      GameClient/Assets/Game/Launcher/GFGAsset/AssetReleaser.cs
  12. 0 0
      GameClient/Assets/Game/Launcher/GFGAsset/AssetReleaser.cs.meta
  13. 0 16
      GameClient/Assets/Game/Launcher/GFGAsset/GFGAsset.cs
  14. 0 0
      GameClient/Assets/Game/Launcher/GFGAsset/GFGAsset.cs.meta
  15. 32 0
      GameClient/Assets/Game/Launcher/GFGAsset/GFGSpawner.cs
  16. 11 0
      GameClient/Assets/Game/Launcher/GFGAsset/GFGSpawner.cs.meta
  17. 2 1
      GameClient/Assets/Game/Launcher/Game.Launcher.asmdef
  18. 36 32
      GameClient/Assets/Game/Launcher/Sound/MusicManager.cs
  19. 22 14
      GameClient/Assets/Game/Launcher/Sound/SoundManager.cs
  20. 8 0
      GameClient/Assets/UniFramework.meta
  21. 8 0
      GameClient/Assets/UniFramework/UniMachine.meta
  22. 3 0
      GameClient/Assets/UniFramework/UniMachine/README.md
  23. 7 0
      GameClient/Assets/UniFramework/UniMachine/README.md.meta
  24. 8 0
      GameClient/Assets/UniFramework/UniMachine/Runtime.meta
  25. 11 0
      GameClient/Assets/UniFramework/UniMachine/Runtime/IStateNode.cs
  26. 11 0
      GameClient/Assets/UniFramework/UniMachine/Runtime/IStateNode.cs.meta
  27. 142 0
      GameClient/Assets/UniFramework/UniMachine/Runtime/StateMachine.cs
  28. 11 0
      GameClient/Assets/UniFramework/UniMachine/Runtime/StateMachine.cs.meta
  29. 16 0
      GameClient/Assets/UniFramework/UniMachine/Runtime/UniFramework.Machine.asmdef
  30. 7 0
      GameClient/Assets/UniFramework/UniMachine/Runtime/UniFramework.Machine.asmdef.meta
  31. 21 0
      GameClient/Assets/UniFramework/UniMachine/Runtime/UniLogger.cs
  32. 11 0
      GameClient/Assets/UniFramework/UniMachine/Runtime/UniLogger.cs.meta
  33. 8 0
      GameClient/Assets/UniFramework/UniPooling.meta
  34. 36 0
      GameClient/Assets/UniFramework/UniPooling/README.md
  35. 7 0
      GameClient/Assets/UniFramework/UniPooling/README.md.meta
  36. 8 0
      GameClient/Assets/UniFramework/UniPooling/Runtime.meta
  37. 70 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/CreatePoolOperation.cs
  38. 11 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/CreatePoolOperation.cs.meta
  39. 222 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/GameObjectPool.cs
  40. 11 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/GameObjectPool.cs.meta
  41. 148 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/SpawnHandle.cs
  42. 11 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/SpawnHandle.cs.meta
  43. 259 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/Spawner.cs
  44. 11 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/Spawner.cs.meta
  45. 16 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/UniFramework.Pooling.asmdef
  46. 7 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/UniFramework.Pooling.asmdef.meta
  47. 21 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/UniLogger.cs
  48. 11 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/UniLogger.cs.meta
  49. 127 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/UniPooling.cs
  50. 11 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/UniPooling.cs.meta
  51. 12 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/UniPoolingDriver.cs
  52. 11 0
      GameClient/Assets/UniFramework/UniPooling/Runtime/UniPoolingDriver.cs.meta
  53. 8 0
      GameClient/Assets/UniFramework/UniWindow.meta
  54. 4 0
      GameClient/Assets/UniFramework/UniWindow/README.md
  55. 7 0
      GameClient/Assets/UniFramework/UniWindow/README.md.meta
  56. 8 0
      GameClient/Assets/UniFramework/UniWindow/Runtime.meta
  57. 70 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/OpenWindowOperation.cs
  58. 11 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/OpenWindowOperation.cs.meta
  59. 325 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UIWindow.cs
  60. 11 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UIWindow.cs.meta
  61. 16 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UniFramework.Window.asmdef
  62. 7 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UniFramework.Window.asmdef.meta
  63. 21 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UniLogger.cs
  64. 11 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UniLogger.cs.meta
  65. 414 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UniWindow.cs
  66. 11 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UniWindow.cs.meta
  67. 12 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UniWindowDriver.cs
  68. 11 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/UniWindowDriver.cs.meta
  69. 24 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/WindowAttribute.cs
  70. 11 0
      GameClient/Assets/UniFramework/UniWindow/Runtime/WindowAttribute.cs.meta

+ 2 - 2
GameClient/Assets/Game/HotUpdate/Views/Card/CardMoodView.cs

@@ -75,7 +75,7 @@ namespace GFGGame
         }
         private void PlayMusic()
         {
-            MusicManager.Instance.PlayByUrl(ResPathUtil.GetCardSoundPath(_viewData.itemCfg.cardMoodSound), true);
+            SoundManager.Instance.PlayOneShotCroutine(ResPathUtil.GetCardSoundPath(_viewData.itemCfg.cardMoodSound));
 
 
         }
@@ -95,7 +95,7 @@ namespace GFGGame
             base.OnHide();
             _ui.m_t1.Stop();
             MusicManager.Instance.Stop();
-            MusicManager.Instance.Play(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
+            MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
         }
     }
 }

+ 1 - 1
GameClient/Assets/Game/HotUpdate/Views/Login/LoginView.cs

@@ -66,7 +66,7 @@ namespace GFGGame
         protected override void OnShown()
         {
             base.OnShown();
-            MusicManager.Instance.Play(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
+            MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
             if (_sceneObject == null)
             {
                 _sceneObject = GFGAsset.Load<GameObject>(ResPathUtil.GetLoginResPath("LoginSkin1/SceneLogin"));

+ 1 - 1
GameClient/Assets/Game/HotUpdate/Views/MainStory/ArenaFightResultView.cs

@@ -116,7 +116,7 @@ namespace GFGGame
                 _sceneObject = null;
             }
 
-            MusicManager.Instance.Play(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
+            MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
 
             // _ui.m_comResult.target.height = 0;
             _ui.m_comResult.m_t1.Play();

+ 1 - 1
GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryChapterView.cs

@@ -81,7 +81,7 @@ namespace GFGGame
         {
             base.OnShown();
             _ui.target.touchable = false;
-            MusicManager.Instance.Play(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
+            MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
             _chapterID = (int)viewData;
             MainStoryDataManager.currentChapterCfgId = _chapterID;
 

+ 2 - 2
GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryDialogView.cs

@@ -173,7 +173,7 @@ namespace GFGGame
                 GameObject.Destroy(_sceneObject);
                 _sceneObject = null;
             }
-            MusicManager.Instance.Play(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
+            MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
             _onCompleteStoryDialogCall = null;
             _onCompleteStoryDialogCallParam = null;
             StoryDialogDataManager.Instance.Clear();
@@ -687,7 +687,7 @@ namespace GFGGame
                 }
                 else
                 {
-                    MusicManager.Instance.Play(ResPathUtil.GetMusicPath(value, "mp3"));
+                    MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(value, "mp3"));
                 }
             }
         }

+ 1 - 1
GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryFightSingleView.cs

@@ -50,7 +50,7 @@ namespace GFGGame
             StoryFightCfg fightCfg = StoryFightCfgArray.Instance.GetCfg(levelCfg.fightID);
             if (!string.IsNullOrEmpty(fightCfg.music))
             {
-                MusicManager.Instance.Play(ResPathUtil.GetMusicPath(fightCfg.music, "mp3"));
+                MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(fightCfg.music, "mp3"));
             }
             _ui.m_roleName.m_txtName.text = RoleDataManager.roleName;
             Timers.inst.Add(0.9f, 1, (object param) =>

+ 2 - 2
GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryFightTargetView.cs

@@ -94,7 +94,7 @@ namespace GFGGame
 
                     _targetDressUpObj.PutOnItemList(targetFightData.itemList);
                 }
-                MusicManager.Instance.Play(ResPathUtil.GetMusicPath("fight", "mp3"));
+                MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath("fight", "mp3"));
 
             }
             else
@@ -113,7 +113,7 @@ namespace GFGGame
                 StoryFightCfg fightCfg = StoryFightCfgArray.Instance.GetCfg(levelCfg.fightID);
                 if (!string.IsNullOrEmpty(fightCfg.music))
                 {
-                    MusicManager.Instance.Play(ResPathUtil.GetMusicPath(fightCfg.music, "mp3"));
+                    MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(fightCfg.music, "mp3"));
                 }
             }
 

+ 1 - 1
GameClient/Assets/Game/HotUpdate/Views/MainStory/StoryLookBackView.cs

@@ -230,7 +230,7 @@ namespace GFGGame
                 }
                 else
                 {
-                    MusicManager.Instance.Play(ResPathUtil.GetMusicPath(value, "mp3"));
+                    MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(value, "mp3"));
                 }
             }
         }

+ 1 - 1
GameClient/Assets/Game/HotUpdate/Views/MainStory/StroyFightResultView.cs

@@ -149,7 +149,7 @@ namespace GFGGame
                 _sceneObject = null;
             }
             InstanceZonesDataManager.isResultFighting = false;
-            MusicManager.Instance.Play(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
+            MusicManager.Instance.PlayCroutine(ResPathUtil.GetMusicPath(ConstMusicName.DEFAULT));
             _ui.m_comResult.m_t1.Play();
             _ui.m_comExpBar.m_proExp.m_comHolder.target.width = 0;
 

+ 0 - 0
GameClient/Assets/Game/Launcher/Xasset.meta → GameClient/Assets/Game/Launcher/GFGAsset.meta


+ 0 - 0
GameClient/Assets/Game/Launcher/Xasset/AssetReleaser.cs → GameClient/Assets/Game/Launcher/GFGAsset/AssetReleaser.cs


+ 0 - 0
GameClient/Assets/Game/Launcher/Xasset/AssetReleaser.cs.meta → GameClient/Assets/Game/Launcher/GFGAsset/AssetReleaser.cs.meta


+ 0 - 16
GameClient/Assets/Game/Launcher/Xasset/GFGAsset.cs → GameClient/Assets/Game/Launcher/GFGAsset/GFGAsset.cs

@@ -48,21 +48,5 @@ namespace GFGGame
             return t;
         }
 
-        public static void Release(string path)
-        {
-            //VEngine.Logger.I($"GFGAsset.Release {path} {Random.Range(0, int.MaxValue)}");
-            if (path.Contains("Asset"))
-            {
-                //Asset asset;
-                //if (Asset.Cache.TryGetValue(path, out asset))
-                //{
-                //    asset.Release();
-                //}
-            }
-            else
-            {
-
-            }
-        }
     }
 }

+ 0 - 0
GameClient/Assets/Game/Launcher/Xasset/GFGAsset.cs.meta → GameClient/Assets/Game/Launcher/GFGAsset/GFGAsset.cs.meta


+ 32 - 0
GameClient/Assets/Game/Launcher/GFGAsset/GFGSpawner.cs

@@ -0,0 +1,32 @@
+using UniFramework.Pooling;
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace GFGGame
+{
+    public class GFGSpawner
+    {
+        private Spawner _entitySpawner;
+        private Dictionary<string, bool> poolCreated = new Dictionary<string, bool>();
+
+        public void Init()
+        {
+            // 创建游戏对象发生器
+            _entitySpawner = UniPooling.CreateSpawner(VersionController.DefaultPackage);
+        }
+
+        public GameObject SpawnSync(string resPath)
+        {
+            poolCreated.TryGetValue(resPath, out bool created);
+            if (!created)
+            {
+                // 创建游戏对象池
+                _entitySpawner.CreateGameObjectPoolAsync(resPath);
+            }
+
+            var handle = _entitySpawner.SpawnSync(resPath);
+            return handle.GameObj;
+        }
+
+    }
+}

+ 11 - 0
GameClient/Assets/Game/Launcher/GFGAsset/GFGSpawner.cs.meta

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

+ 2 - 1
GameClient/Assets/Game/Launcher/Game.Launcher.asmdef

@@ -4,7 +4,8 @@
     "references": [
         "GUID:7a41fac89c3ce014e99efb3723e6a98e",
         "GUID:13ba8ce62aa80c74598530029cb2d649",
-        "GUID:e34a5702dd353724aa315fb8011f08c3"
+        "GUID:e34a5702dd353724aa315fb8011f08c3",
+        "GUID:b0a84d582f6a6fa4185f67ce934d99c2"
     ],
     "includePlatforms": [],
     "excludePlatforms": [],

+ 36 - 32
GameClient/Assets/Game/Launcher/Sound/MusicManager.cs

@@ -1,12 +1,17 @@
-using System;
+using System.Collections;
 using UnityEngine;
 using GFGGame.Launcher;
+using YooAsset;
+
 namespace GFGGame
 {
     public class MusicManager : SingletonMonoBase<MusicManager>
     {
-        private AudioSource _player;
-        private string _currentName;
+        private AudioSource player;
+        private string currentName;
+        private AssetOperationHandle handle;
+        private Coroutine coroutine;
+
         private bool _isOn = true;
         public bool isOn
         {
@@ -21,9 +26,9 @@ namespace GFGGame
                     _isOn = value;
                     if(_isOn)
                     {
-                        if(_currentName != null)
+                        if(currentName != null)
                         {
-                            Play(_currentName, true);
+                            PlayCroutine(currentName, true);
                         }
                     }
                     else
@@ -37,8 +42,8 @@ namespace GFGGame
 
         private void Awake()
         {
-            _player = this.gameObject.AddComponent<AudioSource>();
-            _player.loop = true;
+            player = this.gameObject.AddComponent<AudioSource>();
+            player.loop = true;
         }
 
         private void Start() 
@@ -51,53 +56,52 @@ namespace GFGGame
             
         }
 
-        public void Play(string path, bool must = false)
+        public void PlayCroutine(string path, bool must = false)
         {
-            if(_currentName != path || must)
+            if(currentName != path || must)
             {
-                _currentName = path;
-                if(_isOn)
+                currentName = path;
+                if(coroutine != null)
                 {
-                    AudioClip clip = GFGAsset.Load<AudioClip>(path);
-                    _player.clip = clip;
-                    _player.Play();
+                    StopCoroutine(coroutine);
+                }
+                handle?.Release();
+                if (_isOn)
+                {
+                    coroutine = StartCoroutine(Play(path));
                 }
             }
         }
-        public void PlayByUrl(string url, bool must = false)
-        {
-            if (_currentName != name || must)
-            {
 
-                _currentName = name;
-                //如果是播人声是不受音乐开关影响
-                //if (_isOn)
-                //{
-                    AudioClip clip = GFGAsset.Load<AudioClip>(url);
-                    _player.clip = clip;
-                    _player.PlayOneShot(clip);
-                //}
-            }
+        private IEnumerator Play(string path)
+        {
+            //AudioClip clip = GFGAsset.Load<AudioClip>(path);
+            handle = YooAssets.LoadAssetAsync<AudioClip>(path);
+            yield return handle;
+            player.clip = handle.AssetObject as AudioClip;
+            player.Play();
         }
+
         public float GetSoundTime()
         {
-            if (_player.clip != null)
+            if (player.clip != null)
             {
-                return _player.clip.length;
+                return player.clip.length;
             }
             return 0;
         }
         public void Pause()
         {
-            _player.Pause();
+            player.Pause();
         } 
         public void UnPause()
         {
-            _player.UnPause();
+            player.UnPause();
         }
         public void Stop()
         {
-            _player.Stop();
+            handle.Release();
+            player.Stop();
         }
     }
 }

+ 22 - 14
GameClient/Assets/Game/Launcher/Sound/SoundManager.cs

@@ -1,12 +1,17 @@
 using UnityEngine;
 using FairyGUI;
 using GFGGame.Launcher;
+using YooAsset;
+using System.Collections;
 
 namespace GFGGame
 {
     public class SoundManager : SingletonMonoBase<SoundManager> 
     {
-        private AudioSource _player;
+        private AudioSource player;
+        private AssetOperationHandle handle;
+        private Coroutine coroutine;
+
         private bool _isOn = true;
         public bool isOn
         {
@@ -35,7 +40,7 @@ namespace GFGGame
 
         private void Awake()
         {
-            _player = this.gameObject.AddComponent<AudioSource>();
+            player = this.gameObject.AddComponent<AudioSource>();
         }
 
         private void Start() 
@@ -48,28 +53,31 @@ namespace GFGGame
             
         }
 
-        public void PlayOneShot(string Path)
+        public void PlayOneShotCroutine(string path)
         {
-            if(_isOn)
+            if (coroutine != null)
             {
-                AudioClip clip = GFGAsset.Load<AudioClip>(Path);
-                _player.clip = clip;
-                _player.PlayOneShot(clip);
+                StopCoroutine(coroutine);
             }
-        }
-
-        public void PlayClipAtPoint(string path, Vector3 position)
-        {
+            handle?.Release();
             if (_isOn)
             {
-                AudioClip clip = GFGAsset.Load<AudioClip>(path);
-                AudioSource.PlayClipAtPoint(clip, position);
+                coroutine = StartCoroutine(PlayOneShot(path));
             }
         }
 
+        private IEnumerator PlayOneShot(string path)
+        {
+            //AudioClip clip = GFGAsset.Load<AudioClip>(path);
+            handle = YooAssets.LoadAssetAsync<AudioClip>(path);
+            yield return handle;
+            player.clip = handle.AssetObject as AudioClip;
+            player.Play();
+        }
+
         public void Stop()
         {
-            _player.Stop();
+            player.Stop();
         }
 
     }

+ 8 - 0
GameClient/Assets/UniFramework.meta

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

+ 8 - 0
GameClient/Assets/UniFramework/UniMachine.meta

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

+ 3 - 0
GameClient/Assets/UniFramework/UniMachine/README.md

@@ -0,0 +1,3 @@
+# UniFramework.Machine
+
+一个轻量级的状态机。

+ 7 - 0
GameClient/Assets/UniFramework/UniMachine/README.md.meta

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

+ 8 - 0
GameClient/Assets/UniFramework/UniMachine/Runtime.meta

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

+ 11 - 0
GameClient/Assets/UniFramework/UniMachine/Runtime/IStateNode.cs

@@ -0,0 +1,11 @@
+
+namespace UniFramework.Machine
+{
+	public interface IStateNode
+	{
+		void OnCreate(StateMachine machine);
+		void OnEnter();
+		void OnUpdate();
+		void OnExit();
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniMachine/Runtime/IStateNode.cs.meta

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

+ 142 - 0
GameClient/Assets/UniFramework/UniMachine/Runtime/StateMachine.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace UniFramework.Machine
+{
+	public class StateMachine
+	{
+		private readonly Dictionary<string, IStateNode> _nodes = new Dictionary<string, IStateNode>(100);
+		private IStateNode _curNode;
+		private IStateNode _preNode;
+
+		/// <summary>
+		/// 状态机持有者
+		/// </summary>
+		public System.Object Owner { private set; get; }
+
+		/// <summary>
+		/// 当前运行的节点名称
+		/// </summary>
+		public string CurrentNode
+		{
+			get { return _curNode != null ? _curNode.GetType().FullName : string.Empty; }
+		}
+
+		/// <summary>
+		/// 之前运行的节点名称
+		/// </summary>
+		public string PreviousNode
+		{
+			get { return _preNode != null ? _preNode.GetType().FullName : string.Empty; }
+		}
+
+
+		private StateMachine() { }
+		public StateMachine(System.Object owner)
+		{
+			Owner = owner;
+		}
+
+		/// <summary>
+		/// 更新状态机
+		/// </summary>
+		public void Update()
+		{
+			if (_curNode != null)
+				_curNode.OnUpdate();
+		}
+
+		/// <summary>
+		/// 启动状态机
+		/// </summary>
+		public void Run<TNode>() where TNode : IStateNode
+		{
+			var nodeType = typeof(TNode);
+			var nodeName = nodeType.FullName;
+			Run(nodeName);
+		}
+		public void Run(Type entryNode)
+		{
+			var nodeName = entryNode.FullName;
+			Run(nodeName);
+		}
+		public void Run(string entryNode)
+		{
+			_curNode = TryGetNode(entryNode);
+			_preNode = _curNode;
+
+			if (_curNode == null)
+				throw new Exception($"Not found entry node: {entryNode }");
+
+			_curNode.OnEnter();
+		}
+
+		/// <summary>
+		/// 加入一个节点
+		/// </summary>
+		public void AddNode<TNode>() where TNode : IStateNode
+		{
+			var nodeType = typeof(TNode);
+			var stateNode = Activator.CreateInstance(nodeType) as IStateNode;
+			AddNode(stateNode);
+		}
+		public void AddNode(IStateNode stateNode)
+		{
+			if (stateNode == null)
+				throw new ArgumentNullException();
+
+			var nodeType = stateNode.GetType();
+			var nodeName = nodeType.FullName;
+
+			if (_nodes.ContainsKey(nodeName) == false)
+			{
+				stateNode.OnCreate(this);
+				_nodes.Add(nodeName, stateNode);
+			}
+			else
+			{
+				UniLogger.Error($"State node already existed : {nodeName}");
+			}
+		}
+
+		/// <summary>
+		/// 转换状态节点
+		/// </summary>
+		public void ChangeState<TNode>() where TNode : IStateNode
+		{
+			var nodeType = typeof(TNode);
+			var nodeName = nodeType.FullName;
+			ChangeState(nodeName);
+		}
+		public void ChangeState(Type nodeType)
+		{
+			var nodeName = nodeType.FullName;
+			ChangeState(nodeName);
+		}
+		public void ChangeState(string nodeName)
+		{
+			if (string.IsNullOrEmpty(nodeName))
+				throw new ArgumentNullException();
+
+			IStateNode node = TryGetNode(nodeName);
+			if (node == null)
+			{
+				UniLogger.Error($"Can not found state node : {nodeName}");
+				return;
+			}
+
+			UniLogger.Log($"{_curNode.GetType().FullName} --> {node.GetType().FullName}");
+			_preNode = _curNode;
+			_curNode.OnExit();
+			_curNode = node;
+			_curNode.OnEnter();
+		}
+
+		private IStateNode TryGetNode(string nodeName)
+		{
+			_nodes.TryGetValue(nodeName, out IStateNode result);
+			return result;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniMachine/Runtime/StateMachine.cs.meta

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

+ 16 - 0
GameClient/Assets/UniFramework/UniMachine/Runtime/UniFramework.Machine.asmdef

@@ -0,0 +1,16 @@
+{
+    "name": "UniFramework.Machine",
+    "rootNamespace": "",
+    "references": [
+        "GUID:e34a5702dd353724aa315fb8011f08c3"
+    ],
+    "includePlatforms": [],
+    "excludePlatforms": [],
+    "allowUnsafeCode": true,
+    "overrideReferences": false,
+    "precompiledReferences": [],
+    "autoReferenced": true,
+    "defineConstraints": [],
+    "versionDefines": [],
+    "noEngineReferences": false
+}

+ 7 - 0
GameClient/Assets/UniFramework/UniMachine/Runtime/UniFramework.Machine.asmdef.meta

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

+ 21 - 0
GameClient/Assets/UniFramework/UniMachine/Runtime/UniLogger.cs

@@ -0,0 +1,21 @@
+using System.Diagnostics;
+
+namespace UniFramework.Machine
+{
+	internal static class UniLogger
+	{
+		[Conditional("DEBUG")]
+		public static void Log(string info)
+		{
+			UnityEngine.Debug.Log(info);
+		}
+		public static void Warning(string info)
+		{
+			UnityEngine.Debug.LogWarning(info);
+		}
+		public static void Error(string info)
+		{
+			UnityEngine.Debug.LogError(info);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniMachine/Runtime/UniLogger.cs.meta

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

+ 8 - 0
GameClient/Assets/UniFramework/UniPooling.meta

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

+ 36 - 0
GameClient/Assets/UniFramework/UniPooling/README.md

@@ -0,0 +1,36 @@
+# UniFramework.Pooling
+
+一个功能强大的游戏对象池系统。
+
+该系统依赖于YooAsset资源系统,支持各类异步编程,支持同步接口和异步接口。
+
+```c#
+using UnityEngine;
+using YooAsset;
+using UniFramework.Pooling;
+
+IEnumerator Start()
+{
+    // 初始化游戏对象池系统
+    UniPooling.Initalize();
+    
+    // 创建孵化器
+    var spawner = UniPooling.CreateSpawner("DefaultPackage");
+    
+    // 创建Cube预制体的对象池
+    var operation = spawner.CreateGameObjectPoolAsync("Cube.prefab");
+    yield return operation;
+    
+    // 孵化Cube游戏对象
+    SpawnHandle handle = spawner.SpawnAsync("Cube.prefab");
+    yield return handle;  
+    Debug.Log(handle.GameObj.name);
+    
+    // 回收游戏对象
+    handle.Restore();
+    
+    // 丢弃游戏对象
+    handle.Discard();
+}
+```
+

+ 7 - 0
GameClient/Assets/UniFramework/UniPooling/README.md.meta

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

+ 8 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime.meta

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

+ 70 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/CreatePoolOperation.cs

@@ -0,0 +1,70 @@
+using YooAsset;
+
+namespace UniFramework.Pooling
+{
+	public class CreatePoolOperation : GameAsyncOperation
+	{
+		private enum ESteps
+		{
+			None,
+			Waiting,
+			Done,
+		}
+
+		private readonly AssetOperationHandle _handle;
+		private ESteps _steps = ESteps.None;
+
+		internal CreatePoolOperation(AssetOperationHandle handle)
+		{
+			_handle = handle;
+		}
+		protected override void OnStart()
+		{
+			_steps = ESteps.Waiting;
+		}
+		protected override void OnUpdate()
+		{
+			if (_steps == ESteps.None || _steps == ESteps.Done)
+				return;
+
+			if (_steps == ESteps.Waiting)
+			{
+				if (_handle.IsValid == false)
+				{
+					_steps = ESteps.Done;
+					Status = EOperationStatus.Failed;
+					Error = $"{nameof(AssetOperationHandle)} is invalid.";
+					return;
+				}
+
+				if (_handle.IsDone == false)
+					return;
+
+				if (_handle.AssetObject == null)
+				{
+					_steps = ESteps.Done;
+					Status = EOperationStatus.Failed;
+					Error = $"{nameof(AssetOperationHandle.AssetObject)} is null.";
+					return;
+				}
+
+				_steps = ESteps.Done;
+				Status = EOperationStatus.Succeed;
+			}
+		}
+
+		/// <summary>
+		/// 等待异步实例化结束
+		/// </summary>
+		public void WaitForAsyncComplete()
+		{
+			if (_handle != null)
+			{
+				if (_steps == ESteps.Done)
+					return;
+				_handle.WaitForAsyncComplete();
+				OnUpdate();
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/CreatePoolOperation.cs.meta

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

+ 222 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/GameObjectPool.cs

@@ -0,0 +1,222 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using YooAsset;
+
+namespace UniFramework.Pooling
+{
+	internal class GameObjectPool
+	{
+		private readonly GameObject _root;
+		private readonly Queue<InstantiateOperation> _cacheOperations;
+		private readonly bool _dontDestroy;
+		private readonly int _initCapacity;
+		private readonly int _maxCapacity;
+		private readonly float _destroyTime;
+		private float _lastRestoreRealTime = -1f;
+
+		/// <summary>
+		/// 资源句柄
+		/// </summary>
+		public AssetOperationHandle AssetHandle { private set; get; }
+
+		/// <summary>
+		/// 资源定位地址
+		/// </summary>
+		public string Location { private set; get; }
+
+		/// <summary>
+		/// 内部缓存总数
+		/// </summary>
+		public int CacheCount
+		{
+			get { return _cacheOperations.Count; }
+		}
+
+		/// <summary>
+		/// 外部使用总数
+		/// </summary>
+		public int SpawnCount { private set; get; } = 0;
+
+		/// <summary>
+		/// 是否常驻不销毁
+		/// </summary>
+		public bool DontDestroy
+		{
+			get { return _dontDestroy; }
+		}
+
+
+		public GameObjectPool(GameObject poolingRoot, string location, bool dontDestroy, int initCapacity, int maxCapacity, float destroyTime)
+		{
+			_root = new GameObject(location);
+			_root.transform.parent = poolingRoot.transform;
+			Location = location;
+
+			_dontDestroy = dontDestroy;
+			_initCapacity = initCapacity;
+			_maxCapacity = maxCapacity;
+			_destroyTime = destroyTime;
+
+			// 创建缓存池
+			_cacheOperations = new Queue<InstantiateOperation>(initCapacity);
+		}
+
+		/// <summary>
+		/// 创建对象池
+		/// </summary>
+		public void CreatePool(ResourcePackage package)
+		{
+			// 加载游戏对象
+			AssetHandle = package.LoadAssetAsync<GameObject>(Location);
+
+			// 创建初始对象
+			for (int i = 0; i < _initCapacity; i++)
+			{
+				var operation = AssetHandle.InstantiateAsync(_root.transform);
+				operation.Completed += Operation_Completed;
+				_cacheOperations.Enqueue(operation);
+			}
+		}
+		private void Operation_Completed(AsyncOperationBase obj)
+		{
+			if (obj.Status == EOperationStatus.Succeed)
+			{
+				var op = obj as InstantiateOperation;
+				if (op.Result != null)
+					op.Result.SetActive(false);
+			}
+		}
+
+		/// <summary>
+		/// 销毁游戏对象池
+		/// </summary>
+		public void DestroyPool()
+		{
+			// 卸载资源对象
+			AssetHandle.Release();
+			AssetHandle = null;
+
+			// 销毁游戏对象
+			GameObject.Destroy(_root);
+			_cacheOperations.Clear();
+
+			SpawnCount = 0;
+		}
+
+		/// <summary>
+		/// 查询静默时间内是否可以销毁
+		/// </summary>
+		public bool CanAutoDestroy()
+		{
+			if (_dontDestroy)
+				return false;
+			if (_destroyTime < 0)
+				return false;
+
+			if (_lastRestoreRealTime > 0 && SpawnCount <= 0)
+				return (Time.realtimeSinceStartup - _lastRestoreRealTime) > _destroyTime;
+			else
+				return false;
+		}
+
+		/// <summary>
+		/// 游戏对象池是否已经销毁
+		/// </summary>
+		public bool IsDestroyed()
+		{
+			return AssetHandle == null;
+		}
+
+		/// <summary>
+		/// 回收
+		/// </summary>
+		public void Restore(InstantiateOperation operation)
+		{
+			if (IsDestroyed())
+			{
+				DestroyInstantiateOperation(operation);
+				return;
+			}
+
+			SpawnCount--;
+			if (SpawnCount <= 0)
+				_lastRestoreRealTime = Time.realtimeSinceStartup;
+
+			// 如果外部逻辑销毁了游戏对象
+			if (operation.Status == EOperationStatus.Succeed)
+			{
+				if (operation.Result == null)
+					return;
+			}
+
+			// 如果缓存池还未满员
+			if (_cacheOperations.Count < _maxCapacity)
+			{
+				SetRestoreGameObject(operation.Result);
+				_cacheOperations.Enqueue(operation);
+			}
+			else
+			{
+				DestroyInstantiateOperation(operation);
+			}
+		}
+
+		/// <summary>
+		/// 丢弃
+		/// </summary>
+		public void Discard(InstantiateOperation operation)
+		{
+			if (IsDestroyed())
+			{
+				DestroyInstantiateOperation(operation);
+				return;
+			}
+
+			SpawnCount--;
+			if (SpawnCount <= 0)
+				_lastRestoreRealTime = Time.realtimeSinceStartup;
+
+			DestroyInstantiateOperation(operation);
+		}
+
+		/// <summary>
+		/// 获取一个游戏对象
+		/// </summary>
+		public SpawnHandle Spawn(Transform parent, Vector3 position, Quaternion rotation, bool forceClone, params System.Object[] userDatas)
+		{
+			InstantiateOperation operation;
+			if (forceClone == false && _cacheOperations.Count > 0)
+				operation = _cacheOperations.Dequeue();
+			else
+				operation = AssetHandle.InstantiateAsync();
+
+			SpawnCount++;
+			SpawnHandle handle = new SpawnHandle(this, operation, parent, position, rotation, userDatas);
+			YooAssets.StartOperation(handle);
+			return handle;
+		}
+
+		private void DestroyInstantiateOperation(InstantiateOperation operation)
+		{
+			// 取消异步操作
+			operation.Cancel();
+
+			// 销毁游戏对象
+			if (operation.Result != null)
+			{
+				GameObject.Destroy(operation.Result);
+			}
+		}
+		private void SetRestoreGameObject(GameObject gameObj)
+		{
+			if (gameObj != null)
+			{
+				gameObj.SetActive(false);
+				gameObj.transform.SetParent(_root.transform);
+				gameObj.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/GameObjectPool.cs.meta

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

+ 148 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/SpawnHandle.cs

@@ -0,0 +1,148 @@
+using UnityEngine;
+using YooAsset;
+
+namespace UniFramework.Pooling
+{
+	public sealed class SpawnHandle : GameAsyncOperation
+	{
+		private enum ESteps
+		{
+			None,
+			Waiting,
+			Done,
+		}
+
+		private readonly GameObjectPool _pool;
+		private InstantiateOperation _operation;
+		private readonly Transform _parent;
+		private readonly Vector3 _position;
+		private readonly Quaternion _rotation;
+		private ESteps _steps = ESteps.None;
+
+		/// <summary>
+		/// 实例化的游戏对象
+		/// </summary>
+		public GameObject GameObj
+		{
+			get
+			{
+				if (_operation == null)
+				{
+					UniLogger.Warning("The spawn handle is invalid !");
+					return null;
+				}
+
+				return _operation.Result;
+			}
+		}
+
+		/// <summary>
+		/// 用户自定义数据集
+		/// </summary>
+		public System.Object[] UserDatas { private set; get; }
+
+		private SpawnHandle()
+		{
+		}
+		internal SpawnHandle(GameObjectPool pool, InstantiateOperation operation, Transform parent, Vector3 position, Quaternion rotation, params System.Object[] userDatas)
+		{
+			_pool = pool;
+			_operation = operation;
+			_parent = parent;
+			_position = position;
+			_rotation = rotation;
+			UserDatas = userDatas;
+		}
+		protected override void OnStart()
+		{
+			_steps = ESteps.Waiting;
+		}
+		protected override void OnUpdate()
+		{
+			if (_steps == ESteps.None || _steps == ESteps.Done)
+				return;
+
+			if (_steps == ESteps.Waiting)
+			{
+				if (_operation.IsDone == false)
+					return;
+
+				if (_operation.Status != EOperationStatus.Succeed)
+				{
+					_steps = ESteps.Done;
+					Status = EOperationStatus.Failed;
+					Error = _operation.Error;
+					return;
+				}
+
+				if (_operation.Result == null)
+				{
+					_steps = ESteps.Done;
+					Status = EOperationStatus.Failed;
+					Error = $"Clone game object is null.";
+					return;
+				}
+
+				// 设置参数	
+				_operation.Result.transform.SetParent(_parent);
+				_operation.Result.transform.SetPositionAndRotation(_position, _rotation);
+				_operation.Result.SetActive(true);
+
+				_steps = ESteps.Done;
+				Status = EOperationStatus.Succeed;
+			}
+		}
+
+		/// <summary>
+		/// 回收
+		/// </summary>
+		public void Restore()
+		{
+			if (_operation != null)
+			{
+				ClearCompletedCallback();
+				CancelHandle();
+				_pool.Restore(_operation);
+				_operation = null;
+			}
+		}
+
+		/// <summary>
+		/// 丢弃
+		/// </summary>
+		public void Discard()
+		{
+			if (_operation != null)
+			{
+				ClearCompletedCallback();
+				CancelHandle();
+				_pool.Discard(_operation);
+				_operation = null;
+			}
+		}
+
+		/// <summary>
+		/// 等待异步实例化结束
+		/// </summary>
+		public void WaitForAsyncComplete()
+		{
+			if (_operation != null)
+			{
+				if (_steps == ESteps.Done)
+					return;
+				_operation.WaitForAsyncComplete();
+				OnUpdate();
+			}
+		}
+
+		private void CancelHandle()
+		{
+			if (IsDone == false)
+			{
+				_steps = ESteps.Done;
+				Status = EOperationStatus.Failed;
+				Error = $"User cancelled !";
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/SpawnHandle.cs.meta

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

+ 259 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/Spawner.cs

@@ -0,0 +1,259 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using YooAsset;
+
+namespace UniFramework.Pooling
+{
+	public class Spawner
+	{
+		private readonly List<GameObjectPool> _gameObjectPools = new List<GameObjectPool>(100);
+		private readonly List<GameObjectPool> _removeList = new List<GameObjectPool>(100);
+		private readonly GameObject _spawnerRoot;
+		private readonly ResourcePackage _package;
+
+		public string PackageName
+		{
+			get
+			{
+				return _package.PackageName;
+			}
+		}
+
+
+		private Spawner()
+		{
+		}
+		internal Spawner(GameObject poolingRoot, ResourcePackage package)
+		{
+			_spawnerRoot = new GameObject($"{package.PackageName}");
+			_spawnerRoot.transform.SetParent(poolingRoot.transform);
+			_package = package;
+		}
+
+		/// <summary>
+		/// 更新游戏对象池系统
+		/// </summary>
+		internal void Update()
+		{
+			_removeList.Clear();
+			foreach (var pool in _gameObjectPools)
+			{
+				if (pool.CanAutoDestroy())
+					_removeList.Add(pool);
+			}
+
+			foreach (var pool in _removeList)
+			{
+				_gameObjectPools.Remove(pool);
+				pool.DestroyPool();
+			}
+		}
+
+		/// <summary>
+		/// 销毁游戏对象池系统
+		/// </summary>
+		internal void Destroy()
+		{
+			DestroyAll(true);
+		}
+
+		/// <summary>
+		/// 销毁所有对象池及其资源
+		/// </summary>
+		/// <param name="includeAll">销毁所有对象池,包括常驻对象池</param>
+		public void DestroyAll(bool includeAll)
+		{
+			if (includeAll)
+			{
+				foreach (var pool in _gameObjectPools)
+				{
+					pool.DestroyPool();
+				}
+				_gameObjectPools.Clear();
+			}
+			else
+			{
+				List<GameObjectPool> removeList = new List<GameObjectPool>();
+				foreach (var pool in _gameObjectPools)
+				{
+					if (pool.DontDestroy == false)
+						removeList.Add(pool);
+				}
+				foreach (var pool in removeList)
+				{
+					_gameObjectPools.Remove(pool);
+					pool.DestroyPool();
+				}
+			}
+		}
+
+
+		/// <summary>
+		/// 异步创建指定资源的游戏对象池
+		/// </summary>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="dontDestroy">资源常驻不销毁</param>
+		/// <param name="initCapacity">对象池的初始容量</param>
+		/// <param name="maxCapacity">对象池的最大容量</param>
+		/// <param name="destroyTime">静默销毁时间(注意:小于零代表不主动销毁)</param>
+		public CreatePoolOperation CreateGameObjectPoolAsync(string location, bool dontDestroy = false, int initCapacity = 0, int maxCapacity = int.MaxValue, float destroyTime = -1f)
+		{
+			return CreateGameObjectPoolInternal(location, dontDestroy, initCapacity, maxCapacity, destroyTime);
+		}
+
+		/// <summary>
+		/// 同步创建指定资源的游戏对象池
+		/// </summary>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="dontDestroy">资源常驻不销毁</param>
+		/// <param name="initCapacity">对象池的初始容量</param>
+		/// <param name="maxCapacity">对象池的最大容量</param>
+		/// <param name="destroyTime">静默销毁时间(注意:小于零代表不主动销毁)</param>
+		public CreatePoolOperation CreateGameObjectPoolSync(string location, bool dontDestroy = false, int initCapacity = 0, int maxCapacity = int.MaxValue, float destroyTime = -1f)
+		{
+			var operation = CreateGameObjectPoolInternal(location, dontDestroy, initCapacity, maxCapacity, destroyTime);
+			operation.WaitForAsyncComplete();
+			return operation;
+		}
+
+		/// <summary>
+		/// 创建指定资源的游戏对象池
+		/// </summary>
+		private CreatePoolOperation CreateGameObjectPoolInternal(string location, bool dontDestroy = false, int initCapacity = 0, int maxCapacity = int.MaxValue, float destroyTime = -1f)
+		{
+			if (maxCapacity < initCapacity)
+				throw new Exception("The max capacity value must be greater the init capacity value.");
+
+			GameObjectPool pool = TryGetGameObjectPool(location);
+			if (pool != null)
+			{
+				UniLogger.Warning($"GameObject pool is already existed : {location}");
+				var operation = new CreatePoolOperation(pool.AssetHandle);
+				YooAssets.StartOperation(operation);
+				return operation;
+			}
+			else
+			{
+				pool = new GameObjectPool(_spawnerRoot, location, dontDestroy, initCapacity, maxCapacity, destroyTime);
+				pool.CreatePool(_package);
+				_gameObjectPools.Add(pool);
+
+				var operation = new CreatePoolOperation(pool.AssetHandle);
+				YooAssets.StartOperation(operation);
+				return operation;
+			}
+		}
+
+
+		/// <summary>
+		/// 异步实例化一个游戏对象
+		/// </summary>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="forceClone">强制克隆游戏对象,忽略缓存池里的对象</param>
+		/// <param name="userDatas">用户自定义数据</param>
+		public SpawnHandle SpawnAsync(string location, bool forceClone = false, params System.Object[] userDatas)
+		{
+			return SpawnInternal(location, null, Vector3.zero, Quaternion.identity, forceClone, userDatas);
+		}
+
+		/// <summary>
+		/// 异步实例化一个游戏对象
+		/// </summary>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="parent">父物体</param>
+		/// <param name="forceClone">强制克隆游戏对象,忽略缓存池里的对象</param>
+		/// <param name="userDatas">用户自定义数据</param>
+		public SpawnHandle SpawnAsync(string location, Transform parent, bool forceClone = false, params System.Object[] userDatas)
+		{
+			return SpawnInternal(location, parent, Vector3.zero, Quaternion.identity, forceClone, userDatas);
+		}
+
+		/// <summary>
+		/// 异步实例化一个游戏对象
+		/// </summary>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="parent">父物体</param>
+		/// <param name="position">世界坐标</param>
+		/// <param name="rotation">世界角度</param>
+		/// <param name="forceClone">强制克隆游戏对象,忽略缓存池里的对象</param>
+		/// <param name="userDatas">用户自定义数据</param>
+		public SpawnHandle SpawnAsync(string location, Transform parent, Vector3 position, Quaternion rotation, bool forceClone = false, params System.Object[] userDatas)
+		{
+			return SpawnInternal(location, parent, position, rotation, forceClone, userDatas);
+		}
+
+		/// <summary>
+		/// 同步实例化一个游戏对象
+		/// </summary>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="forceClone">强制克隆游戏对象,忽略缓存池里的对象</param>
+		/// <param name="userDatas">用户自定义数据</param>
+		public SpawnHandle SpawnSync(string location, bool forceClone = false, params System.Object[] userDatas)
+		{
+			SpawnHandle handle = SpawnInternal(location, null, Vector3.zero, Quaternion.identity, forceClone, userDatas);
+			handle.WaitForAsyncComplete();
+			return handle;
+		}
+
+		/// <summary>
+		/// 同步实例化一个游戏对象
+		/// </summary>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="parent">父物体</param>
+		/// <param name="forceClone">强制克隆游戏对象,忽略缓存池里的对象</param>
+		/// <param name="userDatas">用户自定义数据</param>
+		public SpawnHandle SpawnSync(string location, Transform parent, bool forceClone = false, params System.Object[] userDatas)
+		{
+			SpawnHandle handle = SpawnInternal(location, parent, Vector3.zero, Quaternion.identity, forceClone, userDatas);
+			handle.WaitForAsyncComplete();
+			return handle;
+		}
+
+		/// <summary>
+		/// 同步实例化一个游戏对象
+		/// </summary>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="parent">父物体</param>
+		/// <param name="position">世界坐标</param>
+		/// <param name="rotation">世界角度</param>
+		/// <param name="forceClone">强制克隆游戏对象,忽略缓存池里的对象</param>
+		/// <param name="userDatas">用户自定义数据</param>
+		public SpawnHandle SpawnSync(string location, Transform parent, Vector3 position, Quaternion rotation, bool forceClone = false, params System.Object[] userDatas)
+		{
+			SpawnHandle handle = SpawnInternal(location, parent, position, rotation, forceClone, userDatas);
+			handle.WaitForAsyncComplete();
+			return handle;
+		}
+
+		/// <summary>
+		/// 实例化一个游戏对象
+		/// </summary>
+		private SpawnHandle SpawnInternal(string location, Transform parent, Vector3 position, Quaternion rotation, bool forceClone, params System.Object[] userDatas)
+		{
+			var pool = TryGetGameObjectPool(location);
+			if (pool != null)
+			{
+				return pool.Spawn(parent, position, rotation, forceClone, userDatas);
+			}
+
+			// 如果不存在创建游戏对象池
+			pool = new GameObjectPool(_spawnerRoot, location, false, 0, int.MaxValue, -1f);
+			pool.CreatePool(_package);
+			_gameObjectPools.Add(pool);
+			return pool.Spawn(parent, position, rotation, forceClone, userDatas);
+		}
+
+
+		private GameObjectPool TryGetGameObjectPool(string location)
+		{
+			foreach (var pool in _gameObjectPools)
+			{
+				if (pool.Location == location)
+					return pool;
+			}
+			return null;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/Spawner.cs.meta

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

+ 16 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/UniFramework.Pooling.asmdef

@@ -0,0 +1,16 @@
+{
+    "name": "UniFramework.Pooling",
+    "rootNamespace": "",
+    "references": [
+        "GUID:e34a5702dd353724aa315fb8011f08c3"
+    ],
+    "includePlatforms": [],
+    "excludePlatforms": [],
+    "allowUnsafeCode": true,
+    "overrideReferences": false,
+    "precompiledReferences": [],
+    "autoReferenced": true,
+    "defineConstraints": [],
+    "versionDefines": [],
+    "noEngineReferences": false
+}

+ 7 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/UniFramework.Pooling.asmdef.meta

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

+ 21 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/UniLogger.cs

@@ -0,0 +1,21 @@
+using System.Diagnostics;
+
+namespace UniFramework.Pooling
+{
+	internal static class UniLogger
+	{
+		[Conditional("DEBUG")]
+		public static void Log(string info)
+		{
+			UnityEngine.Debug.Log(info);
+		}
+		public static void Warning(string info)
+		{
+			UnityEngine.Debug.LogWarning(info);
+		}
+		public static void Error(string info)
+		{
+			UnityEngine.Debug.LogError(info);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/UniLogger.cs.meta

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

+ 127 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/UniPooling.cs

@@ -0,0 +1,127 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using YooAsset;
+
+namespace UniFramework.Pooling
+{
+	/// <summary>
+	/// 游戏对象池系统
+	/// </summary>
+	public static class UniPooling
+	{
+		private static bool _isInitialize = false;
+		private static GameObject _driver = null;
+		private static readonly List<Spawner> _spawners = new List<Spawner>();
+
+
+		/// <summary>
+		/// 初始化游戏对象池系统
+		/// </summary>
+		public static void Initalize()
+		{
+			if (_isInitialize)
+				throw new Exception($"{nameof(UniPooling)} is initialized !");
+
+			if (_isInitialize == false)
+			{
+				// 创建驱动器
+				_isInitialize = true;
+				_driver = new UnityEngine.GameObject($"[{nameof(UniPooling)}]");
+				_driver.AddComponent<UniPoolingDriver>();
+				UnityEngine.Object.DontDestroyOnLoad(_driver);
+				UniLogger.Log($"{nameof(UniPooling)} initalize !");
+			}
+		}
+
+		/// <summary>
+		/// 销毁游戏对象池系统
+		/// </summary>
+		public static void Destroy()
+		{
+			if (_isInitialize)
+			{
+				foreach (var spawner in _spawners)
+				{
+					spawner.Destroy();
+				}
+				_spawners.Clear();
+
+				_isInitialize = false;
+				if (_driver != null)
+					GameObject.Destroy(_driver);
+				UniLogger.Log($"{nameof(UniPooling)} destroy all !");
+			}
+		}
+
+		/// <summary>
+		/// 更新游戏对象池系统
+		/// </summary>
+		internal static void Update()
+		{
+			if (_isInitialize)
+			{
+				foreach (var spawner in _spawners)
+				{
+					spawner.Update();
+				}
+			}
+		}
+
+		/// <summary>
+		/// 创建游戏对象生成器
+		/// </summary>
+		/// <param name="packageName">资源包名称</param>
+		public static Spawner CreateSpawner(string packageName)
+		{
+			// 获取资源包
+			var assetPackage = YooAssets.GetPackage(packageName);
+			if (assetPackage == null)
+				throw new Exception($"Not found asset package : {packageName}");
+
+			// 检测资源包初始化状态
+			if (assetPackage.InitializeStatus == EOperationStatus.None)
+				throw new Exception($"Asset package {packageName} not initialize !");
+			if (assetPackage.InitializeStatus == EOperationStatus.Failed)
+				throw new Exception($"Asset package {packageName} initialize failed !");
+
+			if (HasSpawner(packageName))
+				return GetSpawner(packageName);
+
+			Spawner spawner = new Spawner(_driver, assetPackage);
+			_spawners.Add(spawner);
+			return spawner;
+		}
+
+		/// <summary>
+		/// 获取游戏对象生成器
+		/// </summary>
+		/// <param name="packageName">资源包名称</param>
+		public static Spawner GetSpawner(string packageName)
+		{
+			foreach (var spawner in _spawners)
+			{
+				if (spawner.PackageName == packageName)
+					return spawner;
+			}
+
+			UniLogger.Warning($"Not found spawner : {packageName}");
+			return null;
+		}
+
+		/// <summary>
+		/// 检测游戏对象生成器是否存在
+		/// </summary>
+		/// <param name="packageName">资源包名称</param>
+		public static bool HasSpawner(string packageName)
+		{
+			foreach (var spawner in _spawners)
+			{
+				if (spawner.PackageName == packageName)
+					return true;
+			}
+			return false;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/UniPooling.cs.meta

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

+ 12 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/UniPoolingDriver.cs

@@ -0,0 +1,12 @@
+using UnityEngine;
+
+namespace UniFramework.Pooling
+{
+	internal class UniPoolingDriver : MonoBehaviour
+	{
+		void Update()
+		{
+			UniPooling.Update();
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniPooling/Runtime/UniPoolingDriver.cs.meta

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

+ 8 - 0
GameClient/Assets/UniFramework/UniWindow.meta

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

+ 4 - 0
GameClient/Assets/UniFramework/UniWindow/README.md

@@ -0,0 +1,4 @@
+# UniFramework.Window
+
+一个轻量级的基于堆栈的界面系统。
+

+ 7 - 0
GameClient/Assets/UniFramework/UniWindow/README.md.meta

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

+ 8 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime.meta

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

+ 70 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/OpenWindowOperation.cs

@@ -0,0 +1,70 @@
+using YooAsset;
+
+namespace UniFramework.Window
+{
+	public class OpenWindowOperation : GameAsyncOperation
+	{
+		private enum ESteps
+		{
+			None,
+			Waiting,
+			Done,
+		}
+
+		private readonly AssetOperationHandle _handle;
+		private ESteps _steps = ESteps.None;
+		
+		internal OpenWindowOperation(AssetOperationHandle handle)
+		{
+			_handle = handle;
+		}
+		protected override void OnStart()
+		{
+			_steps = ESteps.Waiting;
+		}
+		protected override void OnUpdate()
+		{
+			if (_steps == ESteps.None || _steps == ESteps.Done)
+				return;
+
+			if (_steps == ESteps.Waiting)
+			{
+				if (_handle.IsValid == false)
+				{
+					_steps = ESteps.Done;
+					Status = EOperationStatus.Failed;
+					Error = $"{nameof(AssetOperationHandle)} is invalid.";
+					return;
+				}
+
+				if (_handle.IsDone == false)
+					return;
+
+				if (_handle.AssetObject == null)
+				{
+					_steps = ESteps.Done;
+					Status = EOperationStatus.Failed;
+					Error = $"{nameof(AssetOperationHandle.AssetObject)} is null.";
+					return;
+				}
+
+				_steps = ESteps.Done;
+				Status = EOperationStatus.Succeed;
+			}
+		}
+
+		/// <summary>
+		/// 等待异步实例化结束
+		/// </summary>
+		public void WaitForAsyncComplete()
+		{
+			if (_handle != null)
+			{
+				if (_steps == ESteps.Done)
+					return;
+				_handle.WaitForAsyncComplete();
+				OnUpdate();
+			}
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/OpenWindowOperation.cs.meta

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

+ 325 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UIWindow.cs

@@ -0,0 +1,325 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+using YooAsset;
+
+namespace UniFramework.Window
+{
+	public abstract class UIWindow
+	{
+		public const int WINDOW_HIDE_LAYER = 2; // Ignore Raycast
+		public const int WINDOW_SHOW_LAYER = 5; // UI
+
+		internal AssetOperationHandle Handle { private set; get;}
+		private System.Action<UIWindow> _prepareCallback;
+		private System.Object[] _userDatas;
+
+		private bool _isCreate = false;
+		private GameObject _panel;
+		private Canvas _canvas;
+		private Canvas[] _childCanvas;
+		private GraphicRaycaster _raycaster;
+		private GraphicRaycaster[] _childRaycaster;
+
+		/// <summary>
+		/// 面板的Transfrom组件
+		/// </summary>
+		public Transform transform
+		{
+			get
+			{
+				return _panel.transform;
+			}
+		}
+
+		/// <summary>
+		/// 面板的游戏对象
+		/// </summary>
+		public GameObject gameObject
+		{
+			get
+			{
+				return _panel;
+			}
+		}
+		
+		/// <summary>
+		/// 窗口名称
+		/// </summary>
+		public string WindowName { private set; get; }
+
+		/// <summary>
+		/// 窗口层级
+		/// </summary>
+		public int WindowLayer { private set; get; }
+
+		/// <summary>
+		/// 是否为全屏窗口
+		/// </summary>
+		public bool FullScreen { private set; get; }
+
+		/// <summary>
+		/// 自定义数据
+		/// </summary>
+		public System.Object UserData
+		{
+			get
+			{
+				if (_userDatas != null && _userDatas.Length >= 1)
+					return _userDatas[0];
+				else
+					return null;
+			}
+		}
+
+		/// <summary>
+		/// 自定义数据集
+		/// </summary>
+		public System.Object[] UserDatas
+		{
+			get { return _userDatas; }
+		}
+
+		/// <summary>
+		/// 窗口深度值
+		/// </summary>
+		public int Depth
+		{
+			get
+			{
+				if (_canvas != null)
+					return _canvas.sortingOrder;
+				else
+					return 0;
+			}
+
+			set
+			{
+				if (_canvas != null)
+				{
+					if (_canvas.sortingOrder == value)
+						return;
+
+					// 设置父类
+					_canvas.sortingOrder = value;
+
+					// 设置子类
+					int depth = value;
+					for (int i = 0; i < _childCanvas.Length; i++)
+					{
+						var canvas = _childCanvas[i];
+						if (canvas != _canvas)
+						{
+							depth += 5; //注意递增值
+							canvas.sortingOrder = depth;
+						}
+					}
+
+					// 虚函数
+					if (_isCreate)
+						OnSortDepth(value);
+				}
+			}
+		}
+
+		/// <summary>
+		/// <summary>
+		/// 窗口可见性
+		/// </summary>
+		public bool Visible
+		{
+			get
+			{
+				if (_canvas != null)
+					return _canvas.gameObject.layer == WINDOW_SHOW_LAYER;
+				else
+					return false;
+			}
+
+			set
+			{
+				if (_canvas != null)
+				{
+					int setLayer = value ? WINDOW_SHOW_LAYER : WINDOW_HIDE_LAYER;
+					if (_canvas.gameObject.layer == setLayer)
+						return;
+
+					// 显示设置
+					_canvas.gameObject.layer = setLayer;
+					for (int i = 0; i < _childCanvas.Length; i++)
+					{
+						_childCanvas[i].gameObject.layer = setLayer;
+					}
+
+					// 交互设置
+					Interactable = value;
+
+					// 虚函数
+					if (_isCreate)
+						OnSetVisible(value);
+				}
+			}
+		}
+
+		/// <summary>
+		/// 窗口交互性
+		/// </summary>
+		private bool Interactable
+		{
+			get
+			{
+				if (_raycaster != null)
+					return _raycaster.enabled;
+				else
+					return false;
+			}
+
+			set
+			{
+				if (_raycaster != null)
+				{
+					_raycaster.enabled = value;
+					for (int i = 0; i < _childRaycaster.Length; i++)
+					{
+						_childRaycaster[i].enabled = value;
+					}
+				}
+			}
+		}
+
+		/// <summary>
+		/// 是否加载完毕
+		/// </summary>
+		internal bool IsLoadDone { get { return Handle.IsDone; } }
+
+		/// <summary>
+		/// 是否准备完毕
+		/// </summary>
+		internal bool IsPrepare { private set; get; }
+
+
+		public void Init(string name, int layer, bool fullScreen)
+		{
+			WindowName = name;
+			WindowLayer = layer;
+			FullScreen = fullScreen;
+		}
+
+		/// <summary>
+		/// 窗口创建
+		/// </summary>
+		public abstract void OnCreate();
+
+		/// <summary>
+		/// 窗口刷新
+		/// </summary>
+		public abstract void OnRefresh();
+
+		/// <summary>
+		/// 窗口更新
+		/// </summary>
+		public abstract void OnUpdate();
+
+		/// <summary>
+		/// 窗口销毁
+		/// </summary>
+		public abstract void OnDestroy();
+
+		/// <summary>
+		/// 当触发窗口的层级排序
+		/// </summary>
+		protected virtual void OnSortDepth(int depth) { }
+
+		/// <summary>
+		/// 当因为全屏遮挡触发窗口的显隐
+		/// </summary>
+		protected virtual void OnSetVisible(bool visible) { }
+
+		internal void TryInvoke(System.Action<UIWindow> prepareCallback, System.Object[] userDatas)
+		{
+			_userDatas = userDatas;
+			if (IsPrepare)
+				prepareCallback?.Invoke(this);
+			else
+				_prepareCallback = prepareCallback;
+		}
+		internal void InternalLoad(string location, System.Action<UIWindow> prepareCallback, System.Object[] userDatas)
+		{
+			if (Handle != null)
+				return;
+
+			_prepareCallback = prepareCallback;
+			_userDatas = userDatas;
+			Handle = YooAssets.LoadAssetAsync<GameObject>(location);
+			Handle.Completed += Handle_Completed;
+		}
+		internal void InternalCreate()
+		{
+			if (_isCreate == false)
+			{
+				_isCreate = true;
+				OnCreate();
+			}
+		}
+		internal void InternalRefresh()
+		{
+			OnRefresh();
+		}
+		internal void InternalUpdate()
+		{
+			if(IsPrepare)
+			{
+				OnUpdate();
+			}
+		}
+		internal void InternalDestroy()
+		{
+			_isCreate = false;
+
+			// 注销回调函数
+			_prepareCallback = null;
+
+			// 卸载面板资源
+			if (Handle != null)
+			{
+				Handle.Release();
+				Handle = null;
+			}
+
+			// 销毁面板对象
+			if (_panel != null)
+			{
+				OnDestroy();
+				GameObject.Destroy(_panel);
+				_panel = null;
+			}
+		}
+
+		private void Handle_Completed(AssetOperationHandle handle)
+		{
+			if (handle.AssetObject == null)
+				return;
+
+			// 实例化对象
+			_panel = handle.InstantiateSync(UniWindow.Desktop.transform);
+			_panel.transform.localPosition = Vector3.zero;
+
+			// 获取组件
+			_canvas = _panel.GetComponent<Canvas>();
+			if (_canvas == null)
+				throw new Exception($"Not found {nameof(Canvas)} in panel {WindowName}");
+			_canvas.overrideSorting = true;
+			_canvas.sortingOrder = 0;
+			_canvas.sortingLayerName = "Default";
+
+			// 获取组件
+			_raycaster = _panel.GetComponent<GraphicRaycaster>();
+			_childCanvas = _panel.GetComponentsInChildren<Canvas>(true);
+			_childRaycaster = _panel.GetComponentsInChildren<GraphicRaycaster>(true);
+
+			// 通知UI管理器
+			IsPrepare = true;
+			_prepareCallback?.Invoke(this);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UIWindow.cs.meta

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

+ 16 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UniFramework.Window.asmdef

@@ -0,0 +1,16 @@
+{
+    "name": "UniFramework.Window",
+    "rootNamespace": "",
+    "references": [
+        "GUID:e34a5702dd353724aa315fb8011f08c3"
+    ],
+    "includePlatforms": [],
+    "excludePlatforms": [],
+    "allowUnsafeCode": true,
+    "overrideReferences": false,
+    "precompiledReferences": [],
+    "autoReferenced": true,
+    "defineConstraints": [],
+    "versionDefines": [],
+    "noEngineReferences": false
+}

+ 7 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UniFramework.Window.asmdef.meta

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

+ 21 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UniLogger.cs

@@ -0,0 +1,21 @@
+using System.Diagnostics;
+
+namespace UniFramework.Window
+{
+	internal static class UniLogger
+	{
+		[Conditional("DEBUG")]
+		public static void Log(string info)
+		{
+			UnityEngine.Debug.Log(info);
+		}
+		public static void Warning(string info)
+		{
+			UnityEngine.Debug.LogWarning(info);
+		}
+		public static void Error(string info)
+		{
+			UnityEngine.Debug.LogError(info);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UniLogger.cs.meta

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

+ 414 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UniWindow.cs

@@ -0,0 +1,414 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UI;
+using YooAsset;
+
+namespace UniFramework.Window
+{
+	public static class UniWindow
+	{
+		public struct WindowInfo
+		{
+			public string WindowName;
+			public int WindowLayer;
+			public bool IsLoadDone;
+		}
+
+		private static bool _isInitialize = false;
+		private static GameObject _driver = null;
+		private static readonly List<UIWindow> _stack = new List<UIWindow>(100);
+		internal static GameObject Desktop { private set; get; }
+
+
+		/// <summary>
+		/// 初始化界面系统
+		/// </summary>
+		public static void Initalize(GameObject desktop)
+		{
+			if (_isInitialize)
+				throw new Exception($"{nameof(UniWindow)} is initialized !");
+			if (desktop == null)
+				throw new ArgumentNullException();
+
+			if (_isInitialize == false)
+			{
+				// 创建驱动器
+				_isInitialize = true;
+				_driver = new UnityEngine.GameObject($"[{nameof(UniWindow)}]");
+				_driver.AddComponent<UniWindowDriver>();
+				UnityEngine.Object.DontDestroyOnLoad(_driver);
+				UniLogger.Log($"{nameof(UniWindow)} initalize !");
+
+				Desktop = desktop;
+			}
+		}
+
+		/// <summary>
+		/// 销毁界面系统
+		/// </summary>
+		public static void Destroy()
+		{
+			if (_isInitialize)
+			{
+				CloseAll();
+
+				_isInitialize = false;
+				if (_driver != null)
+					GameObject.Destroy(_driver);
+				UniLogger.Log($"{nameof(UniWindow)} destroy all !");
+			}
+		}
+
+		/// <summary>
+		/// 更新界面系统
+		/// </summary>
+		internal static void Update()
+		{
+			if (_isInitialize)
+			{
+				int count = _stack.Count;
+				for (int i = 0; i < _stack.Count; i++)
+				{
+					if (_stack.Count != count)
+						break;
+					var window = _stack[i];
+					window.InternalUpdate();
+				}
+			}
+		}
+
+		/// <summary>
+		/// 设置屏幕安全区域(异形屏支持)
+		/// </summary>
+		/// <param name="safeRect">安全区域</param>
+		public static void ApplyScreenSafeRect(Rect safeRect)
+		{
+			CanvasScaler scaler = Desktop.GetComponentInParent<CanvasScaler>();
+			if (scaler == null)
+			{
+				UniLogger.Error($"Not found {nameof(CanvasScaler)} !");
+				return;
+			}
+
+			// Convert safe area rectangle from absolute pixels to UGUI coordinates
+			float rateX = scaler.referenceResolution.x / Screen.width;
+			float rateY = scaler.referenceResolution.y / Screen.height;
+			float posX = (int)(safeRect.position.x * rateX);
+			float posY = (int)(safeRect.position.y * rateY);
+			float width = (int)(safeRect.size.x * rateX);
+			float height = (int)(safeRect.size.y * rateY);
+
+			float offsetMaxX = scaler.referenceResolution.x - width - posX;
+			float offsetMaxY = scaler.referenceResolution.y - height - posY;
+
+			// 注意:安全区坐标系的原点为左下角	
+			var rectTrans = Desktop.transform as RectTransform;
+			rectTrans.offsetMin = new Vector2(posX, posY); //锚框状态下的屏幕左下角偏移向量
+			rectTrans.offsetMax = new Vector2(-offsetMaxX, -offsetMaxY); //锚框状态下的屏幕右上角偏移向量
+		}
+
+		/// <summary>
+		/// 模拟IPhoneX异形屏
+		/// </summary>
+		public static void SimulateIPhoneXNotchScreen()
+		{
+			Rect rect;
+			if (Screen.height > Screen.width)
+			{
+				// 竖屏Portrait
+				float deviceWidth = 1125;
+				float deviceHeight = 2436;
+				rect = new Rect(0f / deviceWidth, 102f / deviceHeight, 1125f / deviceWidth, 2202f / deviceHeight);
+			}
+			else
+			{
+				// 横屏Landscape
+				float deviceWidth = 2436;
+				float deviceHeight = 1125;
+				rect = new Rect(132f / deviceWidth, 63f / deviceHeight, 2172f / deviceWidth, 1062f / deviceHeight);
+			}
+
+			Rect safeArea = new Rect(Screen.width * rect.x, Screen.height * rect.y, Screen.width * rect.width, Screen.height * rect.height);
+			ApplyScreenSafeRect(safeArea);
+		}
+
+
+		/// <summary>
+		/// 获取窗口堆栈信息
+		/// </summary>
+		public static void GetWindowInfos(List<WindowInfo> output)
+		{
+			if (output == null)
+				output = new List<WindowInfo>();
+			else
+				output.Clear();
+
+			for (int i = 0; i < _stack.Count; i++)
+			{
+				var window = _stack[i];
+				WindowInfo info = new WindowInfo();
+				info.WindowName = window.WindowName;
+				info.WindowLayer = window.WindowLayer;
+				info.IsLoadDone = window.IsLoadDone;
+				output.Add(info);
+			}
+		}
+
+		/// <summary>
+		/// 获取所有层级下顶部的窗口名称
+		/// </summary>
+		public static string GetTopWindow()
+		{
+			if (_stack.Count == 0)
+				return string.Empty;
+
+			UIWindow topWindow = _stack[_stack.Count - 1];
+			return topWindow.WindowName;
+		}
+
+		/// <summary>
+		/// 获取指定层级下顶部的窗口名称
+		/// </summary>
+		public static string GetTopWindow(int layer)
+		{
+			UIWindow lastOne = null;
+			for (int i = 0; i < _stack.Count; i++)
+			{
+				if (_stack[i].WindowLayer == layer)
+					lastOne = _stack[i];
+			}
+
+			if (lastOne == null)
+				return string.Empty;
+
+			return lastOne.WindowName;
+		}
+
+		/// <summary>
+		/// 是否有任意窗口正在加载
+		/// </summary>
+		public static bool IsAnyLoading()
+		{
+			for (int i = 0; i < _stack.Count; i++)
+			{
+				var window = _stack[i];
+				if (window.IsLoadDone == false)
+					return true;
+			}
+			return false;
+		}
+
+		/// <summary>
+		/// 查询窗口是否存在
+		/// </summary>
+		public static bool HasWindow<T>()
+		{
+			return HasWindow(typeof(T));
+		}
+		public static bool HasWindow(Type type)
+		{
+			return IsContains(type.FullName);
+		}
+
+
+		/// <summary>
+		/// 异步打开窗口
+		/// </summary>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="userDatas">用户自定义数据</param>
+		public static OpenWindowOperation OpenWindowAsync<T>(string location, params System.Object[] userDatas) where T : UIWindow
+		{
+			return OpenWindowAsync(typeof(T), location, userDatas);
+		}
+		public static OpenWindowOperation OpenWindowAsync(Type type, string location, params System.Object[] userDatas)
+		{
+			string windowName = type.FullName;
+
+			// 如果窗口已经存在
+			if (IsContains(windowName))
+			{
+				UIWindow window = GetWindow(windowName);
+				Pop(window); //弹出窗口
+				Push(window); //重新压入
+				window.TryInvoke(OnWindowPrepare, userDatas);
+				var operation = new OpenWindowOperation(window.Handle);
+				YooAssets.StartOperation(operation);
+				return operation;
+			}
+			else
+			{
+				UIWindow window = CreateInstance(type);
+				Push(window); //首次压入
+				window.InternalLoad(location, OnWindowPrepare, userDatas);
+				var operation = new OpenWindowOperation(window.Handle);
+				YooAssets.StartOperation(operation);
+				return operation;
+			}
+		}
+
+		/// <summary>
+		/// 同步打开窗口
+		/// </summary>
+		/// <typeparam name="T">窗口类</typeparam>
+		/// <param name="location">资源定位地址</param>
+		/// <param name="userDatas">用户自定义数据</param>
+		public static OpenWindowOperation OpenWindowSync<T>(string location, params System.Object[] userDatas) where T : UIWindow
+		{
+			var operation = OpenWindowAsync(typeof(T), location, userDatas);
+			operation.WaitForAsyncComplete();
+			return operation;
+		}
+		public static OpenWindowOperation OpenWindowSync(Type type, string location, params System.Object[] userDatas)
+		{
+			var operation = OpenWindowAsync(type, location, userDatas);
+			operation.WaitForAsyncComplete();
+			return operation;
+		}
+
+		/// <summary>
+		/// 关闭窗口
+		/// </summary>
+		public static void CloseWindow<T>() where T : UIWindow
+		{
+			CloseWindow(typeof(T));
+		}
+		public static void CloseWindow(Type type)
+		{
+			string windowName = type.FullName;
+			UIWindow window = GetWindow(windowName);
+			if (window == null)
+				return;
+
+			window.InternalDestroy();
+			Pop(window);
+			OnSortWindowDepth(window.WindowLayer);
+			OnSetWindowVisible();
+		}
+
+		/// <summary>
+		/// 关闭所有窗口
+		/// </summary>
+		public static void CloseAll()
+		{
+			for (int i = 0; i < _stack.Count; i++)
+			{
+				UIWindow window = _stack[i];
+				window.InternalDestroy();
+			}
+			_stack.Clear();
+		}
+
+
+		private static void OnWindowPrepare(UIWindow window)
+		{
+			OnSortWindowDepth(window.WindowLayer);
+			window.InternalCreate();
+			window.InternalRefresh();
+			OnSetWindowVisible();
+		}
+		private static void OnSortWindowDepth(int layer)
+		{
+			int depth = layer;
+			for (int i = 0; i < _stack.Count; i++)
+			{
+				if (_stack[i].WindowLayer == layer)
+				{
+					_stack[i].Depth = depth;
+					depth += 100; //注意:每次递增100深度
+				}
+			}
+		}
+		private static void OnSetWindowVisible()
+		{
+			bool isHideNext = false;
+			for (int i = _stack.Count - 1; i >= 0; i--)
+			{
+				UIWindow window = _stack[i];
+				if (isHideNext == false)
+				{
+					window.Visible = true;
+					if (window.IsPrepare && window.FullScreen)
+						isHideNext = true;
+				}
+				else
+				{
+					window.Visible = false;
+				}
+			}
+		}
+
+		private static UIWindow CreateInstance(Type type)
+		{
+			UIWindow window = Activator.CreateInstance(type) as UIWindow;
+			WindowAttribute attribute = Attribute.GetCustomAttribute(type, typeof(WindowAttribute)) as WindowAttribute;
+
+			if (window == null)
+				throw new Exception($"Window {type.FullName} create instance failed.");
+			if (attribute == null)
+				throw new Exception($"Window {type.FullName} not found {nameof(WindowAttribute)} attribute.");
+
+			window.Init(type.FullName, attribute.WindowLayer, attribute.FullScreen);
+			return window;
+		}
+		private static UIWindow GetWindow(string name)
+		{
+			for (int i = 0; i < _stack.Count; i++)
+			{
+				UIWindow window = _stack[i];
+				if (window.WindowName == name)
+					return window;
+			}
+			return null;
+		}
+		private static bool IsContains(string name)
+		{
+			for (int i = 0; i < _stack.Count; i++)
+			{
+				UIWindow window = _stack[i];
+				if (window.WindowName == name)
+					return true;
+			}
+			return false;
+		}
+		private static void Push(UIWindow window)
+		{
+			// 如果已经存在
+			if (IsContains(window.WindowName))
+				throw new System.Exception($"Window {window.WindowName} is exist.");
+
+			// 获取插入到所属层级的位置
+			int insertIndex = -1;
+			for (int i = 0; i < _stack.Count; i++)
+			{
+				if (window.WindowLayer == _stack[i].WindowLayer)
+					insertIndex = i + 1;
+			}
+
+			// 如果没有所属层级,找到相邻层级
+			if (insertIndex == -1)
+			{
+				for (int i = 0; i < _stack.Count; i++)
+				{
+					if (window.WindowLayer > _stack[i].WindowLayer)
+						insertIndex = i + 1;
+				}
+			}
+
+			// 如果是空栈或没有找到插入位置
+			if (insertIndex == -1)
+			{
+				insertIndex = 0;
+			}
+
+			// 最后插入到堆栈
+			_stack.Insert(insertIndex, window);
+		}
+		private static void Pop(UIWindow window)
+		{
+			// 从堆栈里移除
+			_stack.Remove(window);
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UniWindow.cs.meta

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

+ 12 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UniWindowDriver.cs

@@ -0,0 +1,12 @@
+using UnityEngine;
+
+namespace UniFramework.Window
+{
+	internal class UniWindowDriver : MonoBehaviour
+	{
+		void Update()
+		{
+			UniWindow.Update();
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/UniWindowDriver.cs.meta

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

+ 24 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/WindowAttribute.cs

@@ -0,0 +1,24 @@
+using System;
+
+namespace UniFramework.Window
+{
+	[AttributeUsage(AttributeTargets.Class)]
+	public class WindowAttribute : Attribute
+	{
+		/// <summary>
+		/// 窗口层级
+		/// </summary>
+		public int WindowLayer;
+
+		/// <summary>
+		/// 全屏窗口标记
+		/// </summary>
+		public bool FullScreen;
+
+		public WindowAttribute(int windowLayer, bool fullScreen)
+		{
+			WindowLayer = windowLayer;
+			FullScreen = fullScreen;
+		}
+	}
+}

+ 11 - 0
GameClient/Assets/UniFramework/UniWindow/Runtime/WindowAttribute.cs.meta

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