using System; using System.Collections.Generic; using TH1_Core.Managers; using TH1_UI; using TH1_UI.Core; using TH1_UI.View.Base; using UnityEngine; // 这是你原有的代码,为了完整性我把它折叠起来了,无需修改 #region Unchanged Interfaces and Usings namespace TH1_UI.Controller.Base { public interface IViewControllerInterface { ViewDestroyDomain Domain { get; } ViewLevelValue ViewLevel { get; } ViewDepthProperty DepthProperty { get; } ViewOpenProperty OpenProperty { get; } ViewCloseProperty CloseProperty { get; } ViewLoadProperty LoadProperty { get; } string Name { get; } int Depth { get; set; } Action OnClosedCallback { get; set; } void Start(string subFolderPath, string resourceName, ViewDestroyDomain domain,UIRootType rootType); bool Open(); void UpdateView(); bool IsShow(); bool OpenWithParam(object param); bool Close(); void Destroy(); } #endregion public class ViewController : IViewControllerInterface where T : View.Base.View { protected object _openParameter; public ViewDestroyDomain Domain => _domain; public Action OnClosedCallback { get; set; } public ViewDepthProperty DepthProperty => _depthProperty; public ViewCloseProperty CloseProperty { get; } = new ViewCloseProperty(); public ViewOpenProperty OpenProperty { get; } = new ViewOpenProperty(); public ViewLoadProperty LoadProperty => _loadProperty; protected ViewHierarchyProperty HierarchyProperty { get; } = new ViewHierarchyProperty(); protected ViewCameraProperty CameraProperty { get; } = new ViewCameraProperty(); protected ViewMaskProperty MaskProperty { get; } = new ViewMaskProperty(); public ViewLevelValue ViewLevel => HierarchyProperty.ViewLevel; public string Name => _loadProperty.ControllerName; public int Depth { get => _depthProperty.Depth; set { if (IsShow()) { _script.SetSortingOrder(value); } } } public T WindowScript => _script; public GameObject WindowObj => _windowObj; public virtual void OnMatchStart() { } public virtual void AfterMatchStart() { } protected void AddLoadAtlas(string name) { UIResourceController.Instance.AddAtlasHolder(name, LoadProperty.ControllerName); if (!_atlasLoadList.Contains(name)) { _atlasLoadList.Add(name); } } public virtual void Start(string subFolderPath, string resourceName, ViewDestroyDomain domain,UIRootType rootType) { //step #1 初始化,处理销毁域 _domain = domain; //step #2 处理loadProperty相关的参数以供prefab加载 LoadProperty.SubFolderPath = subFolderPath; LoadProperty.ResourceName = resourceName; LoadProperty.ControllerName = this.GetType().Name; LoadProperty.UIRootType = rootType; //step #3 处理深度、层级和camra,目前全部架空了,没用,仅保留步骤供将来使用。深度目前使用UIRootType方案来替代(分组了) DepthProperty.Start(this); HierarchyProperty.Start(this); CameraProperty.ThisController = this; } public virtual bool Open() { _atlasPreloadingValue.Clear(); if (EnableOpen()) { _TryOpen(); return true; } else { return false; } } //带参数open,目前架空了 public virtual bool OpenWithParam(object param) { _openParameter = param; return Open(); } //判断能否打开。目前架空了 protected virtual bool EnableOpen() { return true; } bool _TryOpen() { //step #1 如果已经在显示了,那么更新数据就行 return false if (IsShow()) { UpdateView(); return false; } // step #2 预加载 目前架空了,一定不会走预加载 bool preLoadWait = _PreLoad(); if (preLoadWait) { _atlasPreloadingValue.Add("PreLoad", 30, true); } // step #3 如果找不到窗口 ,那么要先create对应的prefab if (WindowObj == null) { // 现在的 GetWindow 只负责查找,Key 使用 ResourceName GameObject window = UIResourceController.Instance.GetWindow(LoadProperty.ResourceName); if (window == null) { // 如果找不到,就启动我们改造后的 ViewLoadProperty 去加载 if (LoadProperty.LoadState == LoadWindowState.NONE) { //LoadProperty.Attach(_loadViewCallback, _OnViewLoadedCallback); LoadProperty.Start(_OnViewLoadedCallback); //_OnViewLoadedCallback回调触发的时候会触发_OnViewStart } } else { // 如果找到了,说明是之前加载过的,直接开始后续流程 LoadProperty.Invoke(); _OnWindowLoadNotify(window); _OnViewStart(); } } // step #4 如果已经有窗口可用 ,直接开干 else { _OnViewStart(); } return true; } protected virtual bool _PreLoad() { //通知所有子界面也做好preload的准备 _PreLoadSubViewController(); //加载所有图集资源 ,不过现在我们不用,默认返回false return UIResourceController.Instance.LoadAtlasList(_atlasLoadList, _OnCompletePreLoad); } void _OnCompletePreLoad() { if (_atlasPreloadingValue.Has("PreLoad")) { _atlasPreloadingValue.Del("PreLoad"); _OnViewStart(); } } void _OnViewLoadedCallback()//UInt32 eveID) 原来传入一个eveId,现在我干掉了 { //在异步(现在是同步)回调中,同样使用新的 GetWindow 方法 GameObject window = UIResourceController.Instance.GetWindow(LoadProperty.ResourceName); _OnWindowLoadNotify(window); _OnViewStart(); } //window加载完了通知用的 void _OnWindowLoadNotify(System.Object obj) { // step #1 将obj设为当前viewController派生类的_windowObj窗口对象 _windowObj = obj as GameObject; if (_windowObj == null) { // 为了兼容性,暂时注释掉需要ViDebuger的代码 // ViDebuger.Error("Window GameObject is NULL! [res: " + LoadProperty.ResourceName + "]"); Debug.LogError("Window GameObject is NULL! [res: " + LoadProperty.ResourceName + "]"); return; } // step #2 获取子节点的UIWindowProperty对象 UIWindowProperty propertyScript = _windowObj?.GetComponentInChildren(); if (propertyScript == null) { // ViDebuger.Error(WindowObj.name + " Script is null, Then Initializtion error!"); Debug.LogError(WindowObj.name + " Script is null, Then Initializtion error!"); } // step #3 获取对应的View派生类的对象,设置好_script _script = _windowObj?.GetComponent(); if (_script == null) { _script = _windowObj?.AddComponent(); } _script?.Initialize(propertyScript); // step #4 加载完毕,触发OnLoaded OnLoaded(); //step #5 依次处理subview的情况,目前架空,没有subview的情景 _LoadSubViewController(_windowObj); } void _OnViewStart() { if (WindowObj != null && WindowScript != null && !_atlasPreloadingValue.Value) { _OnOpenWindowNotify(WindowObj); } } void _OnOpenWindowNotify(System.Object obj) { _TrueOpen(); } void _TrueOpen() { if (_script != null) { _script.SetEnableClick(MaskProperty.EnableClick); _script.Show(); } DepthProperty.Open(); RegisterEventCallback(); OpenProperty.Execute(); HierarchyProperty.Open(); CameraProperty.Open(); OnOpen(); _OpenSubViewController(); OnOpenSub(); } void _TrueClose() { // 这些是需要在动画开始前就执行的逻辑 UnregisterEventCallback(); OnClose(); _CloseSubViewController(); DepthProperty.Close(); CameraProperty.Close(); _openParameter = null; // 定义一个 Action,它包含了所有必须在动画播放完毕后才执行的逻辑 Action postHideAction = () => { // 添加调试日志 //Debug.Log($"[VIEW] '{Name}' hide animation complete. Preparing to invoke OnClosedCallback."); //Debug.Log($"[VIEW] Before Invoke, OnClosedCallback is: '{(object)OnClosedCallback ?? "null"}'."); HierarchyProperty.Close(); CloseProperty.Execute(); OnClosedCallback?.Invoke(); //血的教训:再这里会设置下一个OnClosedCallback,结果下一行直接给 = null了,白设置。这里不应该清空OnClosedCallback的!!! //OnClosedCallback = null; }; // 如果 View 脚本存在,就调用带回调的 Hide 方法 if (_script != null) { // 把清理逻辑作为回调传进去,这样 View 就会在动画播放完后调用它 // 添加调试日志 //Debug.Log($"[VIEW] '{Name}' calling _script.Hide() with callback."); _script.Hide(postHideAction); } else { // 如果 View 脚本不存在(例如,窗口还未加载完就被关闭), // 就没有动画可播,直接执行清理逻辑。 //Debug.Log($"[VIEW] '{Name}' script is null, executing postHideAction immediately."); postHideAction(); } } public virtual bool Close() { _loadViewCallback.End(); _atlasPreloadingValue.Clear(); if (IsShow()) { _ReleasePreLoad(); _TrueClose(); return true; } else { _atlasLoadList.Clear(); if (LoadProperty.LoadState == LoadWindowState.LOADING) { _loadViewCallback.End(); LoadProperty.LoadState = LoadWindowState.NONE; } else if (LoadProperty.LoadState == LoadWindowState.LOADED) { _loadViewCallback.End(); } return true; } } protected bool _ReleasePreLoad() { for (int iter = 0; iter < _atlasLoadList.Count; ++iter) { string iterAtlas = _atlasLoadList[iter]; if (UIResourceController.Instance.DelAtlasHolder(iterAtlas, LoadProperty.ControllerName)) { UIResourceController.Instance.ReleaseAtlas(iterAtlas); } } _atlasLoadList.Clear(); return true; } public virtual void Destroy() { Close(); _ReleasePreLoad(); _DestroySubViewController(); // ✅ 核心修改 #3: 删除窗口时,使用的key必须和获取/添加窗口时一致,即 ResourceName UIResourceController.Instance.DelWindow(LoadProperty.ResourceName); if (_windowObj != null) { // GameObject.Destroy() 已经是 UIResourceController.DelWindow 的一部分了 // 所以这里不需要再 Destroy,只需要将引用置空 _windowObj = null; } _script = null; LoadProperty.End(); } public virtual bool IsShow() { if (_script == null) { return false; } return _script.IsShow(); } public virtual void UpdateView() { } // ... (这里往后的代码,包括子界面管理等,都保持原样,无需改动) ... protected virtual void OnLoaded() { } protected virtual void OnOpen() { } protected virtual void OnOpenSub() { } protected virtual void RegisterEventCallback() { } protected virtual void UnregisterEventCallback() { } protected virtual void OnClose() { } T _script; ViewDestroyDomain _domain; GameObject _windowObj; ViAsynCallback _loadViewCallback = new ViAsynCallback(); // Vi的异步回调事件 ViewDepthProperty _depthProperty = new ViewDepthProperty(); ViewLoadProperty _loadProperty = new ViewLoadProperty(); ViPriorityValue _atlasPreloadingValue = new ViPriorityValue(false); // List _atlasLoadList = new List(); Dictionary _subViewDict = new Dictionary(); protected T1 AddSubViewController(string childPath, string ex = null) where T1 : ViewControllerSubInterface, new() { var sub = new T1(); sub.Start(childPath, this); _subViewDict.Add(typeof(T1).Name + ex, sub); return sub; } public T1 GetSubViewController(string ex = null) where T1 : ViewControllerSubInterface, new() { _subViewDict.TryGetValue(typeof(T1).Name + ex, out var subViewController); return (T1) subViewController; } void _PreLoadSubViewController() { foreach (var kv in _subViewDict) { var subViewController = kv.Value; subViewController.PreLoad(); var loadAtlas = subViewController.GetLoadAtlas(); foreach (var atlas in loadAtlas) { AddLoadAtlas(atlas); } } } void _LoadSubViewController(GameObject windowObj) { foreach (var kv in _subViewDict) { kv.Value.OnWindowLoadNotify(windowObj); } } void _OpenSubViewController() { foreach (var kv in _subViewDict) { if (kv.Value.DefaultOpen) { kv.Value.Open(); } } } void _CloseSubViewController() { foreach (var kv in _subViewDict) { kv.Value.Close(); } } void _DestroySubViewController() { foreach (var kv in _subViewDict) { kv.Value.Destroy(); } } protected void CloseAllSubView() { foreach (var kv in _subViewDict) { kv.Value.Close(); } } } }