using FairyGUI; using Spine.Unity; using System; using System.Collections.Generic; using UnityEngine; using XGame.Framework.Asset; using XGame.Framework.Asyncs; using XGame.Framework.Interfaces; using XGame.Framework.UI; namespace XGame.Framework.FGUI { internal partial class FguiModule : IUIModule, IUpdate, ILateUpdate, IUIContextSetter, IDisposable, IUIAssetModuleCreator, IPackageHandle { /// /// bitmapFont的shader名字 /// private const string bitmap_font_shader = "XGame/Text Shader"; private UIContext _context; private IUITree _uitree; private IAssetModule _assetModule; /// /// 加载中的 /// private Dictionary _loadingMap; /// /// 已打开的UI /// private Dictionary _openedMap; /// /// 已关闭的UI /// private Dictionary _closedMap; /// /// Update 和 LateUpdate用 /// private List _updates; /// /// 已加载的字体 /// private HashSet _fontNames; public FguiModule(IAssetModule assetModule, IUITree uitree) { _assetModule = assetModule; _uitree = uitree; _loadingMap = new Dictionary(); _openedMap = new Dictionary(); _closedMap = new Dictionary(); _updates = new List(); _fontNames = new HashSet(); //Gtween的默认曲线 GTweener.defaultEaseType = EaseType.Linear; #region 只对PackageItem有效 NTexture.CustomDestroyMethod += RecycleTexture; NAudioClip.CustomDestroyMethod += RecycleAudio; #if FAIRYGUI_SPINE GLoader3D.CustomLoadSpine += OnLoadSpine; GLoader3D.CustomSpineDestroyMethod += RecycleSpine; #endif #endregion UIObjectFactory.SetLoaderExtension(CreatCustomLoader); } public UIContext Context { get => _context; set => _context = value; } #region 接口实现 Camera IUIModule.Camera => _uitree.Camera; Canvas IUIModule.GetCanvas(UILayer layer) { return _uitree.GetCanvas(layer); } IAsync IUIModule.OpenAsync(UIKey uikey, object intent) { string key = uikey; if (_loadingMap.ContainsKey(key)) { return _loadingMap[key]; } var asyncGroup = new AsyncGroup(); if (_openedMap.ContainsKey(key)) { Log.Debug($"重复开启UI:{key}"); } else if (_closedMap.TryGetValue(key, out var closedView)) { // 已关闭的UI asyncGroup.On(_ => { _closedMap.Remove(key); _uitree.SetAsLastSibling(closedView.Panel); _openedMap.Add(key, closedView); closedView.Enable(intent); FrameworkEvent.Instance.Notify(EventDefine.UI_OPENED, uikey); }); } else { _loadingMap.Add(key, asyncGroup); var objLoadAsync = new GObjectFromPackageAsync(uikey.PackageName, uikey.PanelName); objLoadAsync.Join(asyncGroup); objLoadAsync.On(_ => { _loadingMap.Remove(key); var panelObj = objLoadAsync.Result as GComponent; if (panelObj == null) { Log.Error($"UI加载结果为空. UIKey:{uikey}"); return; } #if UNITY_EDITOR panelObj.displayObject.gameObject.name = uikey.PackageName + uikey.PanelName; #endif var panel = new FguiPanel(panelObj, GetLayer(uikey.UIViewType)); var view = Activator.CreateInstance(uikey.UIViewType) as IUIView; var context = _context.Clone(); (context as IUIViewAdapter).Key = uikey; (view as UIView).Init(context, panel); _uitree.AddPanel(panel); _openedMap.Add(key, view); view.Enable(intent); FrameworkEvent.Instance.Notify(EventDefine.UI_OPENED, uikey); }); var pkgLoadAsync = LoadPackageAsync(uikey); if (pkgLoadAsync != null) { pkgLoadAsync.Join(asyncGroup); pkgLoadAsync.On(_ => { objLoadAsync.Start(); }); } else { objLoadAsync.Start(); } } asyncGroup.End(); return asyncGroup; } void IUIModule.Close(UIKey uikey, bool isDestroy) { var key = uikey.Key; if (_loadingMap.TryGetValue(key, out var async)) { _loadingMap.Remove(key); async.RemoveAll(); return; } if (_openedMap.TryGetValue(key, out var uiView)) { _openedMap.Remove(key); uiView.Disable(); _uitree.RemovePanel(uiView.Panel); if (isDestroy) { DestroyView(uiView); } else { _closedMap.Add(key, uiView); } FrameworkEvent.Instance.Notify(EventDefine.UI_CLOSED, uikey); return; } if (isDestroy && _closedMap.TryGetValue(key, out uiView)) { _closedMap.Remove(key); DestroyView(uiView); } } IAsync IUIModule.Preload(UIKey uikey) { var loadAsync = LoadPackageAsync(uikey); return loadAsync; } bool IUIModule.IsOpened(UIKey uikey) { return _openedMap.ContainsKey(uikey); } void IUpdate.Update(int millisecond) { if (_openedMap.Count == 0) return; _updates.Clear(); _updates.AddRange(_openedMap.Values); for (var i = 0; i < _updates.Count;) { var view = _updates[i]; view.Update(millisecond); if (view.Active) { i++; } else { _updates.RemoveAt(i); } } } void ILateUpdate.LateUpdate(int millisecond) { if (_updates.Count == 0) return; foreach (var uiview in _updates) { uiview.LateUpdate(millisecond); } _updates.Clear(); } void IDisposable.Dispose() { _updates.Clear(); foreach (var async in _loadingMap.Values) { async.RemoveAll(); } _loadingMap.Clear(); foreach (var uiview in _closedMap.Values) { DestroyView(uiview); } _closedMap.Clear(); foreach (var uiview in _openedMap.Values) { DestroyView(uiview); } _openedMap.Clear(); foreach (var fontName in _fontNames) { var font = FontManager.UnregisterFont(fontName); if (font is DynamicFont dynFont) { _assetModule.Recycle(dynFont.nativeFont); } font?.Dispose(); } _fontNames.Clear(); #region 清理FGUI緩存 StageEngine.beingQuit = true; UIPackage.RemoveAllPackages(); Stage.inst.Dispose(); NTexture.CustomDestroyMethod -= RecycleTexture; NAudioClip.CustomDestroyMethod -= RecycleAudio; #if FAIRYGUI_SPINE GLoader3D.CustomLoadSpine -= OnLoadSpine; GLoader3D.CustomSpineDestroyMethod -= RecycleSpine; #endif UIObjectFactory.Clear(); ClearGloaderAssets(); #endregion (_uitree as IDisposable)?.Dispose(); (_assetModule as IDisposable)?.Dispose(); _assetModule = null; _context = null; } #endregion #region IPackageHandle 接口实现 public IGObjectLoadAsync LoadGobjectAsync(UIKey uikey) { var objLoadAsync = new GObjectFromPackageAsync(uikey.PackageName, uikey.PanelName); var pkgLoadAsync = LoadPackageAsync(uikey); if (pkgLoadAsync != null) { pkgLoadAsync.On(_ => { objLoadAsync.Start(); }); } else { objLoadAsync.Start(); } return objLoadAsync; } /// /// 加载UIPackage /// /// /// public IAsync LoadPackageAsync(UIKey uikey) { if (UIPackage.GetByName(uikey.PackageName) == null) { var pkgAddressable = FguiUtils.ToDescFileName(uikey.PackageName); if (_loadingMap.ContainsKey(pkgAddressable)) { return _loadingMap[pkgAddressable]; } var pkgLoadAsync = _assetModule.LoadAsync(pkgAddressable); _loadingMap.Add(pkgAddressable, pkgLoadAsync); pkgLoadAsync.On(_ => { _loadingMap.Remove(pkgAddressable); var pkgAsset = pkgLoadAsync.Result; var descData = pkgAsset?.bytes; _assetModule.Recycle(pkgAsset); if (descData == null || descData.Length == 0) { Log.Error($"UIPackage 加载失败. UIKey:{uikey}"); return; } if (UIPackage.GetByName(uikey.PackageName) == null) { UIPackage.AddPackage(descData, string.Empty, LoadPackageItemAsync); //var pkg = UIPackage.GetByName(uikey.PackageName); //if (pkg != null && pkg.dependencies != null) //{ // Log.Debug($"{uikey.PackageName} dependencies:{pkg.dependencies.Length}"); // foreach (var dependency in pkg.dependencies) // { // foreach(var item in dependency) // { // Log.Debug($"dependency key:{item.Key} value:{item.Value}"); // } // } //} } }); return pkgLoadAsync; } else { Log.Info($"UIPackage已加载. UIKey:{uikey}"); return null; } } public IAsync LoadFontAsync(string fontName) { if (_fontNames.Contains(fontName)) { return null; } if (_loadingMap.TryGetValue(fontName, out var loading)) { return loading; } var async = _assetModule.LoadAsync(fontName); _loadingMap.Add(fontName, async); async.On(_ => { _loadingMap.Remove(fontName); var result = async.Result; if (result == null) return; _fontNames.Add(fontName); var fguiFont = new DynamicFont() { name = fontName, nativeFont = result }; if (result.material != null && result.material.shader.name == bitmap_font_shader) { // bitmapfont字体使用自带的shader fguiFont.shader = bitmap_font_shader; } FontManager.RegisterFont(fguiFont); }); return async; } #endregion IUIAssetModule IUIAssetModuleCreator.Create(UIContext context) { return new FguiAssetModule(context, this); } #region 内部函数 /// /// 异步加载PackageItem /// UIPackage的Texture、AudioClip等资源 /// /// /// /// /// private void LoadPackageItemAsync(string name, string extension, System.Type type, PackageItem item) { Log.Info($"LoadPackageItemAsync name:{name} extension:{extension} type:{type} itemName:{item.name}"); var addressable = $"{item.owner.name}_{name}"; var loadAsync = _assetModule.LoadAsync(addressable); loadAsync.On(_ => { var asset = loadAsync.Result; item.owner.SetItemAsset(item, Convert(asset, type), DestroyMethod.Custom); }); } private object Convert(UnityEngine.Object value, Type type) { if (value == null) return default; if (type.IsInstanceOfType(value)) { return value; } if (type.IsClass) { if (type.IsSubclassOf(typeof(Component))) { var go = value as GameObject; if (go != null) { return go.GetComponent(type); } var component = value as Component; if (component != null) { return component.gameObject.GetComponent(type); } } else if (type == typeof(Sprite) && value is Texture2D texture) { object sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); return sprite; } } Log.Error($"UIModuleFGUI object conver type Error. source type:{value.GetType()} name:{value} target:{type}"); return value; } private void DestroyView(IUIView view) { var uiPanel = (view.Panel as FguiPanel).Panel; (view as IDisposable).Dispose(); try { uiPanel.Dispose(); } catch(Exception ex) { Log.Exception($"FguiModule DestroyView exception.", ex); } } /// /// 只对LoadPackageItemAsync加载的资源有效 /// /// private void RecycleTexture(Texture texture) { Log.Info($"RecycleTexture Texture:{texture.name}"); _assetModule.Recycle(texture); } /// /// 只对LoadPackageItemAsync加载的资源有效 /// /// private void RecycleAudio(AudioClip audioClip) { Log.Info($"RecycleAudio AudioClip:{audioClip.name}"); _assetModule?.Recycle(audioClip); } private GLoader CreatCustomLoader() { return new CustomLoader(this); } private void OnLoadSpine(string addressable, Action action) { var loadAsync = _assetModule.LoadAsync(addressable); loadAsync.On(_ => { var data = loadAsync.Result; if (data == null) { return; } action(data); }); } private void RecycleSpine(SkeletonDataAsset asset) { _assetModule.Recycle(asset); } private UILayer GetLayer(Type uiviewType) { var layer = UILayer.Normal; var property = uiviewType.GetProperty("Layer", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreCase); if (property != null) { layer = (UILayer)property.GetValue(null); } return layer; } #endregion } }