基于MVVM框架的XUUI Tutorial_App — 多模块应用框架教程

发布时间:2026/9/23 18:19:36

基于MVVM框架的XUUI Tutorial_App — 多模块应用框架教程
Tutorial_App — 多模块应用框架与之前教程最大的不同模块化架构Helloworld 和 MoreComplex 都是单个 ViewModel 的简单示例。App 模式引入了真正的模块化架构将 UI 逻辑拆分为多个模块module各自独立每个模块有自己的data/computed/commands互不干扰模块之间通过exports可控交互支持模块热刷新reload不影响其他模块1. 场景搭建1.1 创建 GameObject 层级App (GameObject) ├── Canvas │ ├── Module1InfoText (Text) ← 显示 module1.info │ ├── Module1ClickBtn (Button) ← 触发 module1.click │ ├── Module2InfoText (Text) ← 显示 module2.info │ ├── Module2ClickBtn (Button) ← 触发 module2.click │ ├── SayHelloBtn (Button) ← 触发 module2.say_hello_to_csharp │ ├── ReloadModule1Btn (Button) ← 重载 module1 │ └── ReloadModule2Btn (Button) ← 重载 module21.2 挂载适配器GameObject适配器BindTo说明Module1InfoTextTextAdaptermodule1.info显示 module1 的 computed.infoModule1ClickBtnButtonAdaptermodule1.click触发 module1 的 click 命令Module2InfoTextTextAdaptermodule2.info显示 module2 的 computed.infoModule2ClickBtnButtonAdaptermodule2.click触发 module2 的 click 命令SayHelloBtnButtonAdaptermodule2.say_hello_to_csharp调用 C# 的 HelloCSharpReloadModule1BtnButtonAdapterReloadModule1App.cs 的 Command重载 module1ReloadModule2BtnButtonAdapterReloadModule2App.cs 的 Command重载 module2关键区别BindTo 的格式变为模块名.路径因为 App 模式下有多个模块UI 需要显式指定数据来自哪个模块。2. 代码解析2.1 App.cs —— C# 入口usingUnityEngine;usingXUUI;publicclassApp:MonoBehaviour{Contextcontextnull;voidStart(){contextnewContext( return { name myapp, modules {module1, module2}, } );context.AddCSharpModule(ModuleManager,this);context.Attach(gameObject);}voidOnDestroy(){context.Dispose();}[Export]publicvoidHelloCSharp(Interface2data,intp){Debug.Log(data.selectdata.select, pp);}[Command]publicvoidReloadModule1(){context.ReloadModule(module1);}[Command]publicvoidReloadModule2(){context.ReloadModule(module2);}}逐行解释代码作用name myapp定义应用名称模块加载时会按myapp.module1、myapp.module2的路径 requiremodules {module1, module2}声明两个模块框架自动加载并为每个创建独立沙盒context.AddCSharpModule(ModuleManager, this)将当前 C# 对象注册为名为 “ModuleManager” 的模块其[Export]方法可在 Lua 中调用context.Attach(gameObject)绑定 UI开始数据监听[Export] public void HelloCSharp(...)标记为导出函数Lua 代码可以调用ModuleManager.HelloCSharp(data, 1024)[Command] public void ReloadModule1()标记为命令UI 可通过 BindTo“ReloadModule1” 绑定按钮2.2 module1.lua.txtreturn{data{namehaha,select0,},commands{clickfunction(data)module2.set_select(data.select)-- 跨模块调用调用 module2 的 exportsdata.selectdata.select0and1or0end,},computed{infofunction(data)returnstring.format(i am %s, my select is %d,data.name,data.select)end,},exports{hellofunction(p)print(hello, p ..p)end,},}2.3 module2.lua.txtlocaldata{messagehehe,select1,}return{datadata,commands{clickfunction(data)module1.hello(1)-- 跨模块调用调用 module1 的 exportsdata.selectdata.select0and1or0end,say_hello_to_csharpfunction(data)ModuleManager.HelloCSharp(data,1024)-- 调用 C# 的 Export 方法end,},computed{infofunction(data)returnstring.format(message is %s, select is %d,data.message,data.select)end,},exports{set_selectfunction(p)data.selectpend,},}3. 核心概念详解3.1 模块沙盒与数据隔离每个模块运行在独立的沙盒中沙盒 module1: data { name haha, select 0 } commands.click 只能读写 module1 的 data computed.info 只能读取 module1 的 data 沙盒 module2: data { message hehe, select 1 } commands.click 只能读写 module2 的 data computed.info 只能读取 module2 的 data这意味着即使两个模块定义了同名变量如select也不会冲突command 函数收到的是自己模块的 data不能直接修改其他模块的数据不同开发团队可以独立开发各自模块不用担心全局命名冲突3.2 模块间通信exports模块通过exports暴露接口供其他模块调用module2.commands.click 调用 module1.hello(1) ↓ 调用 module1.exports.hello(p) ↓ 打印 hello, p 1 module1.commands.click 调用 module2.set_select(data.select) ↓ 调用 module2.exports.set_select(p) ↓ 设置 module2 的 data.select p注意module2.exports.set_select使用了闭包引用外部的local datalocaldata{...}-- 闭包变量return{datadata,exports{set_selectfunction(p)data.selectp-- 直接操作闭包引用的 dataend,},}这种方式绕过了 command 的只能看到自己 data的限制——因为set_select不是 command不接收data参数而是通过 Lua 闭包直接访问原始data表。这在需要允许其他模块修改本模块数据时非常有用。3.3 C# 桥接Export 与 AddCSharpModuleApp.cs 中AddCSharpModule(ModuleManager, this)注册后Lua 侧调用: ModuleManager.HelloCSharp(data, 1024) ↓ XLua 桥接: data → XLua 自动包装为 Interface2 代理 ↓ C# 侧: HelloCSharp(Interface2 data, int p) → Debug.Log(data.select data.select , p 1024)这里的Interface2是一个 C# 接口XLua 自动将 Lua table 代理为实现了该接口的对象。所以data.select读取的就是 Lua 模块数据表中的select字段。3.4 模块热刷新Reload[Command]publicvoidReloadModule1(){context.ReloadModule(module1);}调用context.ReloadModule(module1)时重新 requiremyapp.module1加载最新的 Lua 脚本保留旧的data状态数据不丢失用新的commands替换旧的——UI 上已绑定的按钮自动关联新命令重新计算所有computed——UI 自动更新module2完全不受影响这非常适合需要热更新的场景修改 Lua 脚本后点击 Reload 按钮立即生效无需重新运行 Unity。4. 完整数据流初始化流程App.Start() └─ new Context(luaScript) ├─ 解析 namemyapp, modules{module1,module2} ├─ require myapp.module1 → 创建沙盒1包装响应式数据 ├─ require myapp.module2 → 创建沙盒2包装响应式数据 └─ 注册模块间引用module1 中可访问 module2反之亦然 └─ context.AddCSharpModule(ModuleManager, this) └─ 注册 [Export] 方法到 Lua 环境 └─ context.Attach(gameObject) └─ 扫描适配器按 模块名.路径 绑定点击 module1.click 时用户点击 Module1ClickBtn → ButtonAdapter.OnAction() → commands[module1.click](module1.data) → module2.set_select(module1.data.select) -- 跨模块调用 → module1.data.select 0 → module2 的 data 变为 0 → module2 的 UI 自动更新 → module1.data.select 1 -- 修改自己的数据 → module1 的 UI 自动更新点击 SayHelloBtn 时用户点击 SayHelloBtn → ButtonAdapter.OnAction() → commands[module2.say_hello_to_csharp](module2.data) → ModuleManager.HelloCSharp(data, 1024) → XLua 接口代理 → C# HelloCSharp(Interface2 data, int p) → Debug.Log(data.select0, p1024)5. 绑定关系总结GameObject适配器BindToViewModel 映射方向Module1InfoTextTextAdaptermodule1.infomodule1 的 computed.infoVM → ViewModule1ClickBtnButtonAdaptermodule1.clickmodule1 的 commands.clickView → VMModule2InfoTextTextAdaptermodule2.infomodule2 的 computed.infoVM → ViewModule2ClickBtnButtonAdaptermodule2.clickmodule2 的 commands.clickView → VMSayHelloBtnButtonAdaptermodule2.say_hello_to_csharpmodule2 的 commands.say_hello_to_csharpView → VMReloadModule1BtnButtonAdapterReloadModule1App.cs 的 [Command] ReloadModule1View → C#ReloadModule2BtnButtonAdapterReloadModule2App.cs 的 [Command] ReloadModule2View → C#6. 跟之前教程的对比特性HelloworldMoreComplexApp 模式模块数1 个隐含模块1 个隐含模块多个独立模块BindTo 格式infooptionsmodule1.info数据隔离无无各模块沙盒隔离模块通信N/AN/A通过 exportsC# 集成无无AddCSharpModule Export热刷新不支持不支持ReloadModule 支持Lua 文件位置内联在 C# 中内联在 C# 中独立的 .lua.txt 文件7. 动手实验7.1 观察模块隔离点击 module1 的 Click 按钮观察两个 InfoText 的变化。你会看到Module1InfoText 里的select在 0 和 1 之间切换Module2InfoText 里的select可能也被 module1 的跨模块调用改变了7.2 尝试跨模块调用链修改module1.commands.click让它调用module2.hello()如果 module2 也有 exports.hello 的话观察调用链如何串联两个模块。7.3 测试热刷新运行场景观察 UI 显示修改module1.lua.txt中的data.name haha改为data.name xixi点击ReloadModule1按钮观察 Module1InfoText 的显示更新为 “i am xixi, …” 而 Module2InfoText 不受影响7.4 新增一个 module3在Resources/myapp/下创建module3.lua.txt在 App.cs 的 modules 列表中加入module3添加对应的 UI 和适配器绑定8. 完整文件清单Assets/ ├── Scenes/ │ └── App.unity ← 本示例的场景 ├── Scripts/ │ └── App.cs ← C# 入口App 模式启动器 ├── Resources/ │ └── myapp/ │ ├── module1.lua.txt ← 模块1data/commands/computed/exports │ └── module2.lua.txt ← 模块2data/commands/computed/exports C# 桥接 └── XUUI/ ├── Scripts/ │ ├── ViewModel.cs ← Context 类包含 AddCSharpModule / ReloadModule │ ├── AdapterBase.cs │ └── UGUIAdapter/ │ ├── TextAdapter.cs │ ├── ButtonAdapter.cs │ └── ... └── Resources/ ├── xuui.lua.txt ← XUUI 主模块包含 app 模式的初始化逻辑 ├── binding.lua.txt ← 绑定引擎 ├── observeable.lua.txt ← 响应式数据系统 └── ...9. 常见问题Q: module1 和 module2 是全局变量吗是的但它们是沙盒内的全局变量。框架在加载每个模块时会把其他模块名注册到当前沙盒的全局环境中所以module1中可以直接用module2.set_select(...)。这本质上是框架管理的命名空间不是真正的 Lua 全局变量不会污染全局环境。Q: 为什么 exports 里的函数能修改 data 而 command 不行command 的函数签名是function(data)这里的data是框架传入的当前模块的 data 副本引用command 只能读写本模块的 data。而 exports 里的函数通过 Lua闭包捕获了外部的data变量可以自由操作。这是设计上的有意区别command 是模块对外的行为接口exports 是模块间的编程接口。Q: Reload 后 data 会重置吗不会。ReloadModule会保留当前的 data 状态只替换 commands、computed 和 exports。所以你在 Reload 前修改的数据会保持不变。Q: 模块脚本必须放在 Resources/myapp 目录下吗不是必须的。默认按 Lua 的 require 规则查找但可以通过CustomLoader自定义加载路径和文件后缀。为了演示方便本示例放在Resources/myapp目录下。

