运行与检定
除了控制故事流程,你还需要在关键时刻执行自定义逻辑(修改属性、播放音效)和进行属性检定(通过掷骰决定成败)。本章介绍 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.playBgm('battle.mp3');
await sleep(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.playBgm('battle.mp3');
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> | 否 | 检定失败时的回调函数 |
骰子表达式
dice 属性支持以下格式的骰子表达式:
| 表达式 | 含义 | 示例 |
|---|---|---|
d20 | 1 个 20 面骰 | 范围 1~20 |
2d6 | 2 个 6 面骰 | 范围 2~12 |
3d8+2 | 3 个 8 面骰+2 | 范围 5~26 |
1d100-5 | 1 个 100 面骰-5 | 范围 -4~95 |
如果未指定 dice,则使用游戏配置中的默认骰子(通常为 d20)。
自定义骰子
你可以传入一个 ADVDice 对象来实现完全自定义的掷骰逻辑:
<script setup>
const customDice = new ADVDice('命运骰', () => {
// 自定义:六面骰,但结果总是奇数
return Math.floor(Math.random() * 3) * 2 + 1;
});
</script>
<ACheck :target="3" :dice="customDice">
<template #success>命运眷顾了你!</template>
<template #fail>命运与你擦肩而过。</template>
</ACheck>API 等价用法
ACheck 组件本质上是 AIf + AElse + Adv.check 的语法糖。在 API 中,有两种等效方式:
方式一:使用 Adv.check 结合条件分支(推荐)
Adv.check() 返回 Promise<boolean>,可直接作为 AIf 的条件源:
Adv.appendDialog('pick_lock')
.say('你试图撬开这把古老的锁。')
.say(
Adv.if(
async () =>
await Adv.check({
target: 10,
modifier: [{ name: '灵巧', value: () => Adv.status.dexterity }],
}),
),
)
.say('锁芯发出一声清脆的"咔嗒",锁开了!')
.say(Adv.else())
.say('锁纹丝不动,你的手指有些酸痛。')
.say(Adv.end());Adv.check() 本身会:
- 掷骰并计算修正。
- 在屏幕上显示详细的检定提示信息(骰子类型、原始点数、修正项、最终结果)。
- 执行
onSuccess/onFail回调。 - 返回
true(成功)或false(失败)。
方式二:使用 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 () =>
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),
}),
),
)
.say('你身轻如燕,悄无声息地翻过了围墙。')
.say('任务日志已更新:潜入成功。')
.say(Adv.else())
.say('你不小心踢到了一只铁桶!')
.say('守卫被惊动,你被迫交了 50 金币罚款。')
.say(Adv.end())
.say('你整理了一下衣襟,继续前进。');工作原理
ACheck 本质上是对 AIf、AElse 和 Adv.check 的封装。它在内部:
- 调用
Adv.check({ ... })执行完整的检定流程:- 掷骰得到原始点数。
- 遍历
modifier列表,累加所有修正值。 - 将最终点数与目标值比较(根据游戏配置的判定模式)。
- 在屏幕上显示详细的检定提示信息,包括骰子类型、原始点数、各项修正和最终结果。
- 根据检定结果:
- 成功 → 渲染
#success插槽的内容,执行onSuccess回调。 - 失败 → 渲染
#fail插槽的内容,执行onFail回调。
- 成功 → 渲染
判定模式
游戏的判定模式通过 game.config.ts 中的 judgmentMode 配置:
'd20'(默认):点数 ≥ 目标值 为成功。适用于 D&D 等 d20 系统。'coC':点数 ≤ 目标值 为成功。适用于克苏鲁的呼唤等百面骰系统。
引擎会输出类似下面的检定信息:
检定成功!潜行难度,目标 15 点,【投掷 d20】13 | 敏捷 +2 | 潜行技能 +3 =18 点。