UI框架从0到1第八节:【初始化优先级】三大层级定序,杜绝找不到父级的尴尬

在上一节中,我们成功实现了通过 UIManager 获取每一个页面的 ViewBase 派生类实例,彻底告别了 GetComponent、拖引用、硬查找这些低效方式。我们的框架第一次拥有了一个真正意义上的“统一面板入口”。

但随着使用深入,一个令人不安的问题浮出水面:

我们的控件(ButtonCustom、ToggleCustom)是在各自的 Init() 方法中通过 transform.parent 向上查找父级的 ViewBase,那如果控件的 Init()ViewBase 先执行了,会发生什么?

答案是——会直接找不到 ViewBase,事件无法转发,控件注册失败

这不是小问题,这是灾难级的初始化顺序隐患。于是本节我们要解决的就是它:

如何确保初始化顺序永远是:ViewBase → PanelBase → EventUIBase?


✅ 为什么 Init() 的顺序如此重要?

我们前面做了这么多“自动化”的工作:让控件自动注册、让 View 自动绑定 Panel、让框架自动反射 Init()……所有这些自动,其实都是建立在“谁先初始化”这个前提上的。

如果初始化顺序不正确,比如控件先于 View 执行了 Init,那就意味着:

  • 无法找到目标父级 ViewBase

  • 无法注册事件转发

  • 无法通过 View 查询控件

  • 所有事件和数据都将中断

所以,我们需要的不只是“自动初始化”,而是**“自动且有序”**的初始化流程。


✅ 解决方案:按类型定义初始化优先级

我们给所有 Base 的子类打上“初始化优先级”,越小越先执行:

类型层级类名优先级
视图层ViewBase0(最早)
逻辑层PanelBase1
控件层EventUIBase2(最晚)

实现方式其实很朴素,就是在 UIManager 中维护一张优先级表:

private static readonly Dictionary<Type, int> InitOrder = new()
{
    { typeof(ViewBase), 0 },
    { typeof(PanelBase), 1 },
    { typeof(EventUIBase), 2 }
};

然后我们在执行反射 Init() 的时候,统一按照这个顺序来排序执行。


✅ 完整升级版 UIManager.Init()

以下是改造之后的 Init() 方法:

private static void Init()
{
    Base[] allBases = Resources.FindObjectsOfTypeAll<Base>();

    var ordered = allBases.OrderBy(b => GetPriority(b.GetType()));

    foreach (var item in ordered)
    {
        if (!IsValid(item)) continue;

        var initMethod = item.GetType().GetMethod("Init", BindingFlags.Instance | BindingFlags.NonPublic);
        initMethod?.Invoke(item, null);
    }
}

private static int GetPriority(Type t)
{
    return InitOrder.TryGetValue(t, out var priority) ? priority : 3;
}

private static bool IsValid(Base b)
{
    return b != null &&
           b.gameObject != null &&
           b.gameObject.scene.IsValid() &&
           !b.hideFlags.HasFlag(HideFlags.NotEditable) &&
           !b.hideFlags.HasFlag(HideFlags.HideAndDontSave);
}

我们做了三件事:

  1. InitOrder 表定义优先级;

  2. OrderBy() 按优先级排序所有 Base 派生类;

  3. 统一执行它们的 Init 方法,确保从 View → Logic → UI 控件。

从此以后,哪怕你在场景中放置顺序错乱、激活状态不同,甚至 prefab 嵌套得再复杂,也不会再出现初始化找不到引用的问题了。


✅ 总结

  • 自动化不是全部,顺序才是稳定性的关键

  • 引入 InitOrder,确保初始化流程可靠、可控;

  • 框架启动再复杂,也能保证每个引用都正确可用。


🔜 下一节预告

现在,我们已经能手动创建 View 和 Panel,并稳定完成注册和调用。但很快你就会觉得——

“每次都复制粘贴模板代码、改类名、查拼写,好烦啊……”

是的,框架再好用,流程再优雅,如果创建脚本还得手写,那开发者的尊严就无处安放。

下一节,我们就来编写一个编辑器工具:一键生成符合框架规范的 View 与 Panel 脚本,让你输入类名和作者名,其他交给代码来完成!

真正做到:

🧠 脑子想好 → ⌨️ 输入类名 → ✅ 自动生成 → 🧱 开始开发

敬请期待——第九节:脚本模板自动生成器

 

阅读指引:

UI框架从0到1第一节:【事件转发起步】让按钮自己“走上来”找逻辑处理-CSDN博客

UI框架从0到1第二节:【全控件适配】把 Toggle、InputField 全都拉进事件系统-CSDN博客

