BD3Modding-Osiris编辑器基础
在本指南中,我们将介绍如何开始使用故事编辑器(也称为 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
INIT、KB** 和 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 Story(Ctrl+F8
)不仅重新加载你的脚本,还重新加载当前关卡中的对象。这在测试时很有帮助 - 例如,如果角色已被击败或物品已被销毁。重新加载关卡会将所有对象恢复到原始设置,除了你角色背包中的物品。
警告:重新加载关卡比仅重新加载剧情需要显著更长的时间。
Option 2: Reload Story
如果你没有对脚本进行任何更改,并且不需要重新加载关卡,你可以通过选择仅重新加载故事来节省一些时间。在故事编辑器中选择 Reload Story(F8
)选项。
常见的构建错误和警告
本节将指导你如何修复一些常见的构建错误和警告。
请注意,本节中的示例代码片段专门用于演示错误。它们不是编写良好的 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 | exec [DIV query] HasActiveStatus( WPN_GOB_Shortbow_A_0_6f92d9b1-8d54-346f-e477-c1a9abbf80d3, "WYR_GORTASH_GRENADE_DETONATE", [out] 1 ) |
Procedures: 对过程的调用,后面跟着其他查询、DB 循环或 RuleActionPart
。这些行以 [Osiris procedure call] 结尾。
当你通过 Osiris 日志跟踪每个规则的执行时,你会看到许多行以 X---->
开头。
X
代表你在跟踪中的深度。在给定规则中嵌套的 PROC 和 QRY 越多,这个数字可能最终会越大。要小心你有多少个,因为它会使调试更困难!