跳到主要内容

加载和运行 Yarn 叙事脚本

  欢迎来到 YarnRunner 功能库使用教程。在本教程中,我们将指导您如何加载和运行您在前一教程中编写的 Yarn 叙事脚本。

1. 初始化 YarnRunner

  首先确保您已正确地导入所有必需的模块。以下是本教程示例中我们需要的模块:

init.lua
local Content <const> = require("Content")
local Path <const> = require("Path")
local Node <const> = require("Node")
local Director <const> = require("Director")
local YarnRunner <const> = require("YarnRunner")

  为了确保我们可以找到 Yarn 脚本,我们需要设置正确的搜索路径。如果 Yarn 脚本和程序模块在同一个目录下时我们可以增加如下代码:

init.lua
local path = Path:getScriptPath(...)
Content:insertSearchPath(1, path)

  接下来,假设我们要加载的 Yarn 文件名为 "tutorial.yarn",起始节点的标题名称为 "Start",则编写如下代码:

init.lua
local runner = YarnRunner("tutorial.yarn", "Start")

2. 执行和展示叙事内容

  我们定义了一个 advance 函数,该函数可以读取并展示 Yarn 脚本中的文本内容或选项。根据叙事的内容,它还可以展示角色的名字:

init.lua
-- advance 函数的参数是一个可选的整数,代表玩家选择的选项索引。
-- 当第一次调用此函数或在不需要选择时,我们将传递 nil
local function advance(option)

-- 函数首先调用 runner:advance(option) 来获取 Yarn 脚本的下一部分内容。
-- 这将返回两个值:一个动作类型和一个结果。
local action, result = runner:advance(option)

-- 根据动作类型,选择如何处理结果。
if action == "Text" then

-- 如果动作是 "Text",则结果会是一个 TextResult 对象,
-- 其中包含文本内容和任何相关的标记(例如角色名)。
-- 检查标记,提取角色名(如果存在)并打印出文本内容。
local characterName = ""
local marks = result.marks
if marks then
for i = 1, #marks do
local mark = marks[i]
if mark.name == "char" then
characterName = mark.attrs.name .. ": "
end
end
end
print(characterName .. result.text)

elseif action == "Option" then

-- 如果动作是 "Option",则结果会是一个 OptionResult 对象,
-- 其中包含了一个或多个选项。函数会遍历这些选项并打印它们,
-- 玩家稍后可以选择它们。
for i, op in ipairs(result) do
if op then
print("[" .. tostring(i) .. "]: " .. op.text)
end
end

else

-- 对于其他动作(例如错误),函数将直接打印结果。
print(result)

end
end

3. 启动叙事

  要启动叙事,我们只需调用 advance 函数,不带任何参数:

init.lua
advance()

4. 响应用户输入

  为了能够让玩家与叙事互动,我们需要一个节点来捕获和响应用户的输入。我们创建一个节点,并为其指定一个名为 "go" 的信号槽。当这个信号被触发时,它将再次调用 advance 函数,并传递玩家选择的选项:

init.lua
local node = Node()
node:gslot("go", function(option)
advance(option)
end)
node:addTo(Director.entry)

  这里只是为了能快速进行测试而注册了一个全局的事件监听器。在实际的游戏开发中会需要通过编写UI交互的逻辑来完成使用。有了这个名叫“go”的全局事件监听器后。我们就可以通过打开 Dora SSR 的控制台界面,在下面的命令行中输入 emit 'go' 来继续推进对话,输入 emit 'go', 1 来选择对话分支完成交互式的测试运行了。

5. 添加自定义命令和状态

  您可以通过给 YarnRunner 传递 commandstate 参数来添加自定义命令和初始状态的变量。command 参数是一个包含可以执行命令的回调函数的 Lua 表,state 参数是一个包含预定义变量的表格。以下是一个示例:

init.lua
local runner = YarnRunner("tutorial.yarn", "Start", {
playerScore = 100
}, {
print = print
})

  然后在 Yarn 脚本中,您就可以通过使用 <<set $变量名 = 表达式>> 命令来改变初始变量的值,并通过自定义的命令 <<print $变量名>> 命令来打印变量的值:

测试对话节点
<<set $playerScore = $playerScore + 200>>
<<print $playerScore>>

6. 调试和错误排查

Yarn 脚本通常会在剧情仍然变化时反复修改。Dora SSR 提供了两条快速反馈路径:在 Web IDE 中编辑时进行语法检查,以及使用 Yarn Tester 运行保存后的 .yarn 文件。