UI框架从0到1第三节:【一键挂载】用编辑器扩展解放双手,告别手动拖脚本-CSDN博客

UI框架从0到1第四节:【页面逻辑拆分】让每一个面板都拥有自己的灵魂-CSDN博客

UI框架从0到1第五节:【事件消息体】不仅知道你点了啥,还知道你改了啥-CSDN博客

UI框架从0到1第六节:【轻量 MVVM】用属性驱动 UI,彻底抛弃命令式调用-CSDN博客

UI框架从0到1第七节:【全局访问 View】UIManager 登场,掌控所有界面入口-CSDN博客

UI框架从0到1第八节:【初始化优先级】三大层级定序,杜绝找不到父级的尴尬-CSDN博客

UI框架从0到1第九节:【脚本模板生成】怎么用代码生成代码?-CSDN博客

UI框架从0到1第十节:【自动化 UI 架构】一键创建结构、强绑定脚本、控件随查随用-CSDN博客

<think>好的,用户让我用C#在Unity中编写一套UI框架。首先,我需要明确用户的需求。可能他们正在开发一个Unity项目,需要一个结构化的UI系统来管理各种界面,比如开始菜单、设置面板、游戏内HUD等。用户可能希望这个框架能方便地处理UI的显示隐藏、层级管理、事件响应,以及可能有动画效果和资源管理。 首先,我应该考虑Unity本身的UGUI系统,因为这是Unity内置的UI解决方案,用户可能已经在使用。接下来,常见的UI框架设计模式,比如MVC、MVP或者MVVM,但考虑到Unity的组件化特性,可能更适合使用基于组件的管理方式。 然后,用户提到的UI框架通常需要几个核心部分:一个UIManager单例来统一管理所有UI面板,每个UI面板作为独立的预制体,可能有基类如BasePanel,包含虚方法如OnEnter、OnExit等,供具体面板继承并实现具体逻辑。 另外,需要考虑UI层级管理,比如不同面板可能处于不同的层级,比如背景层、普通层、弹出层、提示层等,这样在显示的时候可以正确排。每个层级可能有不同的渲染顺和遮挡关系。 事件系统方面,可能需要处理按钮点击、滑动等输入,Unity的EventSystem已经提供了基础,但可能需要封装以便于在框架内统一管理,比如使用委托、事件或者UnityEvent。 资源管理方面,需要考虑动态加载和卸载UI预制体,使用Resources.Load或者Addressables/AssetBundle,但为了简化示例,可能先使用Resources文件夹来加载预制体。 动画部分,可能需要为UI的显示和隐藏添加过渡效果,比如渐隐、缩放等,可以使用Unity的Animation或Dotween插件来实现。 接下来,我应该组织代码结构。UIManager作为核心,负责实例化、缓存、显示和隐藏面板。BasePanel作为所有面板的基类,义生命周期方法。具体的面板如MainMenuPanel、SettingPanel继承BasePanel并实现具体逻辑。 然后,需要处理UI层级,可能义一个枚举UILayer,UIManager在实例化面板时将其放置到对应的Canvas下,并根据层级设置排。 另外,考虑单例模式的安全实现,确保UIManager在场景中唯一存在,并且方便其他脚本访问。 可能还需要考虑UI的输入控制,比如当弹出窗口出现时,禁止底层UI的交互,这可以通过使用CanvasGroup的interactable和blockRaycasts属性来控制。 测试方面,需要确保面板的显示隐藏正确,层级符合预期,动画播放流畅,事件触发无误。 最后,可能需要提供一些扩展点,比如全局事件监听、多语言支持、分辨率自适应等,但根据用户的需求,可能先实现基础功能,再逐步扩展。 现在,我需要将这些思考整理成一个结构清晰的回答,分步骤介绍如何实现,并给出示例代码,同时指出可能的优化方向,比如对象池、异步加载等,供用户参考。</think>在Unity中开发UI框架时,建议采用分层架构设计。以下是一套可扩展的UI框架实现方案,包含核心模块和功能实现: 1. **框架结构设计** ```csharp // UI层级类型(可根据项目扩展) public enum UILayer { Background = 0, // 背景层(如加载界面) Normal = 1, // 常规层(主界面) Popup = 2, // 弹窗层(设置面板) Tips = 3, // 提示层(Toast提示) Loading = 4 // 加载层(转圈动画) } // UI基类(所有面板的类) public abstract class BasePanel : MonoBehaviour { // 面板唯一标识 public string PanelKey { get; protected set; } // 生命周期方法 public virtual void OnCreate() {} // 首次创建时调用 public virtual void OnShow() {} // 显示时调用 public virtual void OnHide() {} // 隐藏时调用 public virtual void OnDestroy() {} // 销毁时调用 } ``` 2. **核心管理器实现** ```csharp public class UIManager : MonoBehaviour { private static UIManager _instance; public static UIManager Instance => _instance; // 面板缓存池 private Dictionary<string, BasePanel> panelPool = new Dictionary<string, BasePanel>(); // 层级容器 private Dictionary<UILayer, Transform> layerContainer = new Dictionary<UILayer, Transform>(); void Awake() { // 单例初始化 if (_instance != null) Destroy(gameObject); _instance = this; // 初始化层级容器 InitializeLayers(); } void InitializeLayers() { // 动态创建各层级Canvas foreach (UILayer layer in Enum.GetValues(typeof(UILayer))) { GameObject go = new GameObject(layer.ToString()); go.transform.SetParent(transform); Canvas canvas = go.AddComponent<Canvas>(); canvas.sortingOrder = (int)layer * 10; // 每层间隔10个排单位 go.AddComponent<GraphicRaycaster>(); layerContainer.Add(layer, go.transform); } } } ``` 3. **面板管理功能** ```csharp // 在UIManager类中继续添加: public T ShowPanel<T>(UILayer layer, string panelPath) where T : BasePanel { string panelKey = typeof(T).Name; // 已存在则直接显示 if (panelPool.TryGetValue(panelKey, out BasePanel panel)) { panel.gameObject.SetActive(true); panel.transform.SetAsLastSibling(); panel.OnShow(); return (T)panel; } // 加载并实例化新面板 GameObject panelPrefab = Resources.Load<GameObject>(panelPath); if (panelPrefab == null) { Debug.LogError($"不到面板预制体:{panelPath}"); return null; } GameObject panelObj = Instantiate(panelPrefab, layerContainer[layer]); T newPanel = panelObj.GetComponent<T>(); newPanel.PanelKey = panelKey; // 初始化组件 newPanel.OnCreate(); newPanel.OnShow(); // 加入缓存池 panelPool.Add(panelKey, newPanel); return newPanel; } public void HidePanel(string panelKey) { if (panelPool.TryGetValue(panelKey, out BasePanel panel)) { panel.OnHide(); panel.gameObject.SetActive(false); } } ``` 4. **使用示例**(主菜单面板实现) ```csharp public class MainMenuPanel : BasePanel { [SerializeField] private Button startButton; [SerializeField] private Button settingButton; public override void OnCreate() { PanelKey = "MainMenu"; startButton.onClick.AddListener(() => { Debug.Log("开始游戏"); SceneManager.LoadScene("GameScene"); }); settingButton.onClick.AddListener(() => { UIManager.Instance.ShowPanel<SettingPanel>( UILayer.Popup, "UI/SettingPanel"); }); } public override void OnShow() { // 显示时播放动画 GetComponent<Animator>().Play("FadeIn"); } } ``` 5. **优化建议** - **资源管理**:使用Addressables实现异步加载 ```csharp IEnumerator LoadPanelAsync<T>(string addressableKey) { var handle = Addressables.LoadAssetAsync<GameObject>(addressableKey); yield return handle; if (handle.Status == AsyncOperationStatus.Succeeded) { Instantiate(handle.Result); } } ``` - **动画系统**:集成DoTween实现平滑过渡 ```csharp public class PanelAnimator : MonoBehaviour { public float showDuration = 0.3f; public void PlayShowAnimation() { transform.localScale = Vector3.zero; transform.DOScale(Vector3.one, showDuration) .SetEase(Ease.OutBack); } } ``` - **事件系统**:创建全局事件中心 ```csharp public class EventCenter : MonoBehaviour { private static EventCenter _instance; public static EventCenter Instance => _instance; public UnityAction<string> OnPanelShow; void Awake() { if (_instance != null) Destroy(gameObject); _instance = this; } } // 面板显示时触发事件 UIManager.Instance.ShowPanel<MainMenuPanel>(...); EventCenter.Instance.OnPanelShow?.Invoke("MainMenu"); ``` 该框架具备以下特性: 1. 层级管理系统:通过动态创建的Canvas实现分层渲染 2. 对象缓存池:避免频繁实例化/销毁造成的性能消耗 3. 生命周期管理:规范化的面板状态控制 4. 扩展性强:支持无缝接入资源管理系统和动画系统 5. 事件驱动架构:通过事件中心实现模块间解耦 实际项目中可根据需求扩展以下功能: - 多语言支持:创建LocalizationManager - UI适配系统:通过CanvasScaler实现多分辨率适配 - 输入控制:使用InputSystem管理UI交互优先级 - 数据绑:实现MVVM模式的数据驱动更新
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值