运行与检定
除了控制故事流程,你还需要在关键时刻执行自定义逻辑(修改属性、播放音效)和进行属性检定(通过掷骰决定成败)。本章介绍 ARun 和 ACheck 两个组件,它们让你的故事具备真正的交互逻辑和随机性。
运行时执行:ARun
ARun 组件让你能够在对话脚本的执行流程中嵌入任意自定义代码,非常适合执行状态变更、播放音效、触发动画等副作用。
<ADialog>
<ALine>你从宝箱中拿出了一把闪闪发光的宝剑。</ALine>
<ARun :run="() => { Adv.bag.legendarySword += 1; Adv.status.attack += 10; }" />
<ALine>攻击力提升了 10 点!</ALine>
</ADialog>属性
| 属性 | 类型 | 必需 | 说明 |
|---|---|---|---|
| run | () => void | Promise<void> | 是 | 需要执行的函数,支持同步和异步操作 |
异步操作
ARun 支持 async 函数,引擎会等待 Promise 完成后再继续执行后续脚本:
<ARun
:run="async () => {
Adv.audio.play('/music/battle.mp3', 'bgm', { loop: true });
await new Promise(r => setTimeout(r, 500));
Adv.bag.gold -= 10;
}"
/>API 等价用法
在 API 中,直接将函数作为参数传给 .say(),引擎会自动执行它:
Adv.appendDialog('sword_chest')
.say('你从宝箱中拿出了一把闪闪发光的宝剑。')
.say(() => {
Adv.bag.legendarySword += 1;
Adv.status.attack += 10;
})
.say('攻击力提升了 10 点!');
// 异步版本
Adv.appendDialog('battle_start').say(async () => {
Adv.audio.play('/music/battle.mp3', 'bgm', { loop: true });
await new Promise((r) => setTimeout(r, 500));
Adv.bag.gold -= 10;
});.say() 接受任意函数 () => void | Promise<void>,其行为与 ARun 完全一致。
典型用途
- 修改玩家属性:增减金币、生命值、道具数量。
- 调用音频 API:播放背景音乐或音效。
- 触发外部动画或特效:如屏幕震动、粒子效果。
- 记录日志或统计:将玩家的关键决策写入存档。
与
AOption的onChoose对比:AOption.onChoose仅在玩家选择该选项时触发,且发生在选项反馈台词播放之前。而ARun可以放在对话或选项反馈的任意位置,在指定的时刻执行。在 API 中两者都表现为函数,区别在于函数在脚本数组中的位置。
属性检定:ACheck
ACheck 是 ADVMaker 中最强大的组件之一,它封装了完整的 TRPG 式属性检定流程:掷骰 → 计算修正 → 比较目标值 → 分流成功/失败分支。
基本用法
ACheck 使用两个具名插槽 #success 和 #fail 来分别定义检定成功和失败后展示的内容:
<ADialog>
<ALine>你试图撬开这把古老的锁。</ALine>
<ACheck :target="10" :modifier="[{ name: '灵巧', value: () => Adv.status.dexterity }]">
<template #success>
<ALine>锁芯发出一声清脆的"咔嗒",锁开了!</ALine>
</template>
<template #fail>
<ALine>锁纹丝不动,你的手指有些酸痛。</ALine>
</template>
</ACheck>
</ADialog>属性详解
| 属性 | 类型 | 必需 | 说明 |
|---|---|---|---|
target | number | (() => number) | 否 | 检定目标值(难度等级 DC),默认为 0 |
targetDesc | string | 否 | 目标描述文本,会在检定提示中显示(如"开锁难度") |
dice | ADVDice | DiceExpression | 否 | 骰子表达式,如 "d20"、"2d6"、"3d8+2" 等 |
modifier | { name: string; value: () => number }[] | 否 | 修正值列表,每个修正项包含名称和取值函数 |
onSuccess | () => VlAndAsync<void> | 否 | 检定成功时的回调函数 |
onFail | () => VlAndAsync<void> | 否 | 检定失败时的回调函数 |
插槽属性
ACheck 的 #success 和 #fail 插槽均为作用域插槽,会向插槽内容暴露检定的详细结果:
| 插槽属性 | 类型 | 说明 |
|---|---|---|
nat | number | 原始掷骰值(natural roll),即修正前的裸骰结果 |
dirty | number | 最终检定值(dirty roll),即原始值加上所有修正后的结果 |
使用示例:
<ACheck :target="15" :dice="'d20'"
:modifier="[{ name: '力量加成', value: () => 3 }]">
<template #success="{ nat, dirty }">
<ALine>检定成功!原始掷骰 {{ nat }},最终值 {{ dirty }}</ALine>
</template>
<template #fail="{ nat, dirty }">
<ALine>检定失败!原始掷骰 {{ nat }},最终值 {{ dirty }}</ALine>
</template>
</ACheck>事件(Emits)
ACheck 会在检定完成后触发 check 事件,供父组件监听:
| 事件名 | 参数 | 说明 |
|---|---|---|
check | (res: boolean, nat: number, dirty: number) | 检定完成时触发 |
API 等价用法
ACheck 组件本质上是 AIf + AElse + Adv.check 的语法糖。在 API 中,有两种等效方式:
方式一:使用 Adv.check 结合条件分支(推荐)
Adv.check() 返回 Promise<{ res: boolean, nat: number, dirty: number }>,其中 res 字段可直接作为 AIf 的条件:
Adv.appendDialog('pick_lock')
.say('你试图撬开这把古老的锁。')
.say(
Adv.if(async () => {
const { res } = await Adv.check({
target: 10,
modifier: [{ name: '灵巧', value: () => Adv.status.dexterity }],
});
return res;
}),
)
.say('锁芯发出一声清脆的"咔嗒",锁开了!')
.say(Adv.else())
.say('锁纹丝不动,你的手指有些酸痛。')
.say(Adv.end());Adv.check() 本身会:
- 掷骰并计算修正。
- 在屏幕上显示详细的检定提示信息(骰子类型、原始点数、修正项、最终结果)。
- 执行
onSuccess/onFail回调。 - 返回
{ res: boolean, nat: number, dirty: number }——成功与否、原始值、最终值。
方式二:使用 Dialog 级别的 check 属性
你也可以将 check 直接配置在 ADialog 或 AChoice 上,这样引擎会在对话脚本执行完毕后自动进行检定:
Adv.appendDialog('pick_lock', {
check: {
target: 10,
modifier: [{ name: '灵巧', value: () => Adv.status.dexterity }],
success: 'after_lock_success', // 检定成功后的跳转
fail: 'after_lock_fail', // 检定失败后的跳转
onSuccess: () => {
/* 成功回调 */
},
onFail: () => {
/* 失败回调 */
},
},
})
.say('你试图撬开这把古老的锁。')
.say('你专注地摆弄着锁芯……');完整示例
下面是一个完整的场景检定的例子,综合展示了 ACheck 的各个特性:
<ADialog>
<ALine>你需要在众目睽睽之下潜入贵族宅邸。</ALine>
<ACheck
target="15"
targetDesc="潜行难度"
dice="d20"
:modifier="[
{ name: '敏捷', value: () => Adv.status.agility },
{ name: '潜行技能', value: () => Adv.status.stealth },
{ name: '夜色掩护', value: () => (Adv.bag.nightCloak > 0 ? 5 : 0) },
]"
:on-success="() => Adv.bag.missionProgress = 'infiltrated'"
:on-fail="() => Adv.bag.gold -= 50"
>
<template #success>
<ALine>你身轻如燕,悄无声息地翻过了围墙。</ALine>
<ALine>任务日志已更新:潜入成功。</ALine>
</template>
<template #fail>
<ALine>你不小心踢到了一只铁桶!</ALine>
<ALine>守卫被惊动,你被迫交了 50 金币罚款。</ALine>
</template>
</ACheck>
<ALine>你整理了一下衣襟,继续前进。</ALine>
</ADialog>API 等价
Adv.appendDialog('stealth_check')
.say('你需要在众目睽睽之下潜入贵族宅邸。')
.say(
Adv.if(async () => {
const { res } = await Adv.check({
target: 15,
targetDesc: '潜行难度',
dice: 'd20',
modifier: [
{ name: '敏捷', value: () => Adv.status.agility },
{ name: '潜行技能', value: () => Adv.status.stealth },
{ name: '夜色掩护', value: () => (Adv.bag.nightCloak > 0 ? 5 : 0) },
],
onSuccess: () => (Adv.bag.missionProgress = 'infiltrated'),
onFail: () => (Adv.bag.gold -= 50),
});
return res;
}),
)
.say('你身轻如燕,悄无声息地翻过了围墙。')
.say('任务日志已更新:潜入成功。')
.say(Adv.else())
.say('你不小心踢到了一只铁桶!')
.say('守卫被惊动,你被迫交了 50 金币罚款。')
.say(Adv.end())
.say('你整理了一下衣襟,继续前进。');工作原理
ACheck 本质上是对 AIf、AElse 和 Adv.check 的封装。它在内部:
- 调用
Adv.check({ ... })执行完整的检定流程:- 掷骰得到原始点数(
nat)。 - 遍历
modifier列表,累加所有修正值,得到最终点数(dirty)。 - 将最终点数与目标值比较(根据游戏配置的判定模式)。
- 在屏幕上显示详细的检定提示信息,包括骰子类型、原始点数、各项修正和最终结果。
- 返回
{ res: boolean, nat: number, dirty: number }。
- 掷骰得到原始点数(
- 将
nat和dirty通过作用域插槽传递给#success/#fail插槽内容。 - 根据检定结果:
- 成功 → 渲染
#success插槽的内容,执行onSuccess回调。 - 失败 → 渲染
#fail插槽的内容,执行onFail回调。
- 成功 → 渲染
- 同时触发
check事件,将(res, nat, dirty)发送给父组件。