Guerrilla高级程序员分享:《地平线零之曙光》的工具管线打造
【GameLook专稿,未经授权不得转载!】
GameLook报道/对游戏团队而言,做不擅长的品类很容易给研发带来比较大的挑战,尤其是对于3A游戏来说,往往意味着很多方面的改变。比如年销量破760万套的《地平线:零之曙光(Horizon Zero Dawn)》背后的开发商Guerrilla Games,在此之前就从未做过开放世界RPG。
在此前的GDC演讲中,Guerrilla Games高级工具程序员Dan Sumaili和首席工具程序员Sander van der Steen谈到了该工作室为新游戏打造工具管线遇到的难题和解决方法。
以下是GameLook听译的全部内容:
Dan Sumaili:
我叫Dan Sumaili,在Guerrilla Games担任工具程序员,今天将与同事Sander van der Steen一起谈谈我们是如何在《地平线:零之曙光》研发过程中重新打造工具管线的。
在《地平线:零之曙光》之前,我们做了FPS游戏《Killzone》,当我们决定从FPS游戏研发转向开放世界游戏的时候,这给我们的公司和研发理念都带来了很大挑战,用到的工具也没有幸免。
所以,今天的分享主要包括我们重新打造工具背后的原因,我们在制作的过程中是如何实现的,以及这给游戏研发带来了什么帮助。如果你的团队也在用之前留下来的技术和工具,并且每天都需要用到,那么今天的内容或许会给你带来一些启发。
《地平线:零之曙光》研发遇到的问题
首先我们会聊聊之前项目遗留的工具,重新打造的工具,还会提到3D编辑器以及其他工具,然后是我们对未来的计划。
工具问题
《地平线:零之曙光》刚开始研发的时候,工具是非常分散的,它们都是基于不同编程语言和框架打造。使用这些工具,我们的迭代时间比较长,比如一名游戏策划想要对某个功能作出调整,至少需要20-30分钟才能完成,坦白的说,团队成员对这些工具的抱怨很大,跟热门的Unity或者虚幻引擎等工具相比,差距很大。
简单说一下技术方面,在Guerrilla Games我们的intermediate plaintext format都是使用CoreText,它用于除了纹理和音频之外的所有游戏数据,在这些数据被用到游戏之前,我们通过转化的方式将其变为二进制数据。
这是我们之前主要使用的CoreText编辑器,可以看出非常的古老,它把内容变成2D图片,目标文件和注释文件的关联性通过不同的线条连接。
我们用它来创造和配置很多的游戏数据,然而在实际的数据编辑方面,它是非常低等级的,很多都是在研发《Killzone》比较忙碌的时候做出来的。你需要用很多时间去了解复杂的数据结构,而不是聚焦于创意过程。
这些都是用C#、WinForms以及(索尼)ATF格式写的,总而言之,这个工具非常复杂,即便是很小的改变都需要付出很多时间。因此,团队里的开发者几乎没有人喜欢这个工具。
另一个工具就是Maya,具体来说就是我们内部的Maya插件,它将我们的数据导入Maya,然后导出到CoreText,这个插件使用了内部引擎Decima的渲染器,以便能够看到这些资源在游戏里的视觉效果是什么。然而,它并不能运行完整的游戏主循环(Game Loop),意味着丢失了一些在游戏里存在的元素。
这个工具用C++和Python编写,而且大量使用了Maya API,考虑到CoreText编辑器,实际上只有少数几个人能够用,也只有很少人愿意用这个工具。
最后一个工具是状态机编辑器(State Machine Editor)SMED,主要是关卡策划们在使用。这个工具最初是技术策划做的创意原型,为了探索我们能否为一款游戏生成LUA脚本,不幸的是这次尝试并没有成功,还需要不断有一名技术策划进行维护,这个工具是用Python写的。
通过以上的介绍,我们可以发现这三个工具能够使用的人并不多,而且每个工具之间的交互也很难做到,因此这三个工具管线并不好用。
我们还有大量其他的工具在使用,但由于时间限制,我们没办法一一说明。
因为工具的原因,我们制作了游戏内UI框架Debug UI,它是在《Killzone:Shadow Fall》制作期间研发的,很多程序员觉得这个工具比较好用,并且在此基础上产生了很多的游戏内debug工具。
这是我们的游戏内GPU分析器(profiler),你可以看到每一帧的很多draw call,不同的采样器等很多信息,它还有一个外部视角,你把窗口放在场景的某个部位,就知道那个intermediate buffer对应最终某一帧。
游戏本身
在《Killzone》包括之前的研发中,我们没有做过RPG,也没见做过开放世界游戏。在《地平线:零之曙光》当中,我们还需要打造一些没做过的内容,比如任务、互动对话等,而且Guerrilla Games的工具程序员团队很小,在一个220人的团队里,我们只有4-6名全职工具程序员,因此必须高效率使用我们的时间和资源。加上之前谈到过的管线工具弊端,我们决定重新开始打造研发工具。
这时候我们开始制定一些工具研发目标,希望能打造对未来工具有利的框架,让我们的工具之间具有连贯性,Decima是我们的内部引擎,在打造新工具的时候,我们希望尽可能多地实现复用。我们还希望让新工具用起来尽可能简单,以便让其他团队接受,并最终帮助到更多的团队。
编辑器方面,我们希望打造适合团队成员需求的工具,比如针对特定方法打造Editing Contexts,以及可以复用的组件,这样在其他团队需要的时候就可以为他们快速打造新工具。我们还希望简化工具使用方法,让所有工具都保留在一个程序当中,也就是我们现在的Decima编辑器。
Decima是我们的内部引擎,最近用它研发的游戏包括《Killzone:Shadow Fall》、《Rigs》、《地平线:零之曙光》,以及小岛工作室的《死亡搁浅》。与大多数引擎不同的是,它被分为很多层,基础层面是为不同平台设计的OS,主要是为了让游戏更容易移植到不同平台,值得注意的是,出于研发目的,我们始终有完整的PC版本。
接下来是核心部分,包括渲染、光照、粒子等等,随后是用来做不同功能的游戏代码,我们将工具框架放在最顶层,以便它可以触达下方的所有东西。
这个引擎有很多优势,其中一个非常重要的优势就是优化程度很高的运行时类型识别(RTTI),这对于打造专门的编辑器是必要的。第二个优势是免费代码和Decima引擎库,为团队其它程序员降低了使用门槛。
这是现在使用的关卡编辑器
这是我们的Visual Script编辑器
这是对话编辑器
新编辑器的打造
这个时候,《Killzone》已经完成了研发,除了少数为它做DLC的成员之外,所有人都转向了《地平线:零之曙光》,这个项目的制作已经开始。尽管之前的工具有很多不便,但它的确是可行的,虽然效率比较低,研发人员已经熟悉了这些工具,因此尽管明知道需要改变,但他们的意愿并没有那么强烈。
我们并不知道打造新工具的结果如何,所以需要从很小的事情做起,这样才可以最小化研发风险,意味着在研发新工具的同时,还需要继续支持旧工具。
鉴于以上原因,我们决定从音频团队开始,这意味着我们最开始只需要满足小团队使用需求(4个人),而且他们的工作需要能够测试框架从CoreText编辑器到Decima编辑器的可行性。
我们的音频团队主要使用NodeGraph,这是一个高性能Visual Scripting语言,可以生成在目标运行的C++代码,它在《Shadow Fall》研发的时候使用率很高,意味着如果音频团队能够使用,那么其他团队也可以陆续采用。
这是我们之前做的两个创意原型,但用处并不大,测试结果也并不成功。
这是我们现在使用的版本,实际上,我们每次迭代都会交给音频团队尝试,这是个很痛苦的过程,经历了很多次迭代。每当遇到问题的时候,工具就需要做出改变,但我们聚焦于完善所有功能。经过几个月的尝试之后,我们实现了同等特性,新功能可以很容易部署,甚至可以让其它程序员在功能研发方面做出贡献。
Sander:游戏设计新流程(3D编辑器)
给研发团队完成大项目研发信心最好的方式,就是为他们打造一个关卡编辑器,也就是我们说的3D编辑器,接下来说说这个新编辑器背后的决策。
就像Dan提到的那样,在已经开始游戏研发的情况下,为什么还要做新的3D编辑器呢?为什么不是在Maya插件投入更多努力进行优化呢?
第一个原因是,《地平线:零之曙光》需要大量的任务内容,对于《Killzone》来说是全新内容类型,所以在实用性方面来说,我们在Maya插件里的内容制作流程是最糟糕的一部分,它本身并不适合做关卡编辑。
其次,类似Unity或者Unreal Engine那样的编辑器可以给我们的工作流带来帮助,如果没有这样的编辑器,我们不可能快速完成很多内容的研发。最后,我们做编辑器的焦点很容易实现,只要搞定对象放置就能满足策划团队的工作流需求,接下来说我们是如何实现的:
有了工具框架雏形和可执行的游戏,这时候就面临第一个关键选择:是将特定子系统导入编辑器,还是把整个游戏都导入其中?
我们来看第一个选择,它的目的性很强,而且比较容易控制,从研发角度来看,效率和速度都比较快。然而它的弊端在于,如果后续做新功能就很难做到视觉或者行为的一致性,由于需要更多代码,因此研发周期会更长。
如果把整个游戏都植入编辑器,你可以看到所有游戏功能,意味着能够自由做DebugUI,而且还可以像玩游戏那样在编辑器改动。这个方式的不利在于性能开销比较高。
考虑到游戏研发已经开始,我们认为后者是最好的选择。这时候我们又面临一个选择:到底是游戏的运行是放在编辑器处理流程之内还是在流程之外?
如果是流程之内,它的优势在于能够快速设置且容易做debug,由于在同一个流程内,我们可以直接同步数据变化。但它的弊端在于游戏崩溃会直接导致编辑器崩溃,需要大量的工作才能搞定,而且随着时间的增加,会导致编辑器里的很多功能导向游戏不同系统,让代码管理非常困难。
另一个方法则可以带来比较高的稳定性,由于游戏和编辑器分离,即便游戏崩溃也很容易恢复,而且代码会比较整洁,因为编辑器不能直接修改游戏目标。由于两者分离,哪怕游戏内UI反馈速度不那么快,你也可以在编辑器内快速看到修改结果。当然,它的不利之处在于,编辑器研发时间会比较长,如果编辑器和游戏流程不同步,就会很难做debug。
结合项目和团队限制,我们决定选择第一个方案,但同时希望在游戏研发后期能够做到流程外研发,所以希望做到代码分离,不让编辑器和游戏之间共享任何数据。
这里用一个研发案例来展示:
首先关卡策划可以在编辑器运行游戏,角色到了一个地点之后发现游戏关卡出现了问题,这时候我们进入编辑模式,去掉挡路的树木,然后在路两边增加植被。通过工具处理地形之后,再加入遭遇的敌人。
在运行游戏中编辑内容:关卡编辑器需要的子系统
这个部分主要说如何实现编辑器代码和游戏代码的分离。早期遇到的问题是,游戏里的数据与硬盘上的数据并不一致,主要是因为Dan之前提到的Process Code Conversion。虽然这个问题很常见,但由于信息确实,它给关卡编辑带来了一些问题。
幸运的是,我们已经说过,并不需要通过编辑器直接控制游戏改动,因此编辑器和游戏使用的数据格式不同,且两者之间无法共享。
处理编辑器与游戏数据的方法,我们称之为EditorNode,它提供了一个目标在游戏内的所有编辑功能,游戏目标并不知道Editor Node的存在。所以,这种方式可以让代码保持简洁,游戏代码不会和编辑器代码混淆。
转化后数据带来的第二个问题是层次,低层次性会导致性能降低,虽然扁平化的目标列表快速有效,但对于本地化编辑并不容易。
因此,我们需要内置一些有层级的游戏内数据结构,这样关卡策划进入编辑模式的时候,就可以更直观地看到调整效果。
为此,我们做了两个Editor Node Tree,他们看起来一样,但参照的是不同数据。编辑器做出改变之后,就会与游戏沟通,这部分沟通内容就是第二部分。
游戏内物体的改变
确定目标的时候,你往往需要列出自己不想要的东西,在我们的案例中,proxy representations、基于字符串的accessor、编辑器特定样板文件,以及版本中的代码生成步骤都是我们不希望出现的。
我们希望直接做能够在引擎里找到的具体目标,能够复用现有系统和工具创造和转换数据。意味着我们的游戏以及引擎程序员需要了解编辑器代码,因此,一个回撤系统才能让他们对新功能尝试有更多控制权和自由度。
因此,回撤系统必须简单而且可靠,不需要虚拟的“做”和“撤销”功能,在做新功能的时候不需要回撤新代码。
因此我们的理想情况是,做一个能自动检测目标变化并知道如何还原它们的回撤系统。我们做出的妥协是实现通过代码指定被修改目标,然后再完成对应的改变。
这些都是非常简单的指令,每个指令都代表了一个改变,这些改变都通过RTTI系统实现,并且可以在用户界面实时通知。
这种方式有很多好处,比如代码分离,还意味着组件变化可以来自任何地方,编辑器代码不需要知道改变或者回撤的太多信息。
最开始的时候,我们的方法是手动处理,但非常耗时且不符合我们的易用性标准,因此我们的解决方案是用DiffUtil来观察已经发生的变化。改变之前,程序员用DiffUtil保留目标状态快照,改变之后再分析当前状态与快照的对比。
这样,在不需要改变任何代码的情况下,编辑器就可以执行改动,还可以根据关卡策划的需求随时回到原来的状态。
总的来说,这是一个非常可靠而且透明的系统,能够呈现复杂的游戏内改动。在编辑器代码中,改变和回撤动作没有严格的区分,在随后两三年的时间里,虽然我们做出了很多改变,但代码稳定性一直比较高。
不过,同样有些东西表现不是那么好,虽然有DiffUtil,但程序员们很容易忘记使用,因此并不是所有改动都可以撤回,另外一个问题就是,我们没办法找到所有bug,最后也很重要的事,它并不支持文件系统操作,这也是我们接下来需要解决的问题。
3D编辑器里的变化
当一个改变发生之后,编辑器里的Node Tree听取并做出改变,并且将具体改变发送到游戏Node Tree,游戏里对应的Node Tree接受信息并作出对应改动。
之前的Node Tree展示图可能会比较简单,接下来我们详细说明,假设MySpotlight是游戏目标MyPrefab的母目标,也就是树木的根,每个node变化都会应用到树根当中,更高级的改变还会触发地形的变化。
比如上述动图当中,路线Node的改变会引发附近的植被甚至地形变化。
不过,这种方式在互动改变的时候容易出现信息丢失的问题,我们发现反向做改动是不现实的:首先,这让数据授权变的不清不楚,由于转换当中的数据丢失,反向改动是不可能实现的,最后,你还需要防止编辑器里做的改动回流到游戏当中。
我们的解决方案是,工具不对数据做出修改,它们只是告诉编辑器希望改动哪些东西,然后由编辑器决定修改哪些东西,这样,数据授权始终由编辑器控制。
回撤操作也是如此。
游戏同步
有一部分结构改变是我们没有管的,更新游戏结构改变是通过GameSync功能实现的,具体流程如下图所示:
GameSync的优势在于,它可以适应编辑器所有的改动,甚至可以在不重启游戏的情况下让用户实现文件同步。不过它的问题在于分析改变的代码会非常复杂,还容易出现性能问题,对于一些不相关的系统,很容易出现不完整的信息更新,导致用户体验较差。
Dan:其他工具
之前提到,我们与使用NodeGraph的团队进行了合作,其中一个非常重要的工具就是NodeGraph Debugger,它提供了很多强大的功能,比如变量检查、执行视觉化以及历史debugging信息。
在《地平线:零之曙光》研发过程中,这个工具是非常有用的,这种视觉化的dubugger工具可以让工具团队参与最小化,这对我们来说是成功的,因为这意味着公司里的其他团队可以自由使用这些工具。
你可以实时查看改动信息,还可以按下暂停,查看之前的改动信息。
对这款游戏来说,有些东西是全新的,比如互动对话。对于这方面的内容,我们没有任何现存工具可以解决,因此不得不从头开始打造工具,专门为作家、任务策划以及过场动画美术师们设计新工具。
这是我们的对话编辑器,清晰地展示了对话、选择,任务目标等信息,我们可以通过简单的游戏内对话来展示:
比较有趣的是,这段对话是自动生成的,包括镜头剪辑、动画、手势都是通过规则系统选择的,这个系统还会生成RovoVoice和面部表情动画,意味着剧情策划只需要输入对话文本,游戏就可以快速看到对话在游戏里的效果。
这个对话生成器的灵感来自于CD Project RED和BioWare之前分享的技术,具体的技术可以参考图片中的GDC演讲链接,我们之所以可以做到,是因为在我们的新框架之下很容易实现。
我们做的另一件事就是世界地图,对于一个开放世界游戏来说,展示所有发生的事情是非常困难的,为此我们通过2D地图代表游戏数据,并且通过覆盖系统展示任务、JIRA bug等信息。
不过,这个功能是在项目后期才推出的,而且使用率并不高,但在下一个项目中还有提升空间。
这是我们做的性能地图,可以展示某个地方的内存、CPU以及GPU能耗,分别通过绿色和红色代表能耗,不过这是游戏研发后期,性能问题已经被解决,所以没有红色区域出现。
最后一个是Decima API,Guerrilla一些程序员希望给这个框架打造C语言API,以便让某些工具可以运用于其他语言,比如文件加载/存储、目标控制等功能,我们还用Python写了一个wrapper,让学过Python的美术师和策划都可以创造控制游戏内容的脚本。
随着项目研发的结束,我们有了更多时间审视工具框架,这些工具是非常成功的,但我们发现还有很多可以提高的地方,比如让更多团队使用我们的编辑器,例如特效、环境美术等等,所以,我们还将开始打造不同的编辑器,比如Entity、粒子、着色器等等,让公司里的每个人都能使用我们的编辑器。
我们还希望重写GameSync系统,把3D编辑器从流程之中剥离出来,以便提升稳定性,在编辑器打造过程中,我们还发现了很多的UI以及UX方面可以提高的地方,所以未来还有很多事情需要做。
如若转载,请注明出处:http://www.gamelook.com.cn/2022/07/490372