《骇游侠探》如何用虚幻整合复杂的分支路线

Anshar Studios程序员Przemysław Osipiuk

Przemysław Osipiuk

Przemysław在《骇游侠探》开发团队中担任程序员已有一年半,他负责创建游戏玩法和为设计团队制作各种工具。他开发出的解决方案能使设计师们的工作更轻松。在闲暇时间,Przemek喜欢通过钻研新技术和拼装新的乐高积木模组来保持创造力。
《骇游侠探(Gamedec)》是一款非战斗型的单人等距视角赛博朋克风RPG。你是一名骇游侠探——在虚拟世界中调查罪案的私家侦探。你做出的决定将会改变你的角色特质,并影响游戏的进程,但是案件中往往充满两难的抉择,少有黑白分明的时候。你的一切选择将最终定义你是什么样的人。
 

《骇游侠探》是一个基于庞杂对话和众多决定分支的游戏。一个决定可能开启一条解决问题的路线,但也可能封闭另一条路线。创作这样复杂的游戏需要特殊的工具。我们使用了虚幻引擎与Articy的组合,后者是将游戏编剧、规划和内容管理包容在一个明晰的可视化工具中的软件。
图片由Anshar Studios提供
这是一个《骇游侠探》中的分支路线示例。
目标
为了使《骇游侠探》的场景更为生动,设计师不仅需要通过丰富的对话影响它,还需要通过NPC活动、场景变化、氛围音乐等手段来影响它。需要让玩家看到他们是在游戏中真正地进行交互,而不仅仅是点击浏览大段大段的文字。

在一开始,《骇游侠探》制作组就需要解决一个难题。为了基于玩家的选择影响世界场景,他们需要依靠一种独特的序列,后者代表一种带有技术性名称的Articy对象。

这种方法有多个缺点。在查看关卡蓝图时,我们没法一眼就看出特定的技术性名称代表的是哪个对话段,它是在哪里被调用的,作用又是什么。我们必须打开Articy,查找有那个技术性名称的节点,并分析分支来了解游戏中应该发生什么。此外,仅使用Articy工作的团队成员并不清楚哪些节点会在虚幻引擎中触发操作。
图片由Anshar Studios提供
我们最早根据对话选择来执行事件的方法就是基于这样的开关。
图片由Anshar Studios提供
这里是《骇游侠探》中一个分支路线的示例。
第二个显而易见的问题是,当设计师选择另一个应该在虚幻引擎中触发事件的节点时,需要在关卡蓝图中更改技术性名称。创作一款游戏,特别是像这样有大量对话和交互的游戏,是非常强调迭代的过程。我们需要在多个地方同时进行更改,这提高了出错的风险,而一旦出错就可能耗费许多时间。
解决方法
为了克服这些问题,Articy提供了一个非常方便的机制,允许执行我们的函数。在指令块中插入函数调用就能消除团队需要处理的上述问题。
图片由Anshar Studios提供
一个指令块就能解决这个问题。
问题的解决方法就是指令块。这种功能的第一个优点是,设计师能够确切知道给定的活动会在什么时候执行。值得一提的是,更改指令块的位置并不需要虚幻引擎中的更改;我们可以在对话中的另一个位置调用已经定义的函数。

使用你自己的函数也会带来显著的好处。游戏性程序员再也不必在蓝图级别定义操作,哪怕是最小的操作。我们创建了许多函数,使大家能够在创建对话时更方便地从Articy层面控制游戏。

例如,假如我们想让一个和我们对话的NPC带我们去一个对话中谈到的特定地点,我们可以使用两个GoTo和FollowBy指令,给它们传递位置、行走速度等必要参数即可。
图片由Anshar Studios提供
这段过场就是由对话选择调用的。
实现
当然,Articy并不知道Locations_TheaterDoor之类的东西位于何处,Characters_Admin又是哪个NPC。我们必须在虚幻引擎中定义这些。为此我们创建了一个组件,让我们能够将Actor注册到系统。
图片由Anshar Studios提供
我们将上图中的剧院大门注册为Locations_TheaterDoor。
这个系统简单明了。当关卡启动时,所有带有这个组件的Actor都会注册到一个对象,该对象将这些数据存储在字典中。键就是给定的辨识符,值是对Actor的引用。

好,那么Articy和UE4是怎样绑定外观的呢?这些函数又是如何工作的?首先,如果我们把函数放进指令块,从Articy将数据导入到虚幻引擎的插件就会生成一个接口:UserMethodProvider,它包含在Articy中使用的所有函数的定义。
图片由Anshar Studios提供
这个列表显示了生成的接口的一个片段。
为了确保生成的接口始终包含一组可用的用户创建函数,我们创建了一个Articy流分段,其中包含所有已定义的指令及其参数。它始终不会执行,但是会导出,所以即使函数在任何地方都没有使用过,我们也能确定我们的所有函数都在这个接口中。这样一来,我们就能避免后面的问题。这有点像对虚幻引擎的API声明。
图片由Anshar Studios提供
这里是一个包含一组所有可用函数的流片段。
虚幻引擎的Articy插件包含一个UAArticyFlowPlayer类。它会在对话树中导航,并允许探索对话分支。在这个类中你可以找到一个函数,用来检查Actor或任何组件是否实现了上述接口。一般来说,这就行了。现在我们可以创建一个实现UserMethodProvider接口的组件,来定义函数的行为。

