到目前为止,我们已经搭建起了一套相当完整的UI 框架体系:
事件驱动、数据派发、静态管理器、自动生成模板脚本......这些功能固然强大,但当你真正开始写实际 UI 页面的那一刻,很快就会遇到两个非常"令人崩溃"的问题:
-
每次都要手动在Hierarchy中搭出 Canvas + Panel 的结构,实在麻烦;
-
想获取某个
Toggle
或InputField
控件的引用,要么拖引用、要么反查,体验太差。
说白了,我们还缺两个最基础也最重要的"开发效率器":
-
一键创建标准页面结构(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 的互相依赖)
很多时候我们会忘记在面板对象上挂上对应的 View
或 Panel
脚本,结果控件事件发出去没人接,调试一晚上。
为了解决这个问题,我们在脚本模板中使用了 [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 框架教学正式收官。