游戏厨房关于制作《疯狂之石》的三个技术挑战

今年早些时候,游戏厨房推出了《疯狂之石》,这是一款战术角色扮演游戏,玩家帮助五名囚犯从宗教审讯监狱中逃脱。在这篇客座文章中,工作室的三位开发者分享了他们在开发过程中如何应对渲染、用户界面和测试挑战。
我们是The Game Kitchen,最近在PC和主机上发布了《疯狂之石》。我们想分享在开发我们最新项目过程中面临的一些最紧迫的挑战,从技术角度出发,并提供实际例子。在这篇合作文章中,我们的编程团队分析了我们在Unity中实施的关键解决方案,以优化性能和开发效率。
首先,Adrián de la Torre(图形程序员)将解释我们如何设计和渲染游戏的艺术流程,以实现其独特的视觉风格。
接下来,阿尔贝托·马丁(UI程序员)将详细介绍我们如何利用Noesis来简化UI开发,通过基于用户反馈的UX改进来增强工作流程。
最后,Raúl Martón(游戏程序员)将展示我们如何将复杂游戏内动作的测试外部化和自动化,确保在不干扰集成的情况下处理多个边缘情况。
让疯狂看起来美好自定义渲染管道的概述
阿德里安·德拉·托雷,图形程序员,游戏厨房
疯狂之石结合了2D视觉和3D游戏机制,这带来了独特的技术挑战。虽然玩家看到的是一个二维世界,但游戏的底层系统在三维空间中运行,创造了其设计中的独特二元性。
为了解决这个挑战,我们的开发团队创建了一个自定义渲染管道,有效地弥合了3D游戏信息与2D视觉表现之间的差距。该解决方案实现了多个渲染通道和专业技术,以保持视觉一致性,同时保留预期的游戏深度,从而允许将3D元素无缝转换为游戏独特的2D艺术风格。
在《疯狂之石》中,有两个主要场景有助于帧的呈现。
第一个场景,我们称之为代理场景,由几何原语组成,这些原语计算最终帧的光照。

第二种场景是画布场景,它由与代理几何形状和位置相匹配的精灵组成。画布分层排列,以模拟3D空间并实现移动游戏元素的正确Z排序。
以下部分详细介绍了我们图形管道中每个步骤的帧渲染。
1.视野锥
每当视野锥或游戏能力被启用时,它会启动管道中的第一步。我们将相机放置在NPC的视角(PoV)上,以渲染其视野(FoV)内代理的深度。

然后,在另一个渲染纹理中,摄像机在 B 通道输出从玩家原点的距离渐变,这用于技能区域效果。

使用NPC的视角渲染纹理,视锥相机在R和G通道上渲染一个锥体,覆盖在之前的纹理上,包含关于障碍物和距离的信息。

最终通道在 Alpha 通道中渲染声波。

这是在此步骤中创建的最终纹理,将在画布相机步骤中用于渲染场景的精灵。

2.画布渲染 ID 相机
我们项目中的每个代理都有一个关联的渲染 ID(一个浮点值)。代理及其相关精灵共享相同的渲染 ID。在此步骤中,我们将渲染 ID 浮点值渲染到渲染纹理中。

在后续步骤中,我们使用此纹理将代理场景中计算的光照信息与画布场景中的精灵进行匹配。
3.照明
我们游戏中的灯光包括:
- 烘焙灯光永久保持活跃的自然光,例如外部照明
- 混合照明场景中的静态灯光可以开关,例如蜡烛
实时照明在场景中移动并可以开关的光源(我们只在一个实例中实现了这一点,阿尔弗雷多的油灯)
使用 RenderID 纹理,我们创建一个包含代理场景光照信息的渲染纹理。