以《骇游侠探》为例,当这个接口在不断添加功能的压力下大大成长以后,我们就忍不住把它分解成了若干较小的接口。我们创建了一些只包含一组函数的小接口。然后我们创建一个实现基本接口的façade,把函数的执行委托给它们各自的对象。通过这个解决方案,我们得以将系统分解成小块,并维持整体的逻辑完整性。

此外,我们可以注册多个类来响应一个特定事件。我们遇到的问题是同一个函数的反复执行。这是树的遍历操作的特异性造成的。FlowPlayer在实际运行特定的对话块之前会分析所有可能的路径——这牵涉到一些需要达成的条件,根据条件确定我们是否能进入一个特定分支,从而显示一系列可用选项。

但是,Articy有一个叫做影子模式的功能可以在这里提供帮助。此时,Articy要分析树来检查哪些分支可用,它会在这个特殊的(影子)模式下执行分析,不会影响任何变量和游戏性。当我们通过一个给定的节点,我们就不处于影子模式了,直到这时候才应该执行我们的函数。这是我们应该特别注意的情况。
图片由Anshar Studios提供
问题
在Articy中使用你自己的函数也会牵扯到一个特定的问题——现有的Articy没有给我们提供任何机制来对它自己的函数执行语法检查。设计师得不到关于所用函数是否存在的信息,也不知道传递给它的参数是否与虚幻引擎中的实现一致。这一度成为一大难题。

在有些情况下,我们要花几个小时来分析Articy中的哪个更改导致了日常构建失败。

有时候是函数名称中的简单拼写错误,参数的类型错误或数量不正确,或者是能够通过分析日志快速找到的错误。有一次是代码块中的最后一个函数中少了一个分号。通常这不会成为问题,因为导入器会在函数末尾添加缺失的分号。但是在这个例子中,我们忘记了给函数添加分号,而且在函数后面添加了一个注释块。

问题指令示例:

CallEvent("OpenHiddenRoom")// Open the secret room

我们无法通过分析日志来快速找出这个问题,因为错误指向了一个完全不同的地方。因此,我们不得不千辛万苦地分析做过的所有更改,把它们一一注释掉来查找错误。当我们忘记给最后一个函数末尾添加分号,又给它添加了注释块,然后添加了一个插件从Articy导入数据,引擎中就出现了Bug。这导致返回类型从空变为对象,因此我们就得到了编译错误。

那一天我们还做了一个决定,就是要设法消除或至少大大减轻这类情况再次出现的风险。不幸的是,Articy不允许我们为用户创建的函数定义标头,事实上这就导致设计师无法进行语法检查,从而可能犯错。

但是Articy允许用户创建自己的插件。因此我们创建了一个插件,它会分析所有指令块来查找函数,然后检查它们是否与其定义兼容。我们为团队中每一个经常使用Articy的成员安装了这个插件,从那以后,我们的日常构建中就再也没有出过那个问题。
图片由Anshar Studios提供
结论
在游戏开发中,每一个决定都很重要,比如你选择的软件、工具链和软件架构。选择Articy作为创建交互的工具,并且通过用户创建的函数和我们自己的插件扩展它,对我们来说是正确的选择。因为能够为虚幻引擎创建编辑器实用程序窗口小部件,所以我们能快速开发工具来调试Articy与UE4之间的交互,这对我们的游戏开发至关重要。及时作出关于简化工作的决定为团队带来了显而易见的效益,具体说来就是交互迭代加快和原型设计速度提高,使我们不需要程序员的介入就能够引导玩家。

使用蓝图可以快速设计游戏原型,而在Articy中可以从创建的对话直接控制游戏性流程,因此如果要创作像《骇游侠探》这样由选择来推动的游戏,它们是一对绝妙的搭档。这使得设计师可以快速检测对项目所做的任何更改是否能以正确的方式来展现剧情。

在设计方面,任务设计师不需要团队中其他成员(例如程序员或图形设计师)的帮助,就能在虚幻引擎中创作交互骨架。他们可以自己做这类骨架,对它进行试验和迭代。这真是妙不可言,为我们省去了大量很耗时间的沟通。Articy与虚幻引擎结合,提供了许多可供我们调整的机制和工具,从而尽量满足我们开发《骇游侠探》时的需求。

    立即获取虚幻引擎!

    获取全球最开放、最先进的创作工具。
    虚幻引擎包罗万象,并提供完整的源代码访问权限,开箱即用,诚意十足。