6.1 在 Web IDE 中检查 Yarn 文件

以文本方式编辑 .yarn 文件时,Web IDE 会使用 Yarn 编译器检查文件,并在编辑器中标记语法错误。错误结果包含消息、行号、列号和节点名,因此标记可以回到源文本中的对应位置。

使用可视化 Yarn 编辑器时,编辑器会按节点分别检查每个 node body。如果某个节点包含无效的 Yarn 语法,检查结果会在编译器错误前带上节点标题,方便定位是哪一个节点出错。

6.2 使用 Yarn Tester 运行文件

保存 .yarn 文件后,可以从 Dora SSR 的开发工具中打开 Yarn Tester。测试器会扫描可写项目路径下所有以 .yarn 结尾的文件。如果测试器已经打开后才新增 Yarn 文件,请重新加载测试器,让文件列表重新扫描。

在 Yarn Tester 中:

  • 使用 Filter 缩小文件列表。
  • 使用 File 选择要运行的 .yarn 文件。
  • 修改并保存文件后,点击 Reload 重新创建 runner。
  • 测试器会从 Start 节点开始运行。
  • 文本会显示在滚动区域中,选项会显示为编号选择,命令会显示为 [command]: commandName args
  • Variables 区域会显示 runner 的状态。测试模式下,初始变量可以从 Yarn 文件开头的 // variables: 注释中读取。

6.3 常见错误现象

缺少节点:如果脚本跳转到不存在的节点,YarnRunner 会返回类似 node "MissingNode" is not exist 的错误。检查 <<jump ...>> 的目标,并确认文件里存在同名的 title: 节点。节点标题按名称匹配,拼写需要一致。

选项越界:如果当前只有两个选项,但代码调用了 runner:advance(3),runner 会返回 choice 3 is out of range。显示 "Option" 动作返回的选项列表,只把用户实际看到的编号传回 runner。

没有选项却传入选择:如果剧情正在显示普通文本,但代码传入了选择编号,runner 会返回 there is no option to choose。普通文本继续时调用不带参数的 advance();只有在 runner 返回 "Option" 后才传入数字。

需要选择却直接继续:如果剧情正在等待玩家选择,但代码调用了不带参数的 advance(),runner 会返回 required a choice to continue。此时应保持选项 UI,直到玩家选择一个可用分支。

脚本语法错误:无效 Yarn 语法会被编辑器检查发现,或在 Yarn Tester 重新加载文件时出现。在 Yarn Tester 中,加载失败会显示 failed to load file ...,后面跟着编译器错误。优先修复报错节点和报错行,保存文件后再点击 Reload

6.4 运行时错误行号如何映射回 Yarn 文本

有些错误会在节点已经编译完成、剧情运行过程中才发生。YarnRunner 内部会把每个 Yarn 节点正文编译为 Lua。生成的 Lua 代码会保留记录原始 Yarn 行号的注释。发生 Lua 运行时错误时,YarnRunner 会把 Lua 错误行号改写回 Yarn 节点中的行号,并返回如下形式的错误:

NodeTitle:line: message

例如,Start:5: attempt to call a nil value 表示 Start 节点正文中的第 5 行,不一定是整个 .yarn 文件的第 5 行。定位时先找到 title: Start,再从该节点的 --- 分隔线之后开始数行。Web IDE 对完整 .yarn 文件做检查时,也可能直接提供源文件行号、列号和节点名。

6.5 最小错误样例

下面的文件有两个问题:它跳转到了一个不存在的节点,并且只有两个选项。如果游戏代码之后尝试选择选项 3,就会发生选项越界。

broken.yarn
title: Start
---
你看到一扇锁住的门。
-> 打开它
<<jump OpenDoor>>
-> 离开
你退后了一步。
===

你可能会看到:

  • 选择选项 1 后:node "OpenDoor" is not exist
  • 如果代码对这个选项列表调用 advance(3)choice 3 is out of range

下面是修复后的版本:

fixed.yarn
title: Start
---
你看到一扇锁住的门。
-> 打开它
<<jump OpenDoor>>
-> 离开
你退后了一步。
===

title: OpenDoor
---
门发出轻响,慢慢打开了。
===

保存修复后的文件,在 Yarn Tester 中重新加载,再次选择选项 1。此时剧情应该会继续进入 OpenDoor 节点。

7. 结论

  现在,您已经设置好了所有必要的代码来加载和运行 Yarn 叙事脚本。您可以运行上述脚本来开始您的互动叙事,玩家可以通过选择选项来与叙事互动。祝您创作愉快!