Skip to content

运行与检定

除了控制故事流程,你还需要在关键时刻执行自定义逻辑(修改属性、播放音效)和进行属性检定(通过掷骰决定成败)。本章介绍 ARunACheck 两个组件,它们让你的故事具备真正的交互逻辑和随机性。


运行时执行:ARun

ARun 组件让你能够在对话脚本的执行流程中嵌入任意自定义代码,非常适合执行状态变更、播放音效、触发动画等副作用。

xml
<ADialog>
    <ALine>你从宝箱中拿出了一把闪闪发光的宝剑。</ALine>
    <ARun :run="() => { Adv.bag.legendarySword += 1; Adv.status.attack += 10; }" />
    <ALine>攻击力提升了 10 点!</ALine>
</ADialog>

属性

属性类型必需说明
run() => void | Promise<void>需要执行的函数,支持同步和异步操作

异步操作

ARun 支持 async 函数,引擎会等待 Promise 完成后再继续执行后续脚本:

xml
<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(),引擎会自动执行它:

ts
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:播放背景音乐或音效。
  • 触发外部动画或特效:如屏幕震动、粒子效果。
  • 记录日志或统计:将玩家的关键决策写入存档。

AOptiononChoose 对比AOption.onChoose 仅在玩家选择该选项时触发,且发生在选项反馈台词播放之前。而 ARun 可以放在对话或选项反馈的任意位置,在指定的时刻执行。在 API 中两者都表现为函数,区别在于函数在脚本数组中的位置。


属性检定:ACheck

ACheck 是 ADVMaker 中最强大的组件之一,它封装了完整的 TRPG 式属性检定流程:掷骰 → 计算修正 → 比较目标值 → 分流成功/失败分支。

基本用法

ACheck 使用两个具名插槽 #success#fail 来分别定义检定成功和失败后展示的内容:

xml
<ADialog>
    <ALine>你试图撬开这把古老的锁。</ALine>
    <ACheck :target="10" :modifier="[{ name: '灵巧', value: () => Adv.status.dexterity }]">
        <template #success>
            <ALine>锁芯发出一声清脆的"咔嗒",锁开了!</ALine>
        </template>
        <template #fail>
            <ALine>锁纹丝不动,你的手指有些酸痛。</ALine>
        </template>
    </ACheck>
</ADialog>

属性详解

属性类型必需说明
targetnumber | (() => number)检定目标值(难度等级 DC),默认为 0
targetDescstring目标描述文本,会在检定提示中显示(如"开锁难度")
diceADVDice | DiceExpression骰子表达式,如 "d20""2d6""3d8+2"
modifier{ name: string; value: () => number }[]修正值列表,每个修正项包含名称和取值函数
onSuccess() => VlAndAsync<void>检定成功时的回调函数
onFail() => VlAndAsync<void>检定失败时的回调函数

插槽属性

ACheck#success#fail 插槽均为作用域插槽,会向插槽内容暴露检定的详细结果:

插槽属性类型说明
natnumber原始掷骰值(natural roll),即修正前的裸骰结果
dirtynumber最终检定值(dirty roll),即原始值加上所有修正后的结果

使用示例:

xml
<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 的条件:

ts
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() 本身会:

  1. 掷骰并计算修正。
  2. 在屏幕上显示详细的检定提示信息(骰子类型、原始点数、修正项、最终结果)。
  3. 执行 onSuccess / onFail 回调。
  4. 返回 { res: boolean, nat: number, dirty: number }——成功与否、原始值、最终值。

方式二:使用 Dialog 级别的 check 属性

你也可以将 check 直接配置在 ADialogAChoice 上,这样引擎会在对话脚本执行完毕后自动进行检定:

ts
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 的各个特性:

xml
<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 等价

ts
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 本质上是对 AIfAElseAdv.check 的封装。它在内部:

  1. 调用 Adv.check({ ... }) 执行完整的检定流程:
    • 掷骰得到原始点数(nat)。
    • 遍历 modifier 列表,累加所有修正值,得到最终点数(dirty)。
    • 将最终点数与目标值比较(根据游戏配置的判定模式)。
    • 在屏幕上显示详细的检定提示信息,包括骰子类型、原始点数、各项修正和最终结果。
    • 返回 { res: boolean, nat: number, dirty: number }
  2. natdirty 通过作用域插槽传递给 #success / #fail 插槽内容。
  3. 根据检定结果:
    • 成功 → 渲染 #success 插槽的内容,执行 onSuccess 回调。
    • 失败 → 渲染 #fail 插槽的内容,执行 onFail 回调。
  4. 同时触发 check 事件,将 (res, nat, dirty) 发送给父组件。