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

到目前为止,我们已经搭建起了一套相当完整的UI 框架体系:

事件驱动、数据派发、静态管理器、自动生成模板脚本......这些功能固然强大,但当你真正开始写实际 UI 页面的那一刻,很快就会遇到两个非常"令人崩溃"的问题:

  1. 每次都要手动在Hierarchy中搭出 Canvas + Panel 的结构,实在麻烦;

  2. 想获取某个 ToggleInputField 控件的引用,要么拖引用、要么反查,体验太差。

说白了,我们还缺两个最基础也最重要的"开发效率器":

  • 一键创建标准页面结构(Canvas + Panel)

  • 自动缓存控件引用并提供类型安全的查询接口

别急,这一节我们就来实现它们。


一、一键创建标准 Canvas + Panel 页面结构

我们希望在 Hierarchy 中右键一键生成如下结构:

css 复制代码
XXXPanel (Canvas)
└── XXX (Image,全屏背景)
    ├── Button
    ├── Toggle
    └── ...

Canvas 是页面的根节点,XXX 是一个全屏的透明 Image,用于承载实际的 UI 控件。

我们来写一个右键菜单,一键生成这个结构。

脚本路径建议放在:Assets/UIFrameWork/Editor/KiraCanvasCreator.cs

ini 复制代码
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

public static class KiraCanvasCreator
{
    [MenuItem("GameObject/Kira UI/创建标准页面", false, 10)]
    public static void CreateStandardCanvas()
    {
        GameObject canvasGO = new GameObject("XXXPanel", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
        Canvas canvas = canvasGO.GetComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;

        CanvasScaler scaler = canvasGO.GetComponent<CanvasScaler>();
        scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        scaler.referenceResolution = new Vector2(1920, 1080);

        GameObject panelGO = new GameObject("XXX", typeof(Image));
        panelGO.transform.SetParent(canvasGO.transform, false);

        RectTransform rt = panelGO.GetComponent<RectTransform>();
        rt.anchorMin = Vector2.zero;
        rt.anchorMax = Vector2.one;
        rt.offsetMin = Vector2.zero;
        rt.offsetMax = Vector2.zero;

        Image img = panelGO.GetComponent<Image>();
        img.color = new Color(1, 1, 1, 0); // 全透明背景

        Selection.activeGameObject = canvasGO;
        Debug.Log("✅ 已创建标准Canvas结构,请添加脚本并改名");
    }
}

你可以在 Unity 的 Hierarchy 视图中右键空白处,点击:

GameObject → Kira UI → 创建标准页面

然后快速得到一个可用的标准 UI 页面结构,省去无聊重复劳动。


二、脚本强绑定(View 与 Panel 的互相依赖)

很多时候我们会忘记在面板对象上挂上对应的 ViewPanel 脚本,结果控件事件发出去没人接,调试一晚上。

为了解决这个问题,我们在脚本模板中使用了 [RequireComponent]

kotlin 复制代码
[RequireComponent(typeof(TemplatePanelView))]
public class TemplatePanelLogic : PanelBase { ... }

[RequireComponent(typeof(TemplatePanelLogic))]
public class TemplatePanelView : ViewBase { ... }

这样只要挂了其中一个脚本,另一个会被 Unity 自动补全,永远不再担心忘挂组件的问题,开发体验直接飞升。


三、控件自动注册到 ViewBase,并支持强类型查询

终于到了最核心的改动:

我们希望每个 UI 控件在初始化时能够自动注册到 ViewBase 中,然后我们就可以随时通过 GetEventUI<T>(name) 方法来取出任意控件引用。

Step 1:修改 ViewBase,添加控件注册与查询方法

csharp 复制代码
protected Dictionary<string, EventUIBase> eventUIs = new();

public void AddEventUI(EventUIBase ui)
{
    if (!eventUIs.ContainsKey(ui.name))
        eventUIs.Add(ui.name, ui);
    else
        Debug.LogWarning($"⚠️ 控件名称重复注册:{ui.name}");
}

public T GetEventUI<T>(string name) where T : EventUIBase
{
    if (eventUIs.TryGetValue(name, out var ui))
        return ui as T;

    Debug.LogWarning($"❌ 未找到控件:{name}");
    return null;
}

Step 2:修改 EventUIBase,在 Init 中自动注册自身到 View

csharp 复制代码
protected override void Init()
{
    base.Init();

    Transform current = transform;
    while (current != null)
    {
        var view = current.GetComponent<ViewBase>();
        if (view != null)
        {
            view.AddEventUI(this);
            break;
        }
        current = current.parent;
    }
}

效果演示:

你只需要在事件处理函数中写:

swift 复制代码
var toggle = UIManager.GetPanelView<XXXPanelView>().GetEventUI<ToggleCustom>("切换开关");

即可拿到 toggle 控件,无需拖引用、无需 GetComponent,永远类型安全且可追踪。


四、在模板脚本中添加"控件获取"示例

为了帮助开发者即使不看文档也能上手,我们在生成的模板脚本中加上一段示范代码:

csharp 复制代码
public override void ProcessEvent(string eventName, Base sender, IEventContext context = null)
{
    base.ProcessEvent(eventName, sender, context);

    Dispatch(eventName, "OnClick", () =>
    {
        if (sender.name == "按钮1")
        {
            Debug.Log("点击了按钮1");
            var view = UIManager.GetPanelView<TemplatePanelView>();
            var toggle = view.GetEventUI<ToggleCustom>("切换开关");
            if (toggle != null)
                Debug.Log($"当前 Toggle 状态:{toggle.GetComponent<Toggle>().isOn}");
        }
    });
}

这段代码会告诉你:只要在事件回调中获取 ViewBase,然后再用控件名查字典,就可以随时访问任意控件。


五、为什么我们不用 GetComponent 搜索控件?

你可能会问,直接用 GetComponentInChildren<Button>() 不就完了吗?

是的,能用。但存在几个问题:

