在本指南中,我们将介绍如何开始使用故事编辑器(也称为 Osiris 编辑器),包括如何构建和重新加载你的脚本、处理构建错误以及进行基本调试。在开始本指南之前,我们建议阅读 Introduction to Osiris 以了解 Osiris 的工作原理并了解目标和数据库等概念。

打开Story Editor

在编辑器的主工具栏中,点击书本图标打开故事编辑器。或者,如果视口被选中,你可以使用键盘快捷键 Ctrl+X

故事编辑器是一个用于创建和修改故事目标的工具。

创建一个脚本文件

让我们首先了解用于组织和加载脚本的系统。

故事编辑器左侧的面板是 goal hierarchy。该面板显示了在博德之门3中使用的所有目标以及我们为它们设置的结构。注意列表中的一些目标是可折叠的 - 这意味着它们有子目标。

在放置目标之前,需要理解几个重要规则。

首先,goals are executed from top to bottom。**这是 Osiris 的整体规则,不仅仅适用于目标。例如,在上面的截图中,GLO_HagCombatStates 将在 GLO_HagDoubles 之前执行,而后者又将在 GLO_HagMaskedVictims 之前执行,依此类推。

其次,parent goals execute first,父目标只有在完成后才会激活其子目标。例如,父目标 GLO_Hag 将首先执行,只有在它以”GoalCompleted”事件完成后,才会激活从 GLO_HagCombatStates 开始的所有子目标。我们稍后会深入讨论”GoalCompleted”事件。

添加新目标时,选择层级中的正确位置很重要。

示例

创造一个喜欢偷苹果的狗头人。首先,我们需要找到下城何时实际加载并准备就绪。在这种情况下,就是 Act3b

ModWrapper_Gustav 包含了博德之门3的所有任务逻辑 - 如果是出现在你日志中的任务,它应该在这里。系统性内容(如犯罪)、成就、法术、存档修补和教程都在此目标之外。

为确保此目标仅在玩家到达下城时才开始,我们要将其作为 Act3b 的子目标。右键点击 Act3b 目标并选择 Add New Sub Item

这会打开 New Script 对话框。选择要添加此新脚本的mod项目,并为其命名。

作为一个大致的经验法则,我们的命名约定通常遵循 Act_Region_Situation 结构。

按照这个指导原则,我们将把这个假设的新脚本命名为 Act3b_LOW_Applesnatcher。在这个例子中,mod项目名称是 NewScript

这在 Act3b 下创建了新目标,并在右侧打开新脚本。

Sections

INITKB** 和 EXIT。这些是我们在 Introduction to Osiris 中讨论过的部分。

INIT 是初始化字段,当目标激活时只运行一次。这用于你希望在目标开始时建立的脚本,比如设置角色。

KB 是脚本的主体部分。这里的脚本在目标处于活动状态时被评估,几乎涵盖了你的情境或mod需要发生的所有事情。在我们的 Act3b_LOW_Applesnatcher 示例中,如果我们想让 Applesnatcher 对玩家向他扔苹果做出反应,这就是我们编写相关脚本的地方:Applesnatcher 监听这样的事件,并对这样的事件做出反应。

EXIT 包含仅在调用 GoalCompleted 时执行的逻辑。一旦 EXIT 部分运行,你的 KB 部分的逻辑将不再被游戏评估。在 EXIT 部分最常见的是像 SysClear("NameOfDatabaseGoesHere", Number) 这样的脚本,它会清理我们不再需要的数据库(DBs)。

故事编辑器底部还有一个 Errors 面板。这是一个调试字段,会告诉你构建脚本时遇到的任何错误。请注意,这会报告每个目标文件的错误,不仅仅是你正在处理的文件!我们稍后会在本指南中深入讨论调试。

创建并重载你的脚本

Generate Definitions, Build and Reload

编写完脚本后,就该构建它了。通过构建故事,游戏将能够评估和执行你的脚本。

使用 Generate Definitions, Build and Reload 选项(Ctrl+F7)可以轻松全面地构建和重新加载故事,同时更容易找到你创建的任何新对象。

一旦完成且没有任何错误,你就可以在游戏中测试你的脚本了。但是,如果出现任何警告,你需要先修复它们。你可以在本指南的 Common Build Errors and Warnings 部分找到关于常见错误的更多信息。


Advanced Build Options

