完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
新手写界面:按钮回调里直接改数值、刷新文本、发包,一个函数干三件事。等第二个入口也要改同一个数值时,要么复制粘贴,要么到处打补丁。MVC 的价值就在这里:Model 管数据,View 管显示,Controller 管转换——数据变了界面自动跟着变,界面永远只是数据的镜像。
Lua 动态特性让"数据绑定"可以做得非常轻。核心思路:Model 的每个字段带观察者列表,写入时通知所有订阅的 View 刷新。
local function bind(model, key, onChange)
local raw = model[key] -- 闭包持有真实值
return setmetatable({}, {
__index = function() return raw end,
__newindex = function(_, _, v)
raw = v
onChange(v) -- 数据一变,界面自动刷新
end,
})
end
-- View 订阅
gold = bind(state, "gold", function(v)
goldLabel:setText("金币:" .. v)
end)
-- 任何地方只改数据,界面自动更新
gold = 8800
配合上一期的 EventBus 使用效果更佳:Controller 监听业务事件改 Model,Model 变更再广播刷新事件,三层各司其职。
第一,View 禁止持有业务状态。 界面控件里不存任何逻辑数据,只做渲染;刷新函数永远从 Model 取值。这样界面重建(开关面板、热更后刷新)不会丢数据。
第二,Model 不引用 View。 数据层出现 UI 对象引用是崩坏的开始,Model 只发事件/回调,谁关心谁来订阅。这保证了无界面环境(机器人压测、单元测试)下业务逻辑照样能跑。
第三,Controller 薄。 控制器只做参数校验、协议转换、流程编排,不堆业务算法;算法沉到 Model 的方法或独立模块里。Controller 越薄,热更替换越安全。
数据与视图分离后,热更界面逻辑(View/Controller)完全不必触碰运行中的 Model 数据,替换即生效、状态零丢失;反过来改数值规则也不必动界面代码。对需要高频更新活动的商业服来说,这个分离不是架构洁癖,而是直接的运维效率。从一个面板开始实践,把"回调里改三件事"的旧代码逐步拆干净,整套规范就能在团队里自然生长。
openwindows(actor, winID)
查看自己面板,Controller 触发面板刷新的入口
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| winID | integer | 101=装备 102=状态 103=属性 104=技能 105=生肖 106=称号 1011=时装(必填参数) |
addbutton(actor, windowid, buttonid, icon)
增加自定义按钮,动态生成的视图元素由 Controller 统一挂载
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| windowid | integer | 主窗口ID(必填参数) |
| buttonid | integer | 按钮ID(必填参数) |
| icon | string | 图标内容(必填参数) |
delbutton(actor, windowid, buttonid)
删除自定义按钮,数据缩水时同步回收,否则残留点击区
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| windowid | string | 主窗口ID(必填参数) |
| buttonid | integer | 按钮ID(必填参数) |
sendredvartoclient(actor)
立即推送前端变量,Model 变化后只推变化的字段,不整页重建
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
refreshitem(actor, item, type, position, value)
刷新物品信息到前端,列表类视图的局部刷新
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| item | object | 物品对象(必填参数) |
| type | number | [1,2,3](必填参数) |
| position | number | 当type=1,取值范围[0..49]type=2,取值范围[0..19](必填参数) |
| value | number | 设置物品对应位置值(必填参数) |
reddot(actor, win_id, btn_id, x, y, type, info)
给按钮增加红点,把状态转换成视图可见的红点,而不是在界面里判断业务
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| win_id | integer | 窗口ID(必填参数) |
| btn_id | integer | 按钮ID/任务栏填任务ID(必填参数) |
| x | integer | X坐标(必填参数) |
| y | integer | Y坐标(必填参数) |
| type | integer | 红点模式0=图片1=特效(必填参数) |
reddel(actor, win_id, btn_id)
给按钮删除红点,状态复位时清标记
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| win_id | integer | 窗口ID(必填参数) |
| btn_id | integer | 按钮ID/任务栏填任务ID(必填参数) |