当前位置: 首页 > news >正文

基于MVVM框架的XUUI HelloWorld 新手教程

概述

本教程将带你从零搭建 XUUI 框架的第一个示例 —— HelloWorld。你将学会:

  • 在 Unity 场景中创建 UI 并挂载适配器组件
  • 编写 C# 入口脚本创建 XUUI Context
  • 理解 Lua ViewModel 的 data / computed / commands 结构
  • 理解整个 MVVM 数据绑定流程

1. XUUI 是什么

XUUI 是一个轻量级的 Unity MVVM 框架,使用XLua作为 ViewModel 层,Unity uGUI 作为 View 层。

核心思想:

View (uGUI) ←→ Adapter (C#) ←→ Binding Engine (Lua) ←→ ViewModel (Lua Table)
  • View:Unity 场景中的 Text、Button 等 uGUI 组件
  • Adapter:挂在 uGUI 组件上的 C# 脚本,提供Value/OnAction等统一接口
  • Binding Engine:Lua 侧的绑定引擎,负责将 Lua ViewModel 数据推送到 Adapter,或将 UI 事件传递回 ViewModel
  • ViewModel:纯 Lua table,包含data(数据)、computed(计算属性)、commands(命令)

2. 场景搭建

2.1 创建 GameObject 层级

在 Hierarchy 窗口中创建如下结构:

HelloWorld (GameObject) ├── Canvas │ ├── MessageText (Text) │ └── ClickButton (Button)

具体操作:

  1. 创建空 GameObject,命名为HelloWorld——这就是入口 GameObject
  2. HelloWorld下创建 Canvas
  3. 在 Canvas 下创建 Text,命名为MessageText,调整好位置和字体大小
  4. 在 Canvas 下创建 Button,命名为ClickButton,修改按钮上的 Text 为 “Click Me”

2.2 挂载适配器组件

XUUI 有两种绑定方式。本教程使用AdapterBase 方式(MonoBehaviour 组件方式),更直观易懂。

给 MessageText 挂载 TextAdapter
  1. 选中MessageTextGameObject
  2. 在 Inspector 中点击Add Component
  3. 搜索并添加XUUI > Text组件(或直接搜索TextAdapter
  4. 在 TextAdapter 的BindTo字段中填入:message
  5. 确认 TextAdapter 的Target已自动指向自身身上的Text (UnityEngine.UI.Text)组件
给 ClickButton 挂载 ButtonAdapter
  1. 选中ClickButtonGameObject
  2. 点击Add ComponentXUUI > Button(或搜索ButtonAdapter
  3. 在 ButtonAdapter 的BindTo字段中填入:click

2.3 挂载 Helloworld 脚本

  1. 选中HelloWorld(根 GameObject)
  2. 点击Add Component→ 搜索并添加Helloworld脚本

2.4 场景结构总览

最终 Inspector 结构如下:

HelloWorld (GameObject) └─ Helloworld (Script) Canvas ├── MessageText (GameObject) │ ├── Text (UI.Text) │ └── TextAdapter (Script) │ ├── BindTo: "message" │ └── Target: Text (auto-assigned) └── ClickButton (GameObject) ├── Button (UI.Button) ├── Text (UI.Text) — 按钮文字 └── ButtonAdapter (Script) ├── BindTo: "click" └── Target: Button (auto-assigned)

3. 代码解析

3.1 Helloworld.cs

usingUnityEngine;usingXUUI;publicclassHelloworld:MonoBehaviour{Contextcontext=null;voidStart(){context=newContext(@" return { data = { info = { name = 'John', }, }, computed = { message = function(data) return 'Hello ' .. data.info.name .. '!' end }, commands = { click = function(data) print(data.info.name) end, }, } ");context.Attach(gameObject);}voidOnDestroy(){context.Dispose();}}
逐行解释
作用
Context context = null;声明一个 Context 引用,它是 XUUI 的核心管理器
context = new Context(...)传入一段 Lua 脚本字符串,Context 的构造函数会:1. 初始化 XLua 环境 2. 编译执行脚本返回 Lua table 3. 调用xuui.new(options)创建 ViewModel
context.Attach(gameObject)将当前 GameObject(及其子物体)绑定到 ViewModel。这一步触发 Binding Engine 扫描适配器、注册数据监听和命令绑定
context.Dispose()在对象销毁时清理所有 Lua 引用和事件监听

3.2 Lua ViewModel 结构

Lua 脚本返回的 table 是 ViewModel 的定义,包含三个核心部分:

data —— 数据模型(Model)
data={info={name='John',},}
  • 这是 MVVM 中的Model
  • 数据被observeable.lua用 metatable 包装成响应式数据
  • data.info.name的值改变时,所有依赖它的 UI 会自动更新
computed —— 计算属性
computed={message=function(data)return'Hello '..data.info.name..'!'end,}
  • 这是一个纯函数,接收data作为参数,返回计算后的值
  • TextAdapter.BindTo = "message"告诉 Binding Engine:这个 Text 显示computed.message的结果
  • 自动依赖追踪:引擎在执行message(data)时,会记录它访问了data.info.name,并自动注册监听。当name变化时,message重新计算,UI 自动刷新
commands —— 命令
commands={click=function(data)print(data.info.name)end,}
  • ButtonAdapter.BindTo = "click"告诉引擎:这个按钮触发commands.click
  • 点击按钮时,Binding Engine 调用click(data),打印 “John”

4. 运行流程

程序启动时的完整调用链:

Helloworld.Start() └─ new Context(luaScript) ├─ LuaEnv.LoadString(script) → 编译 Lua 返回 table └─ xuui.new(options) → 包装响应式数据,返回 attach 函数 └─ context.Attach(gameObject) └─ binding.bind(data, observe, computed, commands, root) ├─ Collector.Collect(root) → 扫描子物体,收集所有 Adapter ├─ listen_to() → 为每个 DataConsumer(TextAdapter)注册数据监听 │ ├─ 发现 BindTo="message" 在 computed 中 │ ├─ 执行 message(data) → 依赖追踪 → 注册 watcher │ └─ 设置 TextAdapter.Value = "Hello John!" ├─ watch_to() → 为每个 DataProducer 绑定"UI → data"监听 └─ bind_action() → 为每个 EventEmitter(ButtonAdapter)绑定命令 └─ button.OnAction = function() commands.click(data) end

运行后效果:

  • 屏幕上显示“Hello John!”
  • 点击按钮,Console 打印“John”

上面的流程图就很好的解释了,我们Content参数里的computed 和 Commands是如何被框架执行的,主要就是这个Attach的方法,跟对应的节点绑定,在Attach的时候做了Lua初始化的工作,而在初始化的时候加载了xuui.lua.txt

local function new(options) options = options or {} options.data = options.data or {} options.computed = options.computed or {} options.computed[0] = {} options.commands = options.commands or {} options.exports = {} if options.modules then app_init(options) end local observe = observeable.new(options.data) local function attach(el) return binding.bind(options.data, observe, options.computed, options.commands, el) end local function reload(module_name, reload_data) loadmodule(options, options.exports, options.name, module_name, not reload_data) end return attach, reload end

里面调用了binding.bind的方法,而_M.bind方法扫描所有的子物体,搜集适配器,其中包含EventEmitters对象然后处理了ButtonAdapter,collector.collect 在 C# 侧 (Collector.cs:12) 通过 GetComponentsInChildren 找到所有 ButtonAdapter,按接口类型分到三个数组里。bindings[3] 是所有实现 EventEmitter 接口的适配器(即 ButtonAdapter)。

5. 绑定关系总结

GameObject适配器BindToViewModel 映射方向
MessageTextTextAdapter"message"computed.message(data)VM → View
ClickButtonButtonAdapter"click"commands.click(data)View → VM

6. 动手实验

理解了基本流程后,可以尝试以下修改来加深理解:

6.1 修改显示的数据

name = 'John'改为name = 'World',运行后 Text 变为“Hello World!”

6.2 新增一个显示字段

在 data 中添加age = 25

data={info={name='John',age=25,-- 新增},},

添加一个 computed:

ageInfo=function(data)returndata.info.name..' is '..data.info.age..' years old'end,

在场景中新建一个 Text,挂载 TextAdapter,BindTo 设为"ageInfo"

6.3 修改命令行为

把 click 命令改为修改数据:

click=function(data)data.info.name='World'-- 改数据end,

点击按钮后,Text 会自动更新为“Hello World!”—— 这就是 MVVM 的威力,修改 Model 自动驱动 View 更新。

6.4 使用 InputField 实现双向绑定

在 Canvas 下创建一个 InputField,挂载InputFieldAdapter,BindTo 设为"info.name"。运行时在输入框中打字,Text 的文字会随之实时变化。因为:

  1. InputFieldAdapter 是DataProducer<string>— 用户输入触发OnValueChange
  2. 引擎将输入值写回data.info.name
  3. 响应式系统检测到name变化,触发computed.message重新计算
  4. TextAdapter 的Value被更新,UI 刷新

7. 常见问题

Q: BindTo 的值如何对应到 Lua data?

BindTo 使用点号分隔路径。例如:

BindToLua 路径
"message"computed.message(先从 computed 查找)
"click"commands.click
"info.name"data.info.name(直接数据路径)
"module1.click"commands["module1.click"](模块化命令)

Q: 为什么 TextAdapter 的 BindTo 是 “message” 却显示 computed 的结果?

Binding Engine 的listen_to函数会先检查BindTo是否在computed表中。如果在,就执行计算属性函数而不是直接读取 data。如果在 computed 和 commands 中都找不到,则直接从 data 中按路径读取。

Q: 适配器只有 Text 和 Button 吗?

XUUI 还内置了 InputFieldAdapter(双向绑定)、DropdownAdapter 等。你也可以自己继承AdapterBase<T>创建自定义适配器。


8. 完整文件清单

Assets/ ├── Scenes/ │ └── Helloworld.unity ← 本示例的场景 ├── Scripts/ │ └── Helloworld.cs ← 入口 C# 脚本 ├── XUUI/ │ ├── Scripts/ │ │ ├── ViewModel.cs ← Context 类 │ │ ├── AdapterBase.cs ← 适配器基类 │ │ ├── RawAdapterBase.cs ← 非 MonoBehaviour 适配器基类 │ │ ├── DataConsumer.cs ← VM→View 接口 │ │ ├── DataProducer.cs ← View→VM 接口 │ │ ├── EventEmitter.cs ← 事件接口 │ │ └── UGUIAdapter/ │ │ ├── TextAdapter.cs ← Text 适配器 │ │ ├── ButtonAdapter.cs ← Button 适配器 │ │ ├── InputFieldAdapter.cs ← InputField 适配器 │ │ ├── DropdownAdapter.cs ← Dropdown 适配器 │ │ ├── ViewBinding.cs ← 序列化绑定组件(另一种方式) │ │ └── Collector.cs ← 扫描收集所有适配器 │ └── Resources/ │ ├── xuui.lua.txt ← XUUI 主模块,new() 工厂 │ ├── binding.lua.txt ← 绑定引擎 │ ├── observeable.lua.txt ← 响应式数据系统 │ ├── collector.lua.txt ← Lua 端适配器收集器 │ └── xuui_utils.lua.txt ← 工具函数

9.补充

DataProvider接口,生产者

DataProducer —— 生产者,View → VM
数据从 UI 流回 ViewModel。定义在 DataProducer.cs:

public interface DataProducer<T> : DataProducer { Action<T> OnValueChange { get; set; } // UI 变化时通知谁 } 只有 OnValueChange,UI 变化时触发它,把值传出去。 使用例子 — InputFieldAdapter (InputFieldAdapter.cs): public class InputFieldAdapter : AdapterBase<InputField>, DataConsumer<string>, DataProducer<string> { // 作为 DataConsumer:VM → View public string Value { set { Target.text = value; } } // 作为 DataProducer:View → VM public Action<string> OnValueChange { get; set; } void Start() { Target.onValueChanged.AddListener((text) => { if (OnValueChange != null) OnValueChange(text); // 用户输入 → Lua }); } }

InputFieldAdapter 同时实现了两个接口,所以支持双向绑定。

DataConsumer接口,消费者

DataConsumer —— 消费者,VM → View
数据从 ViewModel 流向 UI。定义在 DataConsumer.cs:

public interface DataConsumer<T> : DataConsumer { T Value { set; } // 只能设置值推到 UI,没有 get } 只有 set 没有 get,意味着这是个单向写入通道。 使用例子 — TextAdapter (TextAdapter.cs): public class TextAdapter : AdapterBase<Text>, DataConsumer<string> { public string Value { set { Target.text = value; // Lua 算好的值 → 写到 UI } } }

Binding 引擎调用 el.Value = “Hello John!” → Text 的文字就变了。

http://www.jsqmd.com/news/1159089/

相关文章:

  • 跟大模型对话,如何让提示词控制力更强?从随缘输出到精准可控完整指南
  • 无需安装WPS 和 Office!开源 OfficeCLI,Claude/Copilot 可直接调用,自动生成编辑Word/PPT/Excel
  • 74LS151 vs 74LS138 数据手册精读:5 个关键参数对比与 3 种典型应用电路
  • 高校后勤精细化革新:智能锁赋能校园安防管控与节能运维升级
  • Calibre繁简中文转换插件:让全球中文电子书无障碍阅读
  • 九号控制器二次开发全解析:从协议逆向到功能实现
  • 信创框架 IIAF-ADM 架构开发方法:3个核心步骤与2大策略实践解析
  • OpenCode嵌入式AI开发环境:离线本地化智能编码底座
  • UC3842 反激电源设计实战:3步完成RT/CT振荡器与反馈环路参数计算
  • 海外留学安全必备:欧美/日本/东南亚留学防身装备合法携带指南
  • AI构建AI:从自动化工作流到Agent自主开发的工程实践
  • 轴向磁通电机技术解析:结构突破、功率密度优势与电动车应用前景
  • S7-200 SMART V2.6 Web服务器HTTPS配置:3步完成内部证书生成与PC端安装
  • 3100份各大庆典的司仪主持人台词大全,2026司仪主持人台词大全——司仪主持人台词大全下载
  • 工程师转型技术合规:AI赋能开发全流程的实战指南
  • Day 006:别被概念忽悠了,Agent 的核心就是个 While 循环
  • AI真正进入企业,是从开始承担责任那一天
  • OpenAI API调用实战:构建跨境支付AI智能验证的技术方案
  • Cursor 2.6.22 Agents 不是开关,而是运行时契约
  • Unity UGUI软遮罩实现原理与性能优化指南
  • 51单片机定时器T0 12MHz晶振:8音阶频率表生成与C51代码实现
  • 基于TS2007FC与STM32F745VG的高保真音频系统设计
  • 火爆了!Nginx常用命令大全
  • Fake UserAgent 2.2.0 高级用法:3种过滤策略精准生成指定浏览器UA
  • HyperFrames关键帧开源:AI视频生成从盲盒到可控编辑的突破
  • 河北xx集团考核系统操作手册
  • JavaScript ES6 模块导入 5 大常见 SyntaxError 排查:从 Unexpected identifier 到 MIME 类型
  • 计算机毕业设计选题怎么选?完整挑选指南,轻松搞定毕业设计
  • 独立开发30天,我用AI做了个发型推荐小程序(含全部踩坑记录)
  • IIC通信协议