正如你可能注意到的,还有多种其他构建故事的选项。

Build

这会编译你的脚本,使游戏能够读取和评估它。

Reload Story

这会重新加载所有目标文件并在编辑器中执行它们的 INIT 部分。请记住,它只重新加载脚本,不会将交互式物品、角色等恢复到之前的位置。有关如何实现这一点的更多详细信息,请参见 Reloading Script 部分。

Generate Definitions

这会生成和更新定义。实际上,这允许 Osiris 知道任何新的对象、QRY、PROC 和 DB 以进行自动完成。它还会更新保持相同 GUID 的任何对象的技术名称。当你的脚本还没有准备好构建时,这很有用。


Reloading Script

Option 1: Reload Level and Story

主编辑器中的 Reload Level and StoryCtrl+F8)不仅重新加载你的脚本,还重新加载当前关卡中的对象。这在测试时很有帮助 - 例如,如果角色已被击败或物品已被销毁。重新加载关卡会将所有对象恢复到原始设置,除了你角色背包中的物品。

警告:重新加载关卡比仅重新加载剧情需要显著更长的时间。

Option 2: Reload Story

如果你没有对脚本进行任何更改,并且不需要重新加载关卡,你可以通过选择仅重新加载故事来节省一些时间。在故事编辑器中选择 Reload StoryF8)选项。

常见的构建错误和警告

本节将指导你如何修复一些常见的构建错误和警告。

请注意,本节中的示例代码片段专门用于演示错误。它们不是编写良好的 Osiris 代码的示例。

Conflict with function definition

如果你看到这样的错误,意味着故事编辑器被告知一个变量有多种类型。在上面的示例中,FlagSet 事件中的 _Player 变量作为 GUIDSTRING 类型发送,但我们检查的 DB_Players 数据库包含 CHARACTER 类型的变量。幸运的是,修复这些很容易。

我们可以通过在变量名前添加预期类型的 () 来在故事编辑器中重新转换变量类型。在这种情况下,我们只需将 DB_Players 检查更改为 DB_Players((CHARACTER)_Player)

如果我们再次构建,错误应该就消失了。

我们也可以通过在初始 FlagSet 行中重新转换变量来实现相同的结果。这两种方法都是正确的,但最好避免在同一代码块中多次重新转换变量的类型。


Database checked but never defined

如果你在脚本的 KB 部分留下了对 DB 的引用,但从未在脚本的 INIT 部分或作为 THEN 块的一部分定义 DB,就会发生这种情况。删除检查或定义 DB 以修复错误。


Parameter X is an unbound variable

当你命名了一个变量但 Osiris 找不到它的任何上下文时,就会发生这种情况。

通常,这发生在你将某些逻辑从一个区域复制/粘贴到另一个区域时,忘记更新变量名以与新逻辑保持一致。在上面的示例中,故事编辑器能够将 _Player 绑定到一个值,因为它从 StatusRemoved 事件接收到该值,但 _Character 没有绑定到任何内容,所以 IsOnStage 查询不知道应该检查谁。

在这种情况下,由于我们希望 IsOnStage 检查状态被移除的同一个角色,我们只需要用 _Player 替换未绑定的 _Character,使变量名匹配,脚本就应该按预期工作。


Auto-define Osiris query failed: type of parameter X unknown

当你尝试检查 DB 中存储的值多于它存储的值时,就会发生这种情况。

如果我们看上面的示例,我们首先将 DB_BuffedPlayers 定义为包含单个值:_Player。但是,在下一个代码块中,我们在同一个 DB 中查找两条信息:_Player_Duration。由于 DB 从未被定义为包含单个条目的 2 个值,构建失败。


Could not find any complete/correct definition of Osiris User Query/Procedure

与上面的错误类似,如果你尝试向查询或过程添加比它配置处理的更多的变量,就会发生这种情况。

在这个示例中,IsOnStage 查询只配置接受 2 个变量,一个 GUIDTSTRING 和一个 INTEGER。当我们尝试添加额外参数时,在这种情况下是 _Level,故事编辑器不知道如何处理额外参数,无法构建。

可以编写自己的查询,使其能够接受不同数量的参数。

如果你的查询/过程名称有拼写错误,也可能抛出此错误,如下所示:

在上面的示例中,PROC_CharacterMoveTo 被错误拼写为 PROC_CharcterMoveTo

