扩展 Unity 工作流程:从中大型项目中汲取的经验

这篇博客文章是 Mega Cat Studios 系列文章的第一篇,他们将在其中分享自己在 Unity 方面的专业知识,并针对现实中的商业游戏开发挑战提供解决方案。希望您能从中获得一些实用建议!
你有一个绝妙的主意,代码的编写速度快得就像你在键盘上敲击的速度一样。随着每次提交,新功能逐渐形成形状。但正是这些想法形成的速度,很快就会让你面对一团混乱、漏洞百出的局面。
在 Mega Cat Studios,我们始终怀着满腔热情开启每一个项目,因此我们深知那种不拘小节、力求速成的魅力,也明白尽快完成工作的价值。对于构建原型来说,这种方法很合适,事实上,我们推荐这种做法!一位明智的开发者知道何时应优先考虑迭代速度,何时应优先考虑稳定性。因为一旦走出构建原型阶段,这种“草率粗放”的做法就会变成一种负担。
在Mega Cat Studios,我们已经多次经历过这种过渡,而且在每个项目中,我们都会学到新东西。我们想分享一些我们在筹备最新项目《后院棒球》上线过程中所汲取的经验。
第1课:适配不同规模的架构

缩放问题很少是由代码质量差引起的;相反,它们更多是由于架构规划不周所导致的。如果开发者或美术师在10秒内找不到某个资产,那么工作流程就需要调整。以下是一些关于如何将项目设计为可扩展的建议:
- 按类型和用途分类:我们先按类型组,再按用途组。类型包括艺术、代码和音频等类别。“用途”指它们被用来做什么。直观的分类结构能减少入职适应的阻力;新加入的美术师无需询问,就能准确知道“角色空闲”精灵该放在哪里。
- 保持场景简洁:我们摒弃了“巨型场景”的设计,转而采用更小的场景,例如一个用于存储存档数据和关键系统的主场景,以及根据玩家是否处于匹配状态而动态加载的标题画面和棒球场场景。同样地,我们使用预制件来实现自包含的功能,这样这些更改就不会被Serialization到包含它们的场景文件中。这样一来,美术师可以在同一个“关卡”中负责环境制作,而设计师则可以同时修改游戏玩法,且不会出现文件冲突(稍后将详细说明)。
- 设置 Addressables 系统:我们摒弃了传统的“资源”文件夹,转而采用“Addressables”系统,仅在需要时加载资产,从而保持内存占用量精简。此外,使用 Addressable 键加载素材资源比通过文件路径加载 Resources 文件夹中的指定的位置更清晰,也更不容易出错。
难点并不在于理解这些最佳实践。这需要在一开始就全身心投入,并在此后的岁月里始终保持这种自律。
明确你目前处于发展的哪个阶段,以及在这个阶段应优先考虑什么。
“在构建原型阶段,由于几乎没有代码,先确保功能正常,再考虑模块化也是可以的,”《后院棒球》的开发者保罗·罗克萨斯说道。“我们想先看看这个项目进展如何,再考虑是否要增加复杂度。”

第2课:与 Unity 协作,而非与其对抗

“没有什么比创建专注、简洁且自成一体的构建模块——无论是组件、ScriptableObject还是自定义类——更强大的了,”Mega Cat Studios 工程总监 David Chávez Armenteros 说道。Unity 的核心理念在于模块化。为了保持灵活性,我们遵循以下三个经典原则:
1.单一职责:每个脚本或类都应有一个明确的角色。
2.松耦合:系统应通过接口或事件进行交互,而非直接引用,且仅在适当的情况下才应如此。我们建议在将系统实现为代码之前,先绘制出系统之间的依赖项图,以避免出现循环依赖项或架构混乱的情况。
3.即插即用:应将小型、专注的单位组合起来以实现复杂的行为,而非编写冗长的“全能”脚本。
第3课:启用角度限制,获得自由

