RuntimeError 错误码参考
ADVMaker 定义了一个 RuntimeError 类,继承自 Error,用于在游戏运行时报告逻辑错误。所有运行时错误通过 Game.error(new RuntimeError(code, reason)) 统一抛出,并在控制台输出 [RuntimeError #xxx] 格式的日志。
错误码一览
| 错误码 | 含义 | 触发位置 |
|---|---|---|
| 101 | 物品数量不能为负数 | state.ts → obtainItem() |
| 102 | 找不到场景 / 对话 / 属性 | game.ts → enter(), speak(), toNext();state.ts → qryStatus() |
| 103 | 骰子错误 | dice.ts → roll() |
| 104 | 存在重复 ID | game.ts → defineConfig();api.ts → checkHasSameId() |
| 105 | 合成配方材料列表为空 | api.ts → defineRecipe(), removeRecipe() |
| 106 | 给 number 类型状态赋值 string | api.ts → Adv.status setter |
| 107 | 给 string 类型状态赋值 number | api.ts → Adv.status setter |
各错误码详解
101 · 物品数量不能为负数
触发条件:调用 obtainItem() 时,当前持有数量 + 变化量 < 0。
typescript
// 假设当前拥有 3 个苹果
Adv.bag.apple = -5; // ❌ 3 + (-5) = -2 < 0 → RuntimeError(101)常见原因:
- 剧情脚本中写错了扣除数量
- 某个条件分支错误地重复扣除了物品
102 · 找不到场景 / 对话 / 属性
触发条件:引用了未定义的场景 ID、对话 ID 或状态 ID。
这是一个使用频率最高的错误,有 4 个触发点:
| 触发点 | 示例 |
|---|---|
enter(sceneId) | 场景 ID 不存在于已注册的场景中 |
speak(dialogId) | 对话 ID 不存在于已注册的对话中 |
toNext() | next 指向的场景或对话 ID 无法解析 |
qryStatus(id) | 查询的状态 ID 从未通过 defineConfig 注册 |
typescript
Adv.goto('secret_room'); // 如果 'secret_room' 未定义 → RuntimeError(102)常见原因:
- 拼写错误:
'bedroom'写成了'bedRoom' - 忘记注册场景/对话:在
game.config.ts中漏写了某个status next指向了一个已被删除的 ID
103 · 骰子错误
触发条件:传递给 dice() 的表达式格式不正确。
typescript
dice('2d6+abc'); // ❌ 无效的骰子表达式 → RuntimeError(103)
dice('hello'); // ❌ 完全无法解析 → 抛出普通 Error注意:骰子数量和面数由正则
\d+保证为正整数,传入'd20'或'3d6+2'等合法格式不会触发此错误。此错误仅在正则匹配成功但后续解析异常时触发。
104 · 重复 ID
触发条件:注册的场景、对话或状态 ID 与已有的冲突。
typescript
Adv.appendScene('attic', { name: '阁楼' });
Adv.appendScene('attic', { name: '另一个阁楼' }); // ❌ ID 重复 → RuntimeError(104)状态同理——在 game.config.ts 中定义了两个同 ID 的 status 也会触发:
typescript
Adv.defineConfig({
status: {
hp: {
content: {
hp: { name: '生命值', value: 100 }, // ❌ 重复了 'hp'
},
},
},
});常见原因:
- 复制粘贴后忘记改 ID
- 多人协作时 ID 命名冲突
105 · 合成配方为空
触发条件:注册或操作合成配方时,need 列表为空。
typescript
Adv.defineRecipe('potion', {
name: '治疗药水',
need: [], // ❌ 空配方 → RuntimeError(105)
});removeRecipe() 移除最后一个材料后也会触发:
typescript
const rc = Adv.recipeControl('potion');
rc.removeRecipe('herb'); // 如果只剩这一个材料
rc.removeRecipe('water'); // ❌ need 为空 → RuntimeError(105)106 · Number 状态被赋值字符串
触发条件:通过 Adv.status 给数字类型状态赋值了字符串。
typescript
Adv.defineConfig({
status: {
hp: { content: { hp: { name: '生命值', value: 100 } } }, // number 类型
},
});
Adv.status.hp = '满血'; // ❌ number 型被赋 string → RuntimeError(106)这是数字专属的类型守卫。如果你想把一个状态改成字符串类型,请在
defineConfig中将value设为字符串。
107 · String 状态被赋值数字
触发条件:通过 Adv.status 给字符串类型状态赋值了数字。
typescript
Adv.defineConfig({
status: {
reputation: { content: { reputation: { name: '声望', value: '默默无闻' } } }, // string 类型
},
});
Adv.status.reputation = 100; // ❌ string 型被赋 number → RuntimeError(107)调试技巧
- 打开控制台:所有 RuntimeError 在抛出前会通过
console.error输出,即使被try-catch捕获也能看到日志。 - 搜索错误码:在源码中搜索
RuntimeError(X,(X 为子码)可快速定位触发点。 - 利用浏览器断点:在
Game.error()处打断点,可以在错误抛出前检查调用栈。
与普通 Error 的区别
项目中还存在一些普通 throw 语句(如 throw new Error('无效的骰子表达式')),它们不是 RuntimeError 体系的成员:
| 类型 | 用途 | 示例 |
|---|---|---|
RuntimeError | 游戏逻辑层面的可预期错误 | 找不到场景、状态类型错误 |
原生 Error / throw | 编程错误或未分类异常 | 骰子正则不匹配、栈为空、内部断言 |