4.画布相机
在创建所有渲染纹理后,摄像机开始渲染精灵,包含有关光照、技能效果区域、视野锥和噪声波的信息。
5.后处理
颜色分级、渐晕和其他效果在后期处理过程中应用。
6.UI
最后,用户界面被覆盖。
仪表盘中的疯狂加快用户界面流程
Alberto Martín, UI Programmer, The Game Kitchen
最终发布版本的《疯狂之石》拥有超过50个用户界面。这个数字背后的原因是这个游戏有很多数据可以展示给用户。我们的用户界面工作非常耗时,尤其是在团队刚开始时规模很小,因此我们不断优化我们的流程,以确保在尽可能短的时间内取得良好的结果。
我们的用户界面工作贯穿整个项目,因此我们的UI/UX设计师必须清楚地理解我们需要实现的所有功能。为了确保我们的游戏提供良好的用户体验并且有趣,我们小心地保持了编程和设计团队之间的开放沟通。
为了创造我们所有用户界面组件的最佳版本,我们需要消除技术团队与创意/研究团队之间的壁垒,以便每个人都能积极参与游戏的开发。这是我们如何处理这个两部分工作流程的。
研究与创意在用户界面设计中的作用
我们的UI/UX设计师负责定义最终游戏中UI元素的外观,并确保我们提供令人满意的用户体验。考虑到这一点,他们首先以最小的技术负担创建每个元素,并与潜在用户进行验证。那个过程看起来是这样的:
- 必要条件:理解玩家的需求,并创建游戏需求和用户目标的清单
- 调查查看其他游戏是如何处理类似问题的
- 线框图正在处理原理图和结构(此时没有最终艺术作品)
- 模型在这一点上,我们将几乎完全设计好的界面与之前创建的元素(按钮、滚动条、框架等)结合起来,使我们能够轻松地进行迭代。
- 原型我们在Figma上使用我们的模型构建原型,模拟与游戏手柄和键盘/鼠标的交互,以展示它在真实环境中的工作方式。
- 用户测试使用我们之前创建的原型,我们开始进行用户测试,以验证我们在第一步中确定的需求和目标。
- 迭代阶段如果用户测试符合预期,则将其传递给技术部分流程,进行更多迭代,或者在方便的情况下进行进一步测试。
技术用户界面实现
正如之前提到的,《疯狂之石》中的用户界面元素数量庞大。开发一个用户界面引擎是昂贵的,因此我们需要使用一个易于学习且具有良好工具和工作流程的框架。在评估了一系列中间件后,我们选择了 Noesis GUI,它遵循模型-视图-视图模型(MVVM)模式。
我们选择Noesis,因为它基于WPF(Windows Presentation Framework),并以一种我们可以重用大部分文档、参考书目、论坛条目等来解决大多数问题的方式遵循MVVM模型。这个框架已经存在了一段时间——自首次发布以来已经18年——并且对大量的UI开发者来说是熟悉的,这使我们的工作室可以从相对更大的人才库中招聘,以实现我们项目的界面和工具。关于Noesis的另一个重要事项是,我们可以使用WPF中的相同工具。
通过XAML,我们的UI创意团队参与了布局工作,并以最小的技术参与来完善所有元素。得益于MVVM方法,我们的技术UI程序员可以专注于功能,并在必要时为创意团队提供支持。
测试(或者,如何不在创建系统设计的游戏时发疯)
劳尔·马尔顿,游戏程序员,Teku工作室
《疯狂之石》的游戏玩法基于三个基本支柱:玩家技能、NPC AI 和场景互动。这三种系统在根本上是相互交织的,这成倍增加了玩家需要控制的情况数量,以及我们需要测试的场景数量。
一开始我们启动这个项目时,就意识到传统的质量保证系统将无法满足需求。有太多场景依赖于几个部分以特定方式相互作用,从而导致了一个失控的局面。此外,这些情况可能发生在一个时间窗口内,这个时间窗口对于QA团队来说太小,无法进行舒适的测试。
为了解决这些问题,我们创建了一套自动测试。这个想法是,所有可能发生在我们开发团队与特定系统相关的场景/情况,都可以在模拟游戏环境中更高效地进行考虑和自动测试。
为了提供一个例子,《疯狂之石》的主要角色之一阿梅利亚·埃克斯波西托拥有扒手的能力。在实施这一技能时,我们启动了一系列测试以确保:
- 技能的基本功能是正确的:当从NPC那里偷东西时,扒窃迷你游戏会开启,游戏会暂停直到游戏结束。
- 较少见的情况也被涵盖:如果你试图在另一个 NPC(如守卫)正在注视你时从一个 NPC 那里偷东西,或者如果 NPC 正在奔跑,这个行为是不可能的。