相关新闻

2026年AI量化提效,工具重点要跟阶段走

2026年AI量化提效,工具重点要跟阶段走

2026/9/5 21:30:26

AI 辅助量化开发时,很多效率来自把阶段任务拆清楚。已有经验者通常不缺基本概念,但可能会低估一个事实:在不同开发阶段,真正需要工具帮助的地方会变化,不能把所有阶段都当成同一种问题来处理。让 AI 先帮你把问题问清楚…

毕业季不再焦虑!7款一键生成论文工具1天实现毕业流程全通关

毕业季不再焦虑!7款一键生成论文工具1天实现毕业流程全通关

2026/9/8 15:42:02

先打破错观念:你正在用的“攒论文”方法,正在害你毕不了业 千万别再熬夜蹲图书馆攒论文了!也别再当“学术裁缝”东拼西凑剪别人的内容了!更别随便找个通用大模型直接生成全文直接用了! 这些看起来“省时间”的旧做法…

FPGA交通灯控制系统:从基础设计到带紧急按键与倒计时显示的3大功能扩展

FPGA交通灯控制系统:从基础设计到带紧急按键与倒计时显示的3大功能扩展

2026/9/7 11:47:15

FPGA交通灯控制系统:从基础设计到三大功能扩展实战在数字系统设计领域,交通灯控制系统作为经典的教学案例,涵盖了状态机、时序控制、外设驱动等核心知识点。本文将从一个基础交通灯控制方案出发,逐步实现紧急按键响应、倒计时显示…

