FguiModule.cs 17 KB

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