Made with Unity | 制作《疯狂之石(The Stone of Madness)》时面临的三大技术挑战
今年初,The Game Kitchen工作室推出战术RPG《疯狂之石》(The Stone of Madness),玩家需带领五名囚犯逃离宗教裁判所监狱。本文由工作室三位核心开发者亲自解析开发过程中解决的渲染、UI及测试难题。我们是Game Kitchen工作室,最近刚刚在PC和主机平台发布了《疯狂之石》。在此,我们想从技术角度分享开发这款最新作品时遇到的一些最紧迫的挑战,并通过实际案例加以说明
今年初,The Game Kitchen工作室推出战术RPG《疯狂之石》(The Stone of Madness),玩家需带领五名囚犯逃离宗教裁判所监狱。本文由工作室三位核心开发者亲自解析开发过程中解决的渲染、UI及测试难题。
我们是Game Kitchen工作室,最近刚刚在PC和主机平台发布了《疯狂之石》。在此,我们想从技术角度分享开发这款最新作品时遇到的一些最紧迫的挑战,并通过实际案例加以说明。在这篇合作撰写的文章中,我们的编程团队将详细解析在Unity中实现的关键解决方案,这些方案不仅优化了游戏性能,还提升了开发效率。
首先,图形程序员Adrián de la Torre将介绍我们如何设计和渲染游戏的美术管线,以实现其独特的视觉风格。
接下来,UI程序员Alberto Martín将详细说明我们如何利用Noesis(即NoesisGUI,专为游戏和实时应用程序设计的高级UI开发框架)来简化UI开发流程,并根据用户反馈进行UX改进以优化工作流。
最后,玩法程序员Raúl Martón将展示我们如何在服务器上外部化和自动化复杂游戏行为的测试,确保在不影响系统集成的情况下处理多个边界案例(corner cases)。
allowfullscreen="true" border="0" frameborder="no" framespacing="0" scrolling="no" src="https://live.csdn.net/v/embed/482153">
视觉效果挑战:通过定制渲染管线解决
Adrián de la Torre,The Game Kitchen图形程序员
《疯狂之石》将2D视觉效果与3D玩法机制相结合,这带来了独特的技术挑战。玩家看到的是2D世界,而游戏的底层系统却在三维空间中运行,形成了独特的设计二元性。
为解决这一挑战,我们开发团队创建了一个定制渲染管线,有效弥合了3D玩法信息与2D视觉表现之间的鸿沟。该方案通过多重渲染通道和专门技术,在保持视觉一致性的同时保留了预期的游戏深度,实现了3D元素到游戏独特2D美术风格的无缝转换。
在《疯狂之石》中,帧渲染主要由两个场景构成:
第一种场景我们称之为代理场景(Proxy Scenario),由计算最终帧光照的几何图元组成。
*底层3D场景视图(代理场景)
第二种是画布场景(Canvas Scenario),由与代理几何体形状和位置相匹配的精灵图组成。画布通过分层排列来模拟3D空间,并为移动游戏元素实现正确的Z轴排序。
allowfullscreen="true" border="0" frameborder="no" framespacing="0" scrolling="no" src="https://live.csdn.net/v/embed/482155">
*2D场景视图(画布场景)
以下将详细介绍我们图形管线中帧渲染的每个步骤:
allowfullscreen="true" border="0" frameborder="no" framespacing="0" scrolling="no" src="https://live.csdn.net/v/embed/482154">
*游戏渲染流程概述
1. 视觉锥(Cone of vision)
当视觉锥或游戏技能被激活时,将启动管线的第一步。我们会在NPC的视点(PoV)位置放置一个摄像机,用于渲染其视野范围(FoV)内代理模型的深度信息。
*由NPC视点摄像机生成的深度纹理
然后,在另一张渲染纹理中,摄像机会在B通道输出玩家原点距离的渐变,该渐变用于技能特效的范围效果。
*由玩家视点摄像机生成的渐变纹理
利用NPC视点(PoV)的渲染纹理,视觉锥摄像机会在R和G通道上叠加一层锥形区域,并存储障碍物和距离信息。
*视域锥(cone of view)覆盖在渐变纹理上
最终渲染通道将声波数据写入Alpha通道。
*声波信息叠加在渐变纹理的Alpha通道上
这是本步骤生成的最终纹理,将被用于画布摄像机(Canvas Camera)阶段来渲染场景中的精灵。
*用于渲染技能范围和视锥的最终纹理
2. 画布Render ID摄像机
在我们的项目中,每个代理模型都有一个对应的Render ID(浮点数值)。代理模型与其关联的精灵共享相同的Render ID。在这一步骤中,我们将这些Render ID的浮点数值渲染到一个纹理中。
*Render ID纹理。每种色度代表代理模型与其对应精灵共享的唯一Render ID。
在后续步骤中,我们将使用此纹理将代理场景中计算的光照信息与画布场景中的精灵进行匹配。
3. 光照系统
游戏中的光照由以下类型组成:
• 烘焙光照:持续生效的固定光源(如室外光照)
• 混合光照:可开关的静态场景光源(如蜡烛)
• 实时光照:可在场景中移动并开关的动态光源(我们仅在一处实现此效果——Alfredo的油灯)
allowfullscreen="true" border="0" frameborder="no" framespacing="0" scrolling="no" src="https://live.csdn.net/v/embed/482150">
*《疯狂之石》中不同类型的光照效果概览
利用RenderID纹理,我们创建了一个包含代理场景光照信息的渲染纹理。
*基于Render ID纹理和光照计算生成的阴影纹理
4. 画布摄像机
在创建完所有渲染纹理后,摄像机开始渲染包含光照信息、技能作用范围、视觉锥和噪声波纹的精灵。
allowfullscreen="true" border="0" frameborder="no" framespacing="0" scrolling="no" src="https://live.csdn.net/v/embed/482149">
*精灵渲染流程概览
5. Post-processing后处理
在后处理阶段,会应用色彩分级(Color Grading)、暗角效果(Vignetting)以及其他特效。
allowfullscreen="true" border="0" frameborder="no" framespacing="0" scrolling="no" src="https://live.csdn.net/v/embed/482151">
*后处理效果应用概览
6. UI
最后叠加UI。
frameborder="0" src="https://developer.unity.cn/player/68343a38edbc2a10eee67667">
*UI叠加流程概览
UI开发中的挑战:加速UI开发流程
Alberto Martín,UI程序员,The Game Kitchen
《疯狂之石》最终版包含50多个用户界面。这是因为游戏需要向玩家展示大量信息。由于团队初期规模较小,UI工作耗时巨大,因此我们持续优化流程以确保在最短时间内获得最佳效果。
UI工作贯穿整个项目周期,因此确保UI/UX设计师清楚理解所有需要实现的功能至关重要。为了保证良好的用户体验和游戏乐趣,我们特别注意保持编程团队与设计团队之间的畅通沟通。
为了打造最佳的UI组件,我们打破了技术团队与创意设计/用户体验研究团队之间的隔阂,让所有人都能积极参与游戏开发。以下是我们的双轨工作流程。
UI设计中的创意设计与用户体验研究
我们的UI/UX设计师负责定义游戏最终界面元素的视觉呈现,并确保为用户提供令人满意的体验。基于这一目标,他们从设计之初就遵循两个核心原则:一是保持每个元素的技术负载最小化,二是通过潜在用户验证设计效果。具体实施流程如下:
-
需求分析:理解玩家需求,列出游戏需求和用户目标
-
调研:研究其他游戏如何处理类似问题
-
线框图:设计结构和布局(此时不包含最终美术)
-
高保真原型:使用既有元素(按钮、滚动条、框架等)搭建近乎完整的设计界面,便于快速迭代
-
交互原型:在Figma中构建原型,模拟手柄和键鼠操作,展示实际环境中的工作方式
-
用户测试:使用原型进行测试,验证第一步确定的需求和目标
-
迭代阶段:根据测试结果进行技术实现、继续迭代或进一步测试
allowfullscreen="true" border="0" frameborder="no" framespacing="0" scrolling="no" src="https://live.csdn.net/v/embed/482152">
*《疯狂之石》物品栏标签页导航设计原型展示
技术UI的实现
如前所述,《疯狂之石》的UI元素数量极为庞大。开发专属UI引擎成本过高,因此我们需要采用一个易于学习、且具备完善工具链和工作流的框架。经过对多种中间件的评估,我们最终选择了遵循MVVM(Model-View-ViewModel)设计模式的Noesis GUI。
选择Noesis的原因在于其基于WPF(Windows Presentation Framework)架构,并完整遵循MVVM模式。这使得我们可以复用绝大多数WPF技术文档、参考文献和论坛资源来解决常见问题。该框架已有18年历史(自首次发布至今),被大量UI开发人员所熟知,这让我们能够从更广泛的人才库中招募专业人员来开发项目所需的界面和工具。Noesis的另一大优势是我们可以直接使用WPF的原生工具链。
通过XAML标记语言,我们的UI创意团队能够以最低限度的技术介入参与布局设计和元素优化。得益于MVVM架构,技术UI团队可以专注于功能实现,仅在必要时为创意团队提供特定领域的支持。
测试(如何在系统性设计中保持理智)
Raul Martón,玩法程序员,Teku Studios
《疯狂之石》的核心玩法建立在三大基础支柱之上:玩家技能系统、NPC人工智能和场景交互机制。这三个系统深度耦合,使得玩家需要应对的复合情境数量呈指数级增长,这也意味着我们需要测试的潜在场景数量激增。
项目启动初期,我们就意识到传统QA测试体系将难以满足需求。当多个系统以特定方式交互时,会产生海量不可控的复合情境。更关键的是,这些情境可能发生在极短的时间窗口内,远超人工QA团队的测试响应极限。
为此我们开发了自动化测试套件。该方案的核心理念是:将所有开发团队能够预见的系统关联情境,通过模拟游戏环境进行高效自动化验证。
例如在实现女主角Amelia Exposito的扒窃技能时,我们通过自动化测试确保:
-
基础功能验证:当对NPC实施偷窃时,正确触发扒窃小游戏并暂停主游戏流程
-
边缘场景覆盖:当存在目击者(如警卫)或目标NPC处于移动状态时,禁止偷窃行为
*扒窃技能的自动化玩法测试,验证该技能在不同游戏场景中是否按预期运作
创建集成测试
每个集成测试需基于以下要求进行配置:
1. 专门设计的测试场景
为测试扒窃技能,我们创建包含两名警卫和一名玩家的场景。精确调整角色朝向以确保测试条件成立(注:玩家在警卫视野范围内无法使用扒窃技能)。
此外,场景中应仅包含测试该场景所需的最简组件,因为多余元素会给测量结果带来干扰。这就是为什么我们的示例场景没有HUD(平视显示器)、手动输入系统、音效等元素。
-
这一步骤要求游戏结构具有高度模块化特性,虽然需要一定前期投入,但一旦实现将物超所值!😉
2. 能够强制触发待测试场景的测试代码
我们需要测试的许多场景往往难以手动创建且耗时,甚至需要通过代码推送才能触发。
例如,若想测试“NPC仅在移动时才会触发捕鼠夹”的场景,就需要按以下步骤操作:
-
加载场景
-
等待1秒
-
在NPC脚下生成捕鼠夹
-
再等待1秒
-
命令NPC开始向任意方向移动
由于该测试环节对开发中的变动极其敏感(可能受游戏规格变更或各种意外场景影响),因此必须确保测试代码和反馈信息绝对清晰。
最糟糕的情况莫过于测试失败时,却无法获取任何明确的错误信息。
3. 一套能够可靠判断场景是否按预期运行、或检测出逻辑错误的机制
自动化测试仍需要人工监督。随着测试数量增加、测试项日益细化,监控难度会大幅上升,甚至可能出现测试时长不足、测试不充分导致结果可能不准。为解决这些问题,我们开发了定制化工具。
例如,部分测试涉及场景中多个NPC的复合交互行为。为有效监控这类情况,我们专门构建了一套系统,用于记录测试过程中NPC经历的不同AI状态循环。
*自动化测试中守卫NPC未能按预期执行追击序列
我们还需要一个完善的API来获取当前游戏状态的可视化数据(例如:NPC是否已被击晕?NPC是否进入逃跑状态?触发次数?哪个玩家角色被捕获?等等)。
4. 快速启动测试的系统架构
与单元测试不同,自动化测试需在游戏实时运行时进行,这可能导致测试效率低下。
在这种情况下,我们充分利用了游戏没有采用Unity标准更新系统的特性。我们所有组件都使用了Tick()函数,这个函数通过游戏引擎以可控方式调用,从而模拟Unity的更新机制。
该设计帮助我们实现了两大测试目标:
• 首先,我们通过强制函数加速测试执行,使单游戏帧内可处理多帧代码逻辑
• 其次,由于测试需实时运行,其结果易受测试设备帧率波动影响。通过转换为固定帧率控制,我们消除了这种差异:确保测试通过率在所有设备上保持一致(反之亦然)
下方就是测试结果。
allowfullscreen="true" border="0" frameborder="no" framespacing="0" scrolling="no" src="https://live.csdn.net/v/embed/482148">
*通过控制游戏更新加速自动化测试:The Game Kitchen的自定义Tick()系统可实现单游戏帧内运行多测试帧,既提升执行速度,又确保不同设备间的结果一致性。
安全测试是帮助我们避免损坏构建版本的
随着测试套件的创建,我们还需要实现一种保护机制:当分支代码存在缺陷时,自动中断其合并流程。为确保这一点,我们创建了一个自动合并脚本,该脚本会在每次更改提交到项目主分支时触发。
这个脚本会启动所有测试并监控结果。如果任何测试失败,它将返回错误检测并中断合并。
通过这个系统,我们可以避免这种情况:看似独立的系统更改破坏了与其交互的其他功能模块。
更多推荐
所有评论(0)