DST模组开发-从UI崩溃到网络同步

2025-08-06

一、基础实现:物品与静态UI

第一阶段的目标是创建一个基础物品,右键点击可打开一个静态UI界面

1.1 组件生命周期与 nil 值错误

问题: 为防止重复给予物品,我使用了 inst.components.inventory:HasItemWithTag() 进行检查,但这导致了 attempt to compare nil with number

分析: 错误源于 HasItemWithTag 在一个 DoTaskInTime 回调中被调用。该回调执行时,玩家的 inventory 组件没有完成初始化,导致 inst.components.inventorynil

解决方案: 改为在玩家的 OnLoad 函数事件监听中,直接遍历 inst.components.inventory.itemslots 检查物品,确保在访问 inventory 组件时,它处于可用状态

1.2 UI实例管理与游戏冻结

问题: 第一次打开UI正常,关闭后第二次再打开,UI不显示且角色无法移动

分析: 每次打开UI都创建了一个新的 AtlasBookUI 实例并推入屏幕栈 TheFrontEnd:PushScreen()。旧实例没有销毁,导致游戏逻辑在等待一个失效的UI响应,从而冻结

解决方案: 在打开UI的函数中增加判断。如果UI实例已存在,则不再创建新实例

1.3 UI根控件的锚点与缩放

问题: UI窗口能显示,但位置总是在屏幕左下角,且在不同分辨率下显示不一致

分析: UI的root widget默认锚点在屏幕左下角。没有设置正确的锚点和缩放模式,导致其位置和尺寸无法适应屏幕

解决方案: 为根控件设置居中锚点和比例缩放模式

self.root:SetVAnchor(ANCHOR_MIDDLE) -- 垂直居中
self.root:SetHAnchor(ANCHOR_MIDDLE) -- 水平居中
self.root:SetScaleMode(SCALEMODE_PROPORTIONAL) -- 比例缩放

二、UI交互:控件的复杂性

第二阶段的目标是实现可自由添加的todolist,但在为UI添加可交互的列表时,遇到了大量报错

模板控件 ScrollingGrid的复杂性

问题: 尝试使用 TEMPLATES.ScrollingGrid 构建可滚动列表,直接导致游戏崩溃,错误指出 widget_heightnil

分析: TEMPLATES 中的许多控件封装较深,其构造函数期望的不是一系列独立参数,我没能提供正确的参数,导致构造失败返回 nil

解决方案: 没有找到合适的教程与参考,彻底放弃复杂的模板控件。改为手动构建列表:

  1. 使用一个基础的 Widget 作为列表容器
  2. 在数据更新时,先调用 container:KillAllChildren() 清空旧项
  3. 遍历数据,为每条数据手动创建独立的UI控件,手动计算和设置它们在容器内的位置

三、网络同步:Todolist 的实现

3.1 C/S架构与RPC通信

问题: UI正常显示了,但点击后没有反应,任务列表为空

分析: UI交互发生在客户端,但数据状态来源服务器。客户端的点击操作需要通过RPC 通知服务器来修改数据

解决方案:

  1. 服务器组件: 创建一个附加在 TheWorld 上的服务器端组件,作为任务数据的唯一来源,负责所有增删改查
  2. RPC注册: 在 modmain.lua 中,使用 AddModRPCHandler 为每个操作注册一个处理器,该处理器调用服务器组件的对应函数
  3. 客户端调用: 在UI脚本中,为按钮点击事件添加 SendModRPCToServer 调用,将操作和数据发送给服务器

3.2 组件注册与数据同步

问题: 游戏日志显示任务已添加并同步,但客户端UI仍为空,并报错 TheWorld.net.components.atlas_todolist 不存在

分析: 服务器成功地广播了数据,但客户端没有一个对应的组件实例来接收和处理这些数据。ReplicableComponent 的注册方式不正确,未能使组件在客户端成功初始化

解决方案:

  1. 统一组件文件: 将客户端和服务端逻辑合并到同一个组件文件,内部使用 if self.inst.ismastersim then ... else ... end 进行区分
  2. 正确的注册时机: 使用 AddSimPostInit,在其回调函数中判断 if not TheWorld.ismastersim then ... end,安全地为客户端的 TheWorld 实体添加组件实例
  3. 事件驱动: 在客户端组件初始化完成后,触发一个自定义事件 (如 atlas_todolist_ready)。UI脚本监听此事件

总结

  • 组件生命周期:必须在正确的时机访问组件,避免 nil 错误
  • UI框架TEMPLATES 控件封装深且脆弱。在缺乏合适的教程时,回归基础控件手动构建UI是更可靠的方案
  • C/S架构:严格区分客户端与服务器的职责。任何改变世界状态的操作都必须通过 RPC 由服务器执行