我们从一个laggy的游戏节目防御型WebGL游戏转变为一个增量游戏,其中你需要用一只蜗牛把沙漠变成绿色。游戏名叫Feed the Forest,你可以在Steam上玩游戏的demo。

一些技术细节:

我们开始时只.spawn一些对象(最多10k - 网格大小是100x100),然后完全依赖SRP来进行批处理。性能迅速下降,我们不得不使用实例渲染+平面数组来加速迭代。所以现在每个mesh类型你spawn的初始mesh类型都以简单渲染器开始,并且仍然依赖于SRP批处理。然后随着植物的生长,我们有缓存的材料,0.1的增量,并应用这些,以便批处理工作正常(因为我们改变物质颜色时生长,批处理在中间步骤中会破裂)。一旦植物完全生长,我们就禁用常规Unity渲染器,并应用我们的自定义实例渲染逻辑。实际上,我们有一个单例,用于跟踪不同植物类型,哪些植物类型被分组到连续的数组中,我们只需将它们推到GPU使用实例渲染API。

每个植物周围的白线是SDF。我们有3组:1.植物+SDF仍在生长中;2.植物完全生长,SDF静态;3.非可绘制区域的遮罩 - 也静态SDF。这使得我们只需重新计算第二个SDF时玩家移动。其他SDF被保存到自定义渲染文本中,我们在着色器中使用min(实际上是三个自定义渲染文本,但我们只重新计算第二个)。这样我们只需要重新计算大约50个对象每帧,这使得我们可以在WebGL构建中使用SDF。如果我们超过500个对象,那么游戏就会变得卡顿,因此缓存是必要的。

最后,由于我们每帧都实例化了数百个对象,我们使用对象池来实现几乎所有的东西。当你首先启动游戏时,我们会预先填充每个类别的池中最多8k个对象,这样在游戏中玩游戏时会有一个相对平滑的体验(WebGL仍然有一些问题)。但是,池化音频源仍然存在一些问题,有时我会注意到当从池中获取音频源播放音频时,音频会裂裂。唯一的解决方案是限制音频池的大小。如果我们使用FMOD的话,问题就解决了。

令人惊讶的是,更新地砖逻辑的速度相当快,所以没有任何优化 - 我们在管理脚本中存储一个2D数组中的地砖结构列表,并在Update()中迭代它们。游戏脚本中其他部分查看这个列表并执行相应的操作。花费大约0.5ms每帧,约10个对象。我们曾经考虑过使用DOTS,但根据WebGL文档,它似乎并不起作用。由于CPU时间似乎很低,我们决定不再深入调查。虽然我想尝试它,或者尝试另一个ECS系统,因为使用平面数组并且保持API友好是相当麻烦的。

我仍然不确定如何处理Text Mesh Pro的垃圾回收问题。在某些情况下,尤其是在你绘制大量时,我们会有大量GUI更新,我们会使用ToString并应用到TMP文本中。然而,我改变了大多数这些操作使用StringBuilder(TMP有一个重载),但仍然似乎会分配大量内存。TMP有一个SetCharArray函数,但我没有足够的时间来调查。希望您能提供任何优化此部分的技巧。

如果您有任何技术问题或建议,我将很高兴听到您的意见:)