程序控制语句
在真实的文字冒险游戏中,故事走向很少是线性的——你需要根据玩家的状态决定显示哪些内容、在特定条件下提前结束对话、或强制跳转到另一段剧情。本章介绍的五个组件让你能够对故事流程进行精确控制。
条件分支:AIf、AElif 与 AElse
现实世界的冒险故事充满了"如果……那么……否则……"。AIf、AElif 和 AElse 三个组件让你能够在故事脚本中嵌入条件逻辑,根据玩家的属性、背包物品或任意自定义条件动态决定显示哪段对话。
AIf — 如果
AIf 组件接受一个 condition 属性,该属性是一个返回 boolean 或 Promise<boolean> 的函数。当条件为 true 时,其默认插槽中的内容会被执行;否则被跳过。
<ADialog>
<ALine>你打量着眼前的神秘宝箱。</ALine>
<AIf :condition="() => Adv.bag.goldKey >= 1">
<ALine>你用金钥匙打开了宝箱,里面金光闪闪!</ALine>
<ALine>你获得了 100 枚金币。</ALine>
</AIf>
<AIf :condition="() => Adv.bag.goldKey < 1">
<ALine>你没有钥匙,宝箱纹丝不动。</ALine>
</AIf>
</ADialog>注意:
condition必须是一个函数() => boolean,而不是一个直接的值。这是因为引擎在收集阶段需要延迟求值——条件只有在对话实际执行时才会被调用,而非在组件挂载时。因此:condition="Adv.bag.goldKey >= 1"是错误的写法。
API 等价用法
使用 Adv.if()、Adv.end() 配合 Adv.appendDialog 的 .say() 链式调用:
Adv.appendDialog('chest_dialog')
.say('你打量着眼前的神秘宝箱。')
.say(Adv.if(() => Adv.bag.goldKey >= 1))
.say('你用金钥匙打开了宝箱,里面金光闪闪!')
.say('你获得了 100 枚金币。')
.say(Adv.end())
.say(Adv.if(() => Adv.bag.goldKey < 1))
.say('你没有钥匙,宝箱纹丝不动。')
.say(Adv.end());Adv.if(condition)开启一个条件块,对应<AIf>。Adv.end()关闭当前条件块,对应隐式的endif。- 条件块之间的内容只有在对应条件满足时才会被引擎执行。
AElif — 否则如果
当你有多个互斥的条件时,AElif 提供了一种更优雅的写法,避免嵌套多层 AIf:
<ADialog>
<ALine>你查看自己的冒险者等级。</ALine>
<AIf :condition="() => Adv.status.level >= 10">
<ALine>你是传说中的传奇冒险者!</ALine>
</AIf>
<AElif :condition="() => Adv.status.level >= 5">
<ALine>你是一名经验丰富的老手。</ALine>
</AElif>
<AElif :condition="() => Adv.status.level >= 2">
<ALine>你逐渐熟悉了冒险的节奏。</ALine>
</AElif>
</ADialog>在这个例子中,引擎会从上到下依次评估条件,只执行第一个满足条件的分支,后续分支自动跳过。
API 等价用法
使用 Adv.elif() 替代 AElif:
Adv.appendDialog('level_check')
.say('你查看自己的冒险者等级。')
.say(Adv.if(() => Adv.status.level >= 10))
.say('你是传说中的传奇冒险者!')
.say(Adv.elif(() => Adv.status.level >= 5))
.say('你是一名经验丰富的老手。')
.say(Adv.elif(() => Adv.status.level >= 2))
.say('你逐渐熟悉了冒险的节奏。')
.say(Adv.end());AElse — 否则
AElse 用于兜底——当所有 AIf 和 AElif 条件都不满足时,执行 AElse 中的内容:
<ADialog>
<ALine>你检查自己的钱包。</ALine>
<AIf :condition="() => Adv.bag.gold >= 100">
<ALine>你囊中丰厚,足够在商店里挥霍一番。</ALine>
</AIf>
<AElif :condition="() => Adv.bag.gold >= 10">
<ALine>你有一些零钱,可以买点小物件。</ALine>
</AElif>
<AElse>
<ALine>你身无分文,得想办法赚点钱……</ALine>
</AElse>
</ADialog>API 等价用法
使用 Adv.else() 替代 AElse:
Adv.appendDialog('wallet_check')
.say('你检查自己的钱包。')
.say(Adv.if(() => Adv.bag.gold >= 100))
.say('你囊中丰厚,足够在商店里挥霍一番。')
.say(Adv.elif(() => Adv.bag.gold >= 10))
.say('你有一些零钱,可以买点小物件。')
.say(Adv.else())
.say('你身无分文,得想办法赚点钱……')
.say(Adv.end());条件块的执行规则
引擎内部使用一个状态栈(IfStateManager)来管理条件块的执行流,规则如下:
- 遇到
AIf(Adv.if):评估条件。为true时进入"执行中"状态,为false时进入"跳过中"状态。 - 遇到
AElif(Adv.elif):若当前状态为"执行中"(说明之前的分支已满足),则切换为"分支已消费"(跳过后续所有分支);若当前状态为"跳过中",则评估自身条件,为true时切换为"执行中"。 - 遇到
AElse(Adv.else):若当前状态为"跳过中"(说明之前所有条件均不满足),则切换为"执行中";若为"执行中",则切换为"分支已消费"。 - 遇到
Adv.end():退出当前条件块,恢复到上一层状态。 AIf、AElif、AElse内部的子内容会各自注册到父级ADialog或AOption,但最终是否实际输出到屏幕取决于执行状态栈的判定。
条件与 AOptions 组合
条件分支可以和选项系统自由组合。下面是一个更真实的例子:
<ADialog>
<ALine>酒馆老板看了你一眼。</ALine>
<AIf :condition="() => Adv.bag.gold >= 50">
<ALine>"嘿,老顾客了!这是你最喜欢的蜜酒。"</ALine>
</AIf>
<AElse>
<ALine>"要点什么?不过我看你口袋比脸还干净。"</ALine>
</AElse>
<AOptions>
<AOption>
<template #content>点一杯最贵的酒</template>
<AIf :condition="() => Adv.bag.gold >= 100">
<ALine>你豪气地拍下一枚金币,全场为你欢呼。</ALine>
</AIf>
<AElse>
<ALine>老板摇摇头:"钱不够,别开玩笑。"</ALine>
</AElse>
</AOption>
<AOption>
<template #content>打听消息</template>
<ALine>老板压低声音:"北边的森林最近不太平……"</ALine>
</AOption>
</AOptions>
</ADialog>提前结束:AEndDialog
AEndDialog 是一个无渲染组件,它的作用是在对话脚本中插入一个提前返回标记。当引擎执行到该标记时,会立即停止当前对话的后续脚本执行,并跳转到当前对话的 next 指向。
<ADialog id="secret_door">
<ALine>你伸手推开暗门……</ALine>
<AIf :condition="() => Adv.status.perception >= 10">
<ALine>你敏锐地察觉到了门后的陷阱,轻松避开。</ALine>
</AIf>
<AElse>
<ALine>你不小心触发了陷阱!</ALine>
<AEndDialog />
<ALine>这行永远不会被执行——因为 AEndDialog 提前结束了对话。</ALine>
</AElse>
</ADialog>工作原理
AEndDialog 向对话脚本中插入一个 ADVCReturn(type: 'return')指令。引擎在逐条执行脚本时,遇到 return 类型的指令会立即跳出循环,不会再执行后续内容。之后会正常调用 Game.toNext(dialog.next) 跳转到下一个对话。
API 等价用法
在 API 中,使用 Adv.return() 即可插入返回标记:
Adv.appendDialog('secret_door')
.say('你伸手推开暗门……')
.say(Adv.if(() => Adv.status.perception >= 10))
.say('你敏锐地察觉到了门后的陷阱,轻松避开。')
.say(Adv.else())
.say('你不小心触发了陷阱!')
.say(Adv.return()) // ← 提前返回,后续内容不会执行
.say('这行永远不会被执行')
.say(Adv.end());典型用途
- 条件提前结束:在条件分支中,某条路径需要终止当前对话但不需要特别处理。
- 配合选项使用:某选项触发特定剧情后立即结束对话,而非继续播放后续行。
跳转:AGoto
AGoto 组件允许你在对话脚本的中间位置强制跳转到另一个对话或场景,而不是等待当前对话自然结束。
<ADialog id="forest_path">
<ALine>你在森林中前行,突然看到一座废弃的祭坛。</ALine>
<AOptions>
<AOption>
<template #content>靠近祭坛调查</template>
<ALine>你走向祭坛,脚下的石板发出异样的光芒……</ALine>
<AGoto tgt="altar_room" />
</AOption>
<AOption>
<template #content>绕道离开</template>
<ALine>你决定谨慎为上,绕开了祭坛。</ALine>
</AOption>
</AOptions>
<ALine>你继续沿着小路前进。</ALine>
<ALine>这行在普通路径下会正常显示,但选择"靠近祭坛"则会被跳过。</ALine>
</ADialog>属性
| 属性 | 类型 | 必需 | 说明 |
|---|---|---|---|
| tgt | string | 是 | 目标对话或场景的 ID,如 "altar_room" |
工作原理
AGoto 在脚本中注册两个连续的操作:
- 跳转回调:执行
Adv.goto(props.tgt),立即将游戏流程跳转到目标 ID。 - 返回标记:紧接着注册一个
ADVCReturn,确保当前对话的后续脚本被跳过。
这意味着 AGoto 之后的任何内容都不会被执行,故事流程会立即转向目标。
API 等价用法
在 API 中,通过 AOption 的 onChoose 回调调用 Adv.goto() 即可实现同样的效果:
Adv.appendDialog('forest_path')
.say('你在森林中前行,突然看到一座废弃的祭坛。')
.choice({
content: '靠近祭坛调查',
onChoose: () => {
Adv.goto('altar_room');
},
})
.choice({
content: '绕道离开',
})
.say('你继续沿着小路前进。');与直接设置
next的区别:ADialog和AOption的next是在对话全部脚本执行完毕后才触发跳转。而AGoto可以在对话的任意位置(如选项反馈中间)提前跳转,跳过当前对话的剩余内容。在 API 中,onChoose里调用Adv.goto()同样会立即跳转,跳过当前对话后续所有脚本。