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

在上一节中,我们设计了 ViewBase 脚本,并初步实现了类 MVVM 风格的数据派发机制,真正做到了"UI 控件通过设置状态值,就能驱动逻辑执行"。乍一看框架已经初具雏形,但很快就有细心的小伙伴意识到了一个问题:

我们的 HelloWorldViewHelloWorldPanel 对应的 ViewBase 派生类,那我们该怎么获取它的引用呢?

一个最直接的思路是------用 GetComponent<HelloWorldView>(),但这正是我们要避免的老路。前面我们反复强调"所有引用必须可溯源 ",而 GetComponent 显然不符合这个原则,它带来的是动态查找、隐式依赖、调试困难。那我们该怎么做?

答案很简单:

我们要做的是,引入一个全局静态类 ------ UIManager ,负责集中注册和管理所有的 ViewBase 派生类对象。通过这个管理器,我们可以:

ini 复制代码
UIManager.GetPanelView<HelloWorldView>().IsShow = true;

一行代码,类型安全,全局唯一,不用手动拖引用,也不用动态查找。


✅ 第一步:ViewBase 自动注册到 UIManager

我们在 ViewBaseInit() 方法中加入这一句:

kotlin 复制代码
UIManager.AddPanelView(this.GetType(), this);

每一个挂载了 ViewBase 派生类的对象都会在初始化时自动将自己注册进 UIManager 的字典中,类型就是 key,实例就是 value。


✅ 第二步:UIManager 实现注册与获取逻辑

我们来实现 UIManager 脚本的基本功能。核心职责只有两点:

  • 注册 :某个 ViewBase 派生类在初始化时注册进来

  • 获取:通过泛型方法获取已注册的实例

csharp 复制代码
public static class UIManager
{
    private static readonly Dictionary<Type, ViewBase> panelViews = new();

    public static void AddPanelView(Type viewType, ViewBase view)
    {
        if (panelViews.ContainsKey(viewType))
            panelViews[viewType] = view;
        else
            panelViews.Add(viewType, view);
    }

    public static T GetPanelView<T>() where T : ViewBase
    {
        Type key = typeof(T);
        if (panelViews.TryGetValue(key, out var view))
            return view as T;

        Debug.LogError($"未找到面板类型:{key}");
        return null;
    }
}

✅ 第三步:自动执行所有 Init()

既然我们在 ViewBaseInit() 方法里注册,那问题就来了:

Unity 默认不会调用我们写的 Init() 方法,我们得自己手动执行,怎么办?

这时候,就轮到 UIManager 出场了。我们可以在 UIManager 中统一调用所有 Base 派生类的 Init() 方法,实现一次性、自动化、稳定的初始化流程。

csharp 复制代码
static UIManager()
{
    Init();
}

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

    foreach (var item in allBases)
    {
        if (item == null || item.gameObject == null) continue;
        if (!item.gameObject.scene.IsValid()) continue;
        if (item.hideFlags.HasFlag(HideFlags.NotEditable) || item.hideFlags.HasFlag(HideFlags.HideAndDontSave)) continue;

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

这个 Init() 做了几件事:

  • 找到所有继承自 Base 的组件

  • 过滤掉无效、隐藏、资源文件中的对象

  • 通过反射执行它们的 Init() 方法(哪怕是 private

最妙的是:我们把它放在了 UIManager静态构造函数 中。这意味着,只要你在任何地方首次调用 UIManager.GetPanelView<T>(),框架就会立刻自动初始化,再也不需要手动管理。


✅ 总结

  • 我们引入了 UIManager ,全局统一管理所有 ViewBase 实例。

  • ViewBase 自动注册UIManager 提供泛型获取

  • 框架初始化通过静态构造 + 反射 Init,全自动无感知。


🔜 下一节预告

虽然我们已经实现了自动注册和泛型获取,但还是埋下了一个隐患:如果控件(EventUIBase)在 ViewBase 尚未初始化时就执行了自己的 Init,那就会导致找不到父级 View,注册失败。

这就意味着:Init 的执行顺序 必须严格控制!我们需要构建一套可控的"初始化优先级系统",让框架自动以 View > Panel > 控件 的顺序初始化,确保所有引用都能被正确建立。

下一节,我们就来解决这个至关重要的问题:UI 初始化优先级机制

相关推荐
泉城老铁4 天前
VUE2实现加载Unity3d
前端·vue.js·unity3d
evamango6 天前
《Unity Shader入门精要》十一、让画面动起来
unity3d
Thomas游戏开发6 天前
Unity Android性能优化设置指南
前端框架·unity3d·游戏开发
Thomas游戏开发9 天前
Unity3D C#监听Button点击事件
前端·unity3d·游戏开发
谷宇.12 天前
【Unity3D实例-功能-拔枪】角色拔枪(三)IK的使用-紧握武器
游戏·unity·c#·unity3d·游戏开发·游戏编程·steam
evamango12 天前
《Unity Shader入门精要》十、高级纹理
unity3d
白色牙膏12 天前
从零到搞定:URP Lit彩色箱子材质的开发碎碎念
unity3d
半夜微笑狗14 天前
数据持久化-PlayerPrefs
unity3d
Thomas游戏开发15 天前
博毅创为 Unity_0基础就业班
前端框架·unity3d·游戏开发
谷宇.16 天前
【Unity3D实例-功能-拔枪】角色拔枪(二)分割上身和下身
游戏·unity·c#·游戏程序·unity3d·游戏开发·游戏编程