FguiModule.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. using FairyGUI;
  2. using Spine.Unity;
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using XGame.Framework.Asset;
  7. using XGame.Framework.Asyncs;
  8. using XGame.Framework.Interfaces;
  9. using XGame.Framework.UI;
  10. namespace XGame.Framework.FGUI
  11. {
  12. internal partial class FguiModule : IUIModule, IUpdate, ILateUpdate, IUIContextSetter, IDisposable, IUIAssetModuleCreator, IPackageHandle
  13. {
  14. private UIContext _context;
  15. private IUITree _uitree;
  16. private IAssetModule _assetModule;
  17. /// <summary>
  18. /// 加载中的
  19. /// </summary>
  20. private Dictionary<string, IAsync> _loadingMap;
  21. /// <summary>
  22. /// 已打开的UI
  23. /// </summary>
  24. private Dictionary<string, IUIView> _openedMap;
  25. /// <summary>
  26. /// 已关闭的UI
  27. /// </summary>
  28. private Dictionary<string, IUIView> _closedMap;
  29. /// <summary>
  30. /// Update 和 LateUpdate用
  31. /// </summary>
  32. private List<IUIView> _updates;
  33. public FguiModule(IAssetModule assetModule, IUITree uitree)
  34. {
  35. _assetModule = assetModule;
  36. _uitree = uitree;
  37. _loadingMap = new Dictionary<string, IAsync>();
  38. _openedMap = new Dictionary<string, IUIView>();
  39. _closedMap = new Dictionary<string, IUIView>();
  40. _updates = new List<IUIView>();
  41. //Gtween的默认曲线
  42. GTweener.defaultEaseType = EaseType.Linear;
  43. #region 只对PackageItem有效
  44. NTexture.CustomDestroyMethod += RecycleTexture;
  45. NAudioClip.CustomDestroyMethod += RecycleAudio;
  46. GLoader3D.CustomLoadSpine += OnLoadSpine;
  47. GLoader3D.CustomSpineDestroyMethod += RecycleSpine;
  48. #endregion
  49. UIObjectFactory.SetLoaderExtension(CreatCustomLoader);
  50. }
  51. public UIContext Context { get => _context; set => _context = value; }
  52. #region 接口实现
  53. Camera IUIModule.Camera => _uitree.Camera;
  54. Canvas IUIModule.GetCanvas(UILayer layer)
  55. {
  56. return _uitree.GetCanvas(layer);
  57. }
  58. IAsync IUIModule.OpenAsync(UIKey uikey, object intent)
  59. {
  60. string key = uikey;
  61. if (_loadingMap.ContainsKey(key))
  62. {
  63. return _loadingMap[key];
  64. }
  65. var asyncGroup = new AsyncGroup();
  66. if (_openedMap.ContainsKey(key))
  67. {
  68. Log.Warn($"重复开启UI:{key}");
  69. }
  70. else if (_closedMap.TryGetValue(key, out var closedView))
  71. { // 已关闭的UI
  72. asyncGroup.On(_ =>
  73. {
  74. _closedMap.Remove(key);
  75. _uitree.SetAsLastSibling(closedView.Panel);
  76. closedView.Enable(intent);
  77. _openedMap.Add(key, closedView);
  78. FrameworkEvent.Instance.Notify(EventDefine.UI_OPENED, uikey);
  79. });
  80. }
  81. else
  82. {
  83. _loadingMap.Add(key, asyncGroup);
  84. var objLoadAsync = new GObjectFromPackageAsync(uikey.PackageName, uikey.PanelName);
  85. objLoadAsync.Join(asyncGroup);
  86. objLoadAsync.On(_ =>
  87. {
  88. _loadingMap.Remove(key);
  89. var panelObj = objLoadAsync.Result as GComponent;
  90. if (panelObj == null)
  91. {
  92. Log.Error($"UI加载结果为空. UIKey:{uikey}");
  93. return;
  94. }
  95. var panel = new FguiPanel(panelObj, GetLayer(uikey.UIViewType));
  96. var view = Activator.CreateInstance(uikey.UIViewType) as IUIView;
  97. var context = _context.Clone();
  98. (context as IUIViewAdapter).Key = uikey;
  99. (view as UIView).Init(context, panel);
  100. _uitree.AddPanel(panel);
  101. view.Enable(intent);
  102. _openedMap.Add(key, view);
  103. FrameworkEvent.Instance.Notify(EventDefine.UI_OPENED, uikey);
  104. });
  105. var pkgLoadAsync = LoadPackageAsync(uikey);
  106. if (pkgLoadAsync != null)
  107. {
  108. pkgLoadAsync.Join(asyncGroup);
  109. pkgLoadAsync.On(_ =>
  110. {
  111. objLoadAsync.Start();
  112. });
  113. }
  114. else
  115. {
  116. objLoadAsync.Start();
  117. }
  118. }
  119. asyncGroup.End();
  120. return asyncGroup;
  121. }
  122. void IUIModule.Close(UIKey uikey, bool isDestroy)
  123. {
  124. var key = uikey.Key;
  125. if (_loadingMap.TryGetValue(key, out var async))
  126. {
  127. _loadingMap.Remove(key);
  128. async.RemoveAll();
  129. return;
  130. }
  131. if (_openedMap.TryGetValue(key, out var uiView))
  132. {
  133. _openedMap.Remove(key);
  134. uiView.Disable();
  135. _uitree.RemovePanel(uiView.Panel);
  136. if (isDestroy)
  137. {
  138. DestroyView(uiView);
  139. }
  140. else
  141. {
  142. _closedMap.Add(key, uiView);
  143. }
  144. FrameworkEvent.Instance.Notify(EventDefine.UI_CLOSED, uikey);
  145. return;
  146. }
  147. if (isDestroy && _closedMap.TryGetValue(key, out uiView))
  148. {
  149. _closedMap.Remove(key);
  150. DestroyView(uiView);
  151. }
  152. }
  153. IAsync IUIModule.Preload(UIKey uikey)
  154. {
  155. var loadAsync = LoadPackageAsync(uikey);
  156. return loadAsync;
  157. }
  158. void IUpdate.Update(int millisecond)
  159. {
  160. if (_openedMap.Count == 0)
  161. return;
  162. _updates.Clear();
  163. _updates.AddRange(_openedMap.Values);
  164. for (var i = 0; i < _updates.Count;)
  165. {
  166. var view = _updates[i];
  167. view.Update(millisecond);
  168. if (view.Active)
  169. {
  170. i++;
  171. }
  172. else
  173. {
  174. _updates.RemoveAt(i);
  175. }
  176. }
  177. }
  178. void ILateUpdate.LateUpdate(int millisecond)
  179. {
  180. if (_updates.Count == 0)
  181. return;
  182. foreach (var uiview in _updates)
  183. {
  184. uiview.LateUpdate(millisecond);
  185. }
  186. _updates.Clear();
  187. }
  188. void IDisposable.Dispose()
  189. {
  190. _updates.Clear();
  191. foreach (var async in _loadingMap.Values)
  192. {
  193. async.RemoveAll();
  194. }
  195. _loadingMap.Clear();
  196. foreach (var uiview in _closedMap.Values)
  197. {
  198. DestroyView(uiview);
  199. }
  200. _closedMap.Clear();
  201. foreach (var uiview in _openedMap.Values)
  202. {
  203. DestroyView(uiview);
  204. }
  205. _openedMap.Clear();
  206. #region 清理FGUI緩存
  207. StageEngine.beingQuit = true;
  208. UIPackage.RemoveAllPackages();
  209. Stage.inst.Dispose();
  210. NTexture.CustomDestroyMethod -= RecycleTexture;
  211. NAudioClip.CustomDestroyMethod -= RecycleAudio;
  212. GLoader3D.CustomLoadSpine -= OnLoadSpine;
  213. GLoader3D.CustomSpineDestroyMethod -= RecycleSpine;
  214. UIObjectFactory.Clear();
  215. ClearGloaderAssets();
  216. #endregion
  217. (_uitree as IDisposable)?.Dispose();
  218. (_assetModule as IDisposable)?.Dispose();
  219. _assetModule = null;
  220. _context = null;
  221. }
  222. #endregion
  223. #region IPackageHandle 接口实现
  224. public IGObjectLoadAsync LoadGobjectAsync(UIKey uikey)
  225. {
  226. var objLoadAsync = new GObjectFromPackageAsync(uikey.PackageName, uikey.PanelName);
  227. var pkgLoadAsync = LoadPackageAsync(uikey);
  228. if (pkgLoadAsync != null)
  229. {
  230. pkgLoadAsync.On(_ =>
  231. {
  232. objLoadAsync.Start();
  233. });
  234. }
  235. else
  236. {
  237. objLoadAsync.Start();
  238. }
  239. return objLoadAsync;
  240. }
  241. /// <summary>
  242. /// 加载UIPackage
  243. /// </summary>
  244. /// <param name="uikey"></param>
  245. /// <returns></returns>
  246. public IAsync LoadPackageAsync(UIKey uikey)
  247. {
  248. if (UIPackage.GetByName(uikey.PackageName) == null)
  249. {
  250. var pkgAddressable = FguiUtils.ToDescFileName(uikey.PackageName);
  251. if (_loadingMap.ContainsKey(pkgAddressable))
  252. {
  253. return _loadingMap[pkgAddressable];
  254. }
  255. var pkgLoadAsync = _assetModule.LoadAsync<TextAsset>(pkgAddressable);
  256. _loadingMap.Add(pkgAddressable, pkgLoadAsync);
  257. pkgLoadAsync.On(_ =>
  258. {
  259. _loadingMap.Remove(pkgAddressable);
  260. var pkgAsset = pkgLoadAsync.Result;
  261. var descData = pkgAsset?.bytes;
  262. _assetModule.Recycle(pkgAsset);
  263. if (descData == null || descData.Length == 0)
  264. {
  265. Log.Error($"UIPackage 加载失败. UIKey:{uikey}");
  266. return;
  267. }
  268. if (UIPackage.GetByName(uikey.PackageName) == null)
  269. {
  270. UIPackage.AddPackage(descData, string.Empty, LoadPackageItemAsync);
  271. //var pkg = UIPackage.GetByName(uikey.PackageName);
  272. //if (pkg != null && pkg.dependencies != null)
  273. //{
  274. // Log.Debug($"{uikey.PackageName} dependencies:{pkg.dependencies.Length}");
  275. // foreach (var dependency in pkg.dependencies)
  276. // {
  277. // foreach(var item in dependency)
  278. // {
  279. // Log.Debug($"dependency key:{item.Key} value:{item.Value}");
  280. // }
  281. // }
  282. //}
  283. }
  284. });
  285. return pkgLoadAsync;
  286. }
  287. else
  288. {
  289. Log.Info($"UIPackage已加载. UIKey:{uikey}");
  290. return null;
  291. }
  292. }
  293. #endregion
  294. IUIAssetModule IUIAssetModuleCreator.Create(UIContext context)
  295. {
  296. return new FguiAssetModule(context, this);
  297. }
  298. #region 内部函数
  299. /// <summary>
  300. /// 异步加载PackageItem
  301. /// UIPackage的Texture、AudioClip等资源
  302. /// </summary>
  303. /// <param name="name"></param>
  304. /// <param name="extension"></param>
  305. /// <param name="type"></param>
  306. /// <param name="item"></param>
  307. private void LoadPackageItemAsync(string name, string extension, System.Type type, PackageItem item)
  308. {
  309. Log.Info($"LoadPackageItemAsync name:{name} extension:{extension} type:{type} itemName:{item.name}");
  310. var addressable = $"{item.owner.name}_{name}";
  311. var loadAsync = _assetModule.LoadAsync<UnityEngine.Object>(addressable);
  312. loadAsync.On(_ =>
  313. {
  314. var asset = loadAsync.Result;
  315. item.owner.SetItemAsset(item, Convert(asset, type), DestroyMethod.Custom);
  316. });
  317. }
  318. private object Convert(UnityEngine.Object value, Type type)
  319. {
  320. if (value == null)
  321. return default;
  322. if (type.IsInstanceOfType(value))
  323. {
  324. return value;
  325. }
  326. if (type.IsClass)
  327. {
  328. if (type.IsSubclassOf(typeof(Component)))
  329. {
  330. var go = value as GameObject;
  331. if (go != null)
  332. {
  333. return go.GetComponent(type);
  334. }
  335. var component = value as Component;
  336. if (component != null)
  337. {
  338. return component.gameObject.GetComponent(type);
  339. }
  340. }
  341. else if (type == typeof(Sprite) && value is Texture2D texture)
  342. {
  343. object sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
  344. return sprite;
  345. }
  346. }
  347. Log.Error($"UIModuleFGUI object conver type Error. source type:{value.GetType()} name:{value} target:{type}");
  348. return value;
  349. }
  350. private void DestroyView(IUIView view)
  351. {
  352. var uiPanel = (view.Panel as FguiPanel).Panel;
  353. (view as IDisposable).Dispose();
  354. uiPanel.Dispose();
  355. }
  356. /// <summary>
  357. /// 只对LoadPackageItemAsync加载的资源有效
  358. /// </summary>
  359. /// <param name="texture"></param>
  360. private void RecycleTexture(Texture texture)
  361. {
  362. Log.Info($"RecycleTexture Texture:{texture.name}");
  363. _assetModule.Recycle(texture);
  364. }
  365. /// <summary>
  366. /// 只对LoadPackageItemAsync加载的资源有效
  367. /// </summary>
  368. /// <param name="audioClip"></param>
  369. private void RecycleAudio(AudioClip audioClip)
  370. {
  371. Log.Info($"RecycleAudio AudioClip:{audioClip.name}");
  372. _assetModule?.Recycle(audioClip);
  373. }
  374. private GLoader CreatCustomLoader()
  375. {
  376. return new CustomLoader(this);
  377. }
  378. private void OnLoadSpine(string addressable, Action<SkeletonDataAsset> action)
  379. {
  380. var loadAsync = _assetModule.LoadAsync<SkeletonDataAsset>(addressable);
  381. loadAsync.On(_ =>
  382. {
  383. var data = loadAsync.Result;
  384. if (data == null)
  385. {
  386. return;
  387. }
  388. action(data);
  389. });
  390. }
  391. private void RecycleSpine(SkeletonDataAsset asset)
  392. {
  393. _assetModule.Recycle(asset);
  394. }
  395. private UILayer GetLayer(Type uiviewType)
  396. {
  397. var layer = UILayer.Normal;
  398. var property = uiviewType.GetProperty("Layer", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreCase);
  399. if (property != null)
  400. {
  401. layer = (UILayer)property.GetValue(null);
  402. }
  403. return layer;
  404. }
  405. #endregion
  406. }
  407. }