  • 性能差:遍历整个子树,做一次查找;

  • 耦合高:写死了类型查找逻辑;

  • 不可控:多个同类型控件容易匹配错;

  • 不可追踪:事件派发逻辑不透明,不符合我们"引用可溯源"的理念。

而我们这套注册机制具备以下优势:

  • 所有控件初始化时主动注册,非反射遍历;

  • 查询方式为类型 + 名字,具备强可读性;

  • 控件查找逻辑始终统一、集中、可追踪;

  • 每次控件引用都来自统一的 UIManager.GetPanelView() 路径,逻辑来源清晰。


总结:完成框架基础闭环

到这里为止,我们终于补齐了 UI 框架中最易被忽略但最关键的一环:

  • ✅ 页面结构一键创建

  • ✅ 脚本互相自动绑定

  • ✅ UI 控件自动注册并可查询

  • ✅ 脚本模板自动生成并示例使用

你已经拥有了一整套"上手即用、结构清晰、逻辑解耦、扩展自由"的 UI 框架基础架构。

下一步就是:以它为基石,自由扩展,构建属于你自己的 UI 工具链生态。


如需继续扩展,也可以思考:

  • 是否要加入 Canvas 分层控制、动画管理?

  • 是否要支持自动隐藏/销毁未激活 UI?

  • 是否加入 UI 弹窗栈与异步加载支持?

但这些,可能就超出了"基础篇"的范畴了。

到这里,第十节结束,基础 UI 框架教学正式收官。

相关推荐
这是我586 小时前
unity实现梦日记式效果
unity·c#·游戏引擎·unity3d·游戏开发·csharp·c#11.0
kirayoshikake2 天前
UGUI 性能优化系列:第二篇——Canvas 与 UI 元素管理
unity3d
kirayoshikake2 天前
UI框架从0到1第九节:【脚本模板生成】怎么用代码生成代码?
unity3d
kirayoshikake2 天前
Unity入门教程之异步篇第五节:对UniTask的高级封装
unity3d
kirayoshikake2 天前
UI框架从0到1第二节:【全控件适配】把 Toggle、InputField 全都拉进事件系统
unity3d
kirayoshikake2 天前
UI框架从0到1第六节:【轻量 MVVM】用属性驱动 UI,彻底抛弃命令式调用
unity3d
kirayoshikake2 天前
Unity入门教程之异步篇第一节:协程基础扫盲--非 Mono 类如何也能启动协程?
unity3d
kirayoshikake2 天前
响应式编程入门教程第一节:揭秘 UniRx 核心 - ReactiveProperty - 让你的数据动起来!
unity3d
kirayoshikake2 天前
UI框架从0到1第五节:【事件消息体】不仅知道你点了啥,还知道你改了啥
unity3d
kirayoshikake2 天前
响应式编程入门教程第六节:进阶?Combine、Merge、SelectMany 与错误处理
unity3d