创建集成测试
我们创建的每个集成测试都需要根据以下要求进行设置:
1.为创造这种特定情况而特别准备的场景
为了测试扒手技能,我们创建了一个有两个守卫和一个玩家的场景。我们将每个角色的位置调整到他们面向所需方向,以便准确测试情况(请记住,如果玩家在守卫的视野范围内,他们无法使用扒窃技能)。
此外,场景应仅包括测试场景所需的最少组件,因为多余的元素可能会给测量带来干扰。这就是为什么我们的示例场景没有HUD、手动输入系统、音效等等。
- 这一步要求游戏结构良好地分隔,这可能需要一些努力,但一旦实现,就非常值得!😉
2.一个能够强制测试情况的测试代码
我们需要测试的许多情况可能很难手动创建,并且需要代码推送来启动。
例如,如果我们想创建一个测试场景,以确保我们的 NPC 除非在移动,否则永远不会踩到捕鼠器,那么指令链将是:
- 启动场景
- 等一下
- 在NPC下面放置一个捕鼠器
- 再等一秒
- 指挥NPC开始朝任意方向行走
这个项目的这一部分在开发过程中对任何变化都非常敏感(依赖于游戏规格的变化和各种意外情况等因素),因此测试代码和反馈结果必须尽可能清晰。
没有什么比一场没有清晰信息的失败测试更糟糕了,尤其是当你不知道到底出了什么问题时。
3.一种可靠的方法来判断场景是否按预期工作,或者测试是否检测到逻辑错误
自动化测试仍然需要监督。越来越多的测试具有更高的特异性,关于测试内容的监测可能变得困难,或者某些场景的测试时间不足,无法达到统计显著性。为了克服这些问题,我们创建了自定义工具。
例如,我们的一些测试涉及场景中几个 NPC 之间的组合互动。为了正确监控这些案例,我们创建了一个系统来记录NPC在测试期间循环的不同AI状态。

我们还需要一个好的API,以便让我们了解当前游戏状态(NPC是否被击晕?)NPC是否进入了路由状态?多少次?哪个玩家角色被捕获了?等等。
4.一个能够快速启动所有这些测试的系统:
与单元测试不同,自动化测试必须在游戏实时运行的情况下进行。这可能会使运行这些测试变得非常慢。
在这种情况下,我们能够利用我们的游戏不使用Unity的标准更新系统这一事实。相反,我们所有的组件都使用 Tick() 函数,它模拟 Unity 更新,但由我们的游戏引擎以受控的方式启动。
这帮助我们通过测试实现了几个不同的目标:
- 首先,我们可以通过一个强制函数加快它们的执行,该函数为游戏的每一帧运行几帧代码。
- 其次,由于这些测试是在实时进行的,因此它们非常容易受到运行测试场景的计算机的帧率变化的影响。通过将它们转换为受控的帧速率,我们避免了这种变化。如果一个测试在一台机器上通过,那么它将在所有机器上通过,反之亦然。
这将是结果。
安全测试如何帮助我们避免构建失败
随着这个测试套件的创建,我们还需要实施一个保护措施,如果一个分支包含错误,它将自动中断合并。为确保这一点,我们创建了一个自动合并脚本,每当对主项目分支进行更改时,该脚本就会启动。
这个脚本确保启动所有这些测试并监控它们的结果。如果任何测试失败,它将返回错误检测并中断合并。
通过这个系统,我们可以避免在一个看似孤立的系统中发生变化而破坏其交互的其他机制的情况。
感谢游戏厨房分享《疯狂之石》开发的幕后花絮。在我们的Steam策展页面上探索更多使用Unity制作的游戏,并在Unity的资源页面上获取更多开发者见解。