通常,拼写错误在故事编辑器中很容易发现。正确拼写的查询会有砖红色的名称,过程会有亮绿色的名称。在上面的示例中,一旦拼写被修正,过程名称立即变成绿色,故事成功构建。


Conflict with function definition: parameter X type mismatch

当你传递正确数量的值给查询或过程,但它们是错误的数据类型时,就会发生这种情况。在这种情况下,IsOnStage 期望在第一个槽中看到一个 GUIDSTRING,但它被传递了一个 INTEGER。传递正确的数据类型将解决错误。

用Osiris日志进行基本调试

即使脚本成功构建,一旦你开始在游戏中测试它,你可能会发现错误。

有几个不同的文件可以帮助你调试:

  • story.div
  • Osiris Log(也称为故事日志)

Note: 当你第一次尝试打开上述文件之一时,系统会要求你选择用于打开它的程序。所有文件都是纯文本,所以随意选择你喜欢的文本编辑器。我们推荐 Notepad++ 或 Visual Studio Code。

Story.div

这个文件包含所有当前的 Osiris 目标,编译在一个单独的文件中,按照之前解释的相同结构:从上到下,按照它们在故事编辑器左侧边栏中出现的顺序。

你可以通过故事编辑器的 File > Open Story.div 打开此文件,或在 ...\Data\Mods\[[your mod name]]\Story 中查找 story.div

文件的一般结构是:

  • Goal(goal_number).Title(name_of_goal): 特定编译目标的开始。
  • INIT: 目标的初始化部分。
  • KB: 包含目标规则的部分。
  • END: 目标关闭时执行的逻辑。

Osiris Log

Osiris 日志包含从你加载关卡、加载存档或在编辑器中重新加载故事那一刻起的完整 Osiris 跟踪。这是你在调试脚本时会花费最多时间的地方。

这个文件包含所有事件、所有对 PROC 和 QRY 的调用,以及在当前游戏会话中每次向 DB 添加或从 DB 移除条目的记录。

它位于你的 Steam 或 GOG 安装文件夹中的工具包目录下(例如,...\steamapps\common\Baldurs Gate 3 Toolkit\),命名为 osirislog.[date_of_creation].log。编辑器将始终保存最后 11 个日志,所以不用担心重新加载故事时会丢失日志。

另一种打开方式是通过故事编辑器中的 File > Open Story Log

Osiris 日志比 story.div 更难阅读。每当发生某事(触发事件或从 DB 添加或移除条目),Osiris 日志都会记录它并开始相应地执行规则。

跟踪将被保存在日志文件中,供你检查发生了什么,看看哪里失败了,或者 Osiris 中当前正在运行什么。

Events:>>> event 开头的行对应游戏触发的事件,例如设置或清除标志、施放法术或某人进入触发器。

Queries: exec [DIV/Osi user query] 表示何时检查了 QRY。

  • DIV 查询对应引擎查询,而 Osi user 是针对 Osiris 定义的查询。如果有参数,它们将始终包含被调用时的参数。
  • 带有 [out]前缀的参数用于从引擎返回参数。当查询失败时,它的行末尾将明确显示 ***** Query Failed! *****。
    • 注意:即使结果不是预期的,成功返回某些内容的查询也不会返回 Query Failed。在下面的示例中,我们正在检查武器是否有一个活动状态。查询本身成功并返回了结果,但结果是武器没有活动状态,因此什么都不做。
1
2
exec [DIV query] HasActiveStatus( WPN_GOB_Shortbow_A_0_6f92d9b1-8d54-346f-e477-c1a9abbf80d3, "WYR_GORTASH_GRENADE_DETONATE", [out] 1 )
Query returns: HasActiveStatus( WPN_GOB_Shortbow_A_0_6f92d9b1-8d54-346f-e477-c1a9abbf80d3, "WYR_GORTASH_GRENADE_DETONATE", 0 )

Procedures: 对过程的调用,后面跟着其他查询、DB 循环或 RuleActionPart。这些行以 [Osiris procedure call] 结尾。

当你通过 Osiris 日志跟踪每个规则的执行时,你会看到许多行以 X----> 开头。

X 代表你在跟踪中的深度。在给定规则中嵌套的 PROC 和 QRY 越多,这个数字可能最终会越大。要小心你有多少个,因为它会使调试更困难!