汇编定义(AsmDefs)是用于将代码组成的 C# 结构。它们宣传的优势是缩短编译时间,但真正的秘密武器在于强制实现模块化。
首席开发者和“意大利面代码”的死对头尼科·高登齐对它们赞不绝口。
在《后院棒球》中,输入层和游戏玩法层位于不同的 DLL 文件中。“游戏玩法完全不依赖于具体的输入方式。”
这通过让每个依赖项都成为经过深思熟虑的决策,从而避免了工程师因自身原因导致的失误。如果真有必要,我们可以重写整个Input System——从手柄处理到Netcode——而无需担心破坏玩家的物理效果或AI行为。更可能的是,它能让一名工程师专注于代码库中的某个特定领域,避免无意中将更改波及到其他系统,并减少开发者在实现功能和修复 bug 时需要理清的代码量。
第4课:测试要讲究方法,而非一味拼命
随着项目规模的扩大,“多米诺骨牌效应”可能会逐渐显现:这里的一个微小改变,会导致那里出现问题。良好的架构固然重要,但绝非万能良方。
在功能变更交由我们尊敬的品质保证团队进行人工测试之前,游戏会先经过一系列严格的自动化单元测试。
“测试就像一份需求清单,”尼科说。“它们阐述了预期功能,并提供了关键用例。”
当《后院棒球》中的角色投出快速球、盗垒或击出本垒打时,我们需要实现指定的游戏玩法效果,例如确保球的飞行速度与投球动作相符,跑垒员的时机与盗垒机制相匹配,或者守场员能对击球做出正确的反应。角色必须与地面正确发生碰撞,球必须以恰当的速度移动,此外,诸如追踪蓄力、挥棒或在垒间冲刺等动作的玩家控制器标志等更精细的系统也必须正常运作。
当我们对帕布罗·桑切斯的击球力量进行修改,或者更关键的是,调整控制挥棒动作的通用代码时,我们需要确保从击球时机到球路轨迹的每一个交互环节,在整个游戏中都能保持一致的行为表现。
通常,出问题的往往是那些你意想不到的地方,这也正是测试如此重要的原因。
将该系统集成到我们的工作流程中后,一旦出现指定的需求故障,我们能立即察觉,从而减少了那些如同大海捞针般耗时的测试和故障排查环节。
当您的系统采用模块化设计时,Unity 的 Test Runner 才能发挥最大效用,这也是我们使用组件定义的另一个原因。
第5课:让您的资产动起来

“人非圣贤,孰能无过;宽恕他人,乃神圣之举。”
但要建立一套系统,从源头上杜绝人为失误,这简直堪称传奇。
经过数小时的代码编写和调试,难免会有一些双眼迷离的开发者在不小心点错几下之后,或者误将本应只是临时性的更改推送到代码库中。虽然这可以原谅(作为偶尔也会眼花缭乱的人,我这么说),但如果某项资产的导入设置配置不当,可能会造成严重后果。而且由于许多开发者在高性能机器上进行开发,最糟糕的情况是性能问题直到后期才被发现,比如当包含角色、动画和特效的体育场场景在低端硬件上开始导致运行卡顿或不稳定时。
为了降低这一风险,您可以限制对资产的访问权限,但由于游戏内容需要调整的原因众多,这会导致工作流程出现瓶颈:
- 这个模型太大,无法放入当前的摄像机布景中。
- 这段音频剪辑音量较小,因此需要添加指定的特效。
- 由于着色器已更改,现在需要调整所有纹理。
在《后院棒球》这类游戏里,视觉风格至关重要,因此在游戏发布前,我们会对模型和视觉特效进行数百次修改,以确保其外观和氛围恰到好处。
“无论技术规格多么完善,都无法改变这样一个事实:内容的多样性意味着必须处理不同资产资源之间那些细微却重要的差异,”尼科说道。
自动化在此处同样发挥了作用:
- 资源后处理器:我们编写定制的导入逻辑,以确保符合项目标准。
- OnValidate:我们使用 OnValidate 方法在编辑器中报告缺失的引用,该方法总是在构建之前触发。
不过,最后要提醒的是,不要因为一味追求自动化,而忽略了那些更快捷的简单手动修复方法。
“千万别花10天时间去自动化处理一项手动只需10分钟就能完成的任务,”大卫提醒道。
第6课:掌握人际协作(协作)

