游戏 GM 工具的动态注册与收藏设计

随着玩法增加,项目的 GM 面板里积累了越来越多按钮。最早把命令放在同一页还算方便,后来查一个不常用的操作要翻很久,添加命令也常常需要修改面板本身。

我在 2024 年 8 月做了带分类的新版本,允许业务在运行时注册命令;到 2025 年 3 月,又增加了本地收藏。改动不复杂,但它解决的是开发和测试每天都会遇到的问题。

把命令表示成数据

先把命令管理从面板类里抽出来。一个命令至少需要名称、分类和执行函数;有些按钮的名称会随当前状态改变,因此名称也可以由函数提供。

下面是经过简化的 Lua 结构:

1
2
3
4
5
6
7
8
gm:register({
name = "补充测试物品",
class = "Inventory",
exec = function(input)
local count = tonumber(input) or 1
debugInventory:addSampleItems(count)
end,
})

业务负责注册它真正需要的命令,面板根据命令数据生成按钮。以后增加分类或操作时,就不必总往同一个 UI 文件里追加逻辑。

管理器按分类保存列表,同时保留名称索引。注册时检查重复项,执行时统一捕获异常并记录结果,面板则负责参数输入、按钮显示和用户操作。

分类帮助找到操作,收藏帮助重复使用

分类与收藏服务于两种不同场景。使用者不知道命令在哪里时,可以按业务分类查找;已经知道某个命令、每天都要反复用时,更需要一个固定入口。

分类与收藏在 GM 面板中分别帮助查找命令和执行常用操作

按原功能重新绘制的示意,命令名称均为通用示例。

收藏保存到本地,重启游戏后再从已注册命令中恢复。这使测试同学不必每天重新找到相同的几个按钮。实现时还补了重复收藏检查和删除后的持久化清理,避免面板显示与下次启动恢复出的内容不一致。

分类改善的是人的查找路径,不能简单把它描述为某种确定的算法复杂度下降。命令数量、分类方式和使用习惯,都会影响实际查找体验。

收藏引用的标识要足够稳定

这版实现使用命令名称键查找收藏,接入很直接,但也有局限:如果名称由函数产生,状态改变之后名称可能变化;如果为了改善文案重命名按钮,旧收藏也可能找不到原命令。

因此在继续设计类似工具时,我会把稳定 ID 与显示名称分开:

1
2
3
4
5
{
id = "debug.inventory.add_sample",
name = "补充测试物品",
exec = addSampleItems,
}

这是对原实现的改进方向,不是这次版本已经具备的能力。收藏只保存 ID,名称可以变化;运行时命令被移除,也能通过 ID 判断收藏已经失效。

另一个细节是恢复时机:需要等业务完成注册,再解析收藏。如果在注册之前就清掉所有“查不到”的项,暂时未加载的业务命令也会丢失。

异常处理集中到执行入口

面板没有必要为每个按钮都写一遍异常处理。Lua 管理器可以用 xpcall 包住命令执行,统一输出成功或失败信息,再刷新可能随状态变化的按钮名称。

但这个包装主要覆盖同步执行。命令内部另起协程或等待异步回调时,后续失败仍然需要对应的异步结果处理,不能因为外层函数已经返回就显示“操作完成”。

注册、显示、执行与收藏各自负责一件事后,工具本身就更容易扩展。真正检验设计的是下一位同事添加一个命令时需要改多少地方,以及使用者能不能很快找到它。