CANN/GE ACL数据集缓冲区添加函数

CANN/GE ACL数据集缓冲区添加函数

2026/9/21 18:38:46

aclmdlAddDatasetBuffer 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、Te…

用ffmpeg高效批量调整图片尺寸的实战指南

用ffmpeg高效批量调整图片尺寸的实战指南

2026/9/23 14:32:22

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

Transformers 音频特征提取工具库 audio_utils 全解析:从 Mel 刻度换算到对数 Mel 频谱

Transformers 音频特征提取工具库 audio_utils 全解析:从 Mel 刻度换算到对数 Mel 频谱

2026/9/21 18:36:40

Transformers 音频特征提取工具库 audio_utils 全解析:从 Mel 刻度换算到对数 Mel 频谱 【免费下载链接】transformers 🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and mu…

RustFS 多节点集群重启与滚动升级实战:Readiness、Quorum 与 Degraded 模式完全指南

RustFS 多节点集群重启与滚动升级实战:Readiness、Quorum 与 Degraded 模式完全指南

2026/9/23 14:31:31

RustFS 多节点集群重启与滚动升级实战:Readiness、Quorum 与 Degraded 模式完全指南 【免费下载链接】rustfs 🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system sup…

Java Integer缓存揭秘:128陷阱原理、避坑与面试全解

Java Integer缓存揭秘:128陷阱原理、避坑与面试全解

2026/9/21 18:40:29

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

RustFS Scanner 数据用量发布权威性决策:配额准入如何获得可用的权威依据

RustFS Scanner 数据用量发布权威性决策:配额准入如何获得可用的权威依据

2026/9/23 14:34:03

RustFS Scanner 数据用量发布权威性决策:配额准入如何获得可用的权威依据 【免费下载链接】rustfs 🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting mi…

远程协作的工作台整理

远程协作的工作台整理

2026/9/22 0:19:28

远程协作的工作台整理远程协作的核心不是再加一个工具,而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/21 23:38:13

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/22 0:48:53

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…