像Git这样的版本控制系统往往是首先想到的工具,当每天需要协调数十名开发人员提出的数百项变更时。大卫主张在Mega Cat的所有项目中采用这些久经考验的方法:
- 微小的、原子级的变化:避免进行“大型提交”,这类提交会同时影响多个系统。将工作隔离在各个功能分支中,直到这些分支稳定且经过审查。将各项更改分别记录在不同的提交中,这样不仅能形成记录详实的版本历史,还能在需要时更轻松地进行 cherry-pick 操作及其他 Git 操作。
- 来自主分支的每日合并:请确保功能分支、部门分支和长期分支与主分支保持同步,这样可以减少最终合并的大小和复杂度,有助于避免大规模冲突。
- 审查合并请求:这是品质保证的第一道防线,在这里,您需要发现缺陷、贯彻项目标准,并确保与整体系统的协调一致。
“在大规模的 Unity 项目中,代码审查绝非走过场,”大卫建议道。“它们是预防冲突和确保项目品质的关键环节。”
只需确保代码审查人员具备所实现领域的专业知识,并了解编码最佳实践,这样他们才能准确评估代码的正确性和可维护性。
在 Unity 中,有一些指定的技巧和窍门,可以让版本控制尽可能平滑。场景和预制件是项目的基础,因此优化它们时不仅要考虑 CPU 性能,还要兼顾开发者的协作效率。
与庞大的场景或单一的预制件相比,我们总是更倾向于使用更小、可组合且嵌套的组件。이렇게, 개발자들은 동시에 작업할 수 있으며 충돌이 발생하지 않습니다.
这一点很重要,因为场景和预制件中的合并冲突最难解决,因为开发者很难读取其中的数据。为了使这个过程更加顺畅,我们将这些文件以文本形式(而非二进制形式)进行序列化,并启用了 Git 配置中针对 YAML 文件的自动合并功能。这使得 Git 更有可能自行解决合并冲突,从而为开发者节省时间,让他们能够专注于开发新功能这一重要工作。
但尽管如此:
“预防冲突通常比试图解决冲突更明智,”尼科说。
明确的资产所有权对此大有裨益。
“明确谁可以修改指定的场景或预制件,”大卫说。“然后,团队成员会请求修改其负责范围之外的内容,而不是直接编辑素材资源。”
尼科将类似的程序描述为“信号系统”。这基本上是一个电子表格,开发人员会在其中记录修改资产的时间,从而有效地“锁定”该资产。如果其他开发者需要修改该文件,他们必须等待锁定该文件的开发者将修改内容推送到代码库并“解锁”该文件。
一如既往,请找出最适合您团队的工作流程。
为未来而构建

在 Mega Cat Studios,我们认识到,Unity 项目的缩放与其说是“更努力地编写代码”,不如说是架构规范性的体现。通过尊重 Unity 的组件化特性、利用 Assembly 定义来明确边界,以及以面向未来的方式组织资产资源,我们既能保持构建原型阶段的大部分创作灵感,又不会在项目上线前让其陷入技术债务的泥潭。
虽然这些经验教训很重要,但请记住,没有任何代码库是完美的。소프트웨어 개발은 역사적인 전투로, 추천되는 프로그래밍 패턴과 실제 고려 사항이 매일 충돌합니다.如果遵循其中某项原则导致开发卡顿,却未能带来有益的权衡,这说明你需要更加关注自己团队的具体情况,而不是一味遵循教科书上的建议。毕竟,每个项目、团队和人都各不相同。
在Mega Cat Studios,我们每天都在努力寻求这种平衡。随着每个新项目的推进,我们的游戏库不断扩充,我们希望自己能成为更优秀的 Unity 开发者,也能成为更出色的团队合作者。
