场景、对话与结局
场景
在文字冒险游戏中,故事总是发生在一个又一个的场景里。场景就像舞台,定义了玩家当前所处的环境,并为后续的剧情发展提供背景。
建议你在 script 文件夹中新建一个专门用来存放场景的文件,例如 scene.ts。当然,你也可以把场景、对话等内容写在同一个文件里,这完全取决于你的组织习惯。
创建一个场景的代码如下:
Adv.appendScene('main', {
name: '家中',
next: 'main-dialog',
});让我们来逐行理解这段代码:
Adv.appendScene这个函数用来向游戏中注册一个新场景。事实上,本项目的所有 API 都挂载在ADVMaker对象上,你将在后续的创作中频繁与它打交道。- 第一个参数
'main'是场景的唯一标识符(ID)。这个 ID 非常重要,你在任何地方引用场景时都要用到它。请务必保证每个场景的 ID 是独一无二的,不能与任何其他场景或对话的 ID 重复,否则程序会报错。 ID 不应该以__开头。 - 第二个参数 是一个配置对象,里面包含了场景的各种属性:
name:场景显示给玩家的名称,例如'家中'或'阴暗的森林'。它会出现在界面上方的标题栏,帮助玩家理解自己所处的位置。next:进入该场景后立即执行的动作。简单来说,就是告诉游戏“接下来该干什么”。如果不填,则默认为null,游戏流程会暂时停止,等待你手动调用其他指令。
除了上面两个必填属性外,场景还支持几个非常实用的可选属性:
onEnter:一个回调函数,类型为() => void。这个函数会在玩家进入场景时被触发。你可以用它来记录日志、播放背景音乐、改变游戏状态,或者执行任何你想一次性完成的操作。onLeave:一个回调函数,类型为() => void。这个函数会在玩家离开场景时被触发。
例如:
Adv.appendScene('forest', {
name: '幽暗森林',
next: 'forest-dialog',
onEnter: () => {
// 当玩家进入森林时,在控制台输出一条信息
console.log('玩家已进入森林区域');
}
});你可以使用链式调用方法。
Adv.appendScene('forest', {
name: '幽暗森林',
onEnter: () => {
// 当玩家进入森林时,在控制台输出一条信息
console.log('玩家已进入森林区域');
}
})
.next('forest-dialog')
.build(); // 这个可选函数会返回整个场景对象,永远可以加在最后面注意:链式调用设置的 next 会覆盖参数中设置的 next。
对话
对话是叙事的主要手段。一段对话可以包含多句台词,并支持丰富的文本样式,让你的故事更加生动。
同样地,建议在 script 文件夹中创建一个 dialog.ts 文件来存放对话。
下面是创建一个对话的示例:
Adv.appendDialog('main-dialog', {
script: [
'你好!',
'我是你的向导。',
'接下来,让我们一同<b>冒险</b>吧',
"Let's go!"
],
next: "End"
});Adv.appendDialog用于注册一个新对话。- 第一个参数
'main-dialog'是对话的唯一标识符(ID),同样需要遵守“全局唯一”的原则。 - 第二个参数 配置对象包含:
script:台词内容。- 它可以是一个字符串数组,程序会先显示第一句,玩家每次点击屏幕后,依次显示下一句,直到全部播完。这种方式非常适合表现角色的内心独白或多轮叙述。
- 每句台词不仅可以是纯文本,还可以是一段HTML代码。例如,你可以让文字变色或加粗:
'这里有一只<span style="color:red">红色的钥匙</span>'。 - 如果你熟悉 Vue ,甚至可以直接传入 VNode 对象来实现复杂的交互组件。
- 每句台词不仅可以是纯文本,还可以是一段HTML代码。例如,你可以让文字变色或加粗:
- 也可以是一个异步函数,此函数运行完后不会等待用户点击屏幕,会直接打印下一条。
- 或者是一个选项数组(
ADVChoice[])。使用new ADVChoice(obj: ADVUserChoice)来包装。 null,什么也不会干,但会阻塞直到玩家点击屏幕。
- 它可以是一个字符串数组,程序会先显示第一句,玩家每次点击屏幕后,依次显示下一句,直到全部播完。这种方式非常适合表现角色的内心独白或多轮叙述。
next:台词全部播放完毕后执行的动作。check:检定的配置,检定会在 next 生效之前发生。in:填写一个场景的 id,表示该对话发生在的场景。若对话开始时不在该场景,则会自动移动到该场景中。若不填写则不会移动。
对话同样支持几个可选的回调:
onStart:在对话开始播放之前执行,类型为() => void。你可以在这里调整角色的属性值、显示提示信息等。onFinish:一个回调函数,类型为() => void。这个函数会在玩家结束对话时被触发。
此外,如果你不想触发完整的对话,只是想在游戏过程中临时插入一条消息,可以使用更轻量级的函数:
Adv.print('你注意到墙上有道裂缝...');这种方式下,消息会直接出现在聊天记录中,且不会打断当前的游戏流程。
如果你这么写,可能会有警告:从 print 返回的 Promise 被忽略。
这个警告不会影响程序运行,但你可以通过增加 void 标识符来取消警告。
void Adv.print('你注意到墙上有道裂缝...');以下异步方式,这种方式下,消息会直接出现在聊天记录中,并会阻塞当前程序直到用户点击屏幕。(如果是异步函数,无论如何都不会阻塞)
await Adv.print('你注意到墙上有道裂缝...');请注意,只有 event(以 on 开头的字段)支持异步操作,且除了 event 之外的任何函数都不保证只执行1次,不应该有副作用。
同样,对话支持链式调用:
Adv.appendDialog('main-dialog')
.say('你好!') // 会加在 script 数组的末尾
.say('我是你的向导。')
.say('接下来,让我们一同<b>冒险</b>吧')
.say("Let's go!")
.next('End')
.build(); // 这个可选函数会返回整个对话对象,永远可以加在最后面链式调用会覆盖参数中配置的 next,但不会覆盖 script,此外,在 next 函数使用后,就不能再连接 say 了。
游戏的结局
每个故事都需要一个终点。ADVMaker 提供了一种优雅的方式来结束游戏:
Adv.end("故事结束")end 函数接受一个字符串参数,这个字符串会作为“死亡原因”或“结局描述”显示在最终的 Game Over 画面上。调用该函数后,它会返回一个特殊的动作,你可以直接把这个返回值赋给任意 next 属性。例如: