在 2026 年 1 月 24 日的 Unity User Group 深圳站活动中,诗悦网络星辰工作室技术专项组负责人黎其桂带来演讲《永远的蔚蓝星球》小游戏性能优化实战,与大家分享在保证高度还原 app 端的效果的前提下,主创团队在包体、内存、渲染等方面付出的心力与经验,以及如何利用团结引擎的优势进一步减少引擎侧的开销。本文为演讲全程实录,点击阅读原文,可下载演讲资料。
![]()
黎其桂:大家好,我是来自诗悦网络星辰工作室技术专项组的黎其桂。很高兴今天能来到这里,跟大家一起分享《永远的蔚蓝星球》这款微信小游戏的性能优化实战经验。
首先简单介绍一下产品。《永远的蔚蓝星球》是一款随机合成萌系塔防手游,好玩上头,已于去年 6 月底公测上线。在完成 APP 端上线后,我们迅速投入到微信小游戏的适配与上线准备中。从 7 月到 8 月,游戏在微信小游戏平台上取得了出色的成绩,最高曾登顶畅销榜第 4 名,目前稳定保持在畅销榜前 20 名。
这次分享,我将重点介绍我们针对微信小游戏平台所做的性能优化工作。在微信平台开发,往往会遇到平台本身的局限性,我们为此在包体、性能、渲染等多个方面投入了大量精力。
Part 1:WASM 包体
包体优化的第一步是常规手段:清理无用模块,并开启最高级别的代码裁减。但开启高级别裁减后,有时会错误地裁掉一些必要代码。这时我们就需要结合 LinkXML 文件,将需要保留的关键代码标记出来,同时将代码裁减级别调到最高。
![]()
因为我们使用了 Lua 来实现系统和界面的逻辑,在 Lua 的封装(Wrap)代码中,往往存在一些在 Lua 端很少用到的 C# 接口或代码。我们也将这部分内容进行了优化和裁减。具体做法是,先收集所有已导出 wrap 的类型和成员到一个列表中,再遍历所有 lua 文件,通过字符串匹配的方式判断已导出的 wrap 是否有调用,剔除掉未被调用的 wrap,最终把需要过滤的成员函数接入到 tolua 的导出过滤里面,最终把整包大小(webgl.wasm)优化到了 30m 以内。
![]()
最后,我们使用了Wasm Analysis工具(团结引擎中已经集成了此工具)来校验裁减结果是否符合预期。这个工具非常好用,能够帮助我们精准定位问题,高效解决包体体积的问题。
![]()
Part 2:WASM 代码分包
第二,代码分包。代码分包在一定意义上来说,是小游戏平台再进一步的代码裁剪的手段,因为它是发布前,通过真实跑游戏去收集过程中使用到的函数,这些收集到的代码函数就作为首包,在进入游戏时加载出来,剩下的用到才会去线上拉取。
代码分包对开发者而言有利有弊。好处在于,分包能大大降低首包大小,提升启动速度和有效转化,同时也能降低首包编译内存;坏处在于,既要“小”,又要“全”,这在工程上比较麻烦。微信平台的分包机制在安卓和 iOS 上有所不同:安卓可以在后台完整加载分包,而 iOS 则需要运行时逐函数拉取。如果 iOS 分包不完整,触发了连续 5 个以上的分包拉取,用户体验会非常糟糕。因此,我们必须做到“既小又全”。
我们的做法是:
首次收集后进行人工微调。
通过工具实现增量管理。
遵循经验:首包占比 30% 是一个较优的比例。
这里有必要介绍一下我们的分包辅助工具。在做微信分包时,我们发现分包界面提供了两个手动管理的按钮。出于好奇点击后,发现“新增函数”和“总收集函数”的格式与 symbol 符号表文件里面非常相似。于是我们基于这个思路,通过反序列化了 symbol 文件,用新包的 symbol 去 diff 旧包,导出一份新增的函数列表,用这份列表导入到微信分包工具中就可以保证业务迭代的代码都能加到首包里面。这样能做到版本增量更新后脚本自动跑增量分包,提高效率提高准确率。最终也能保持分包后,首包函数占比 30% 左右,整体运行流畅。
![]()
Part 3:大量同屏元素
对于肉鸽塔防游戏来说,“爽感”至关重要,而这往往意味着满屏的怪物、满屏的特效和满屏的飘字。这种感官体验带来的是巨大的性能挑战。
以《永远的蔚蓝星球》为例,游戏场景中存在高达 5000+ 的粒子系统(Particle System)、同屏超过 200+ 怪物以及 3000+ 的伤害飘字。为了保证还原 app 的效果和品质,数量不能减少,要在小游戏平台做到大量级,同时兼顾性能。面对这种同屏场景,无论是在内存、CPU 还是渲染层面,都带来了巨大的压力。

Part 3-1: GPU 序列帧
面对原生 Particle System 高昂的性能消耗(每个组件约占用 10KB),我们的第一反应是:尽量不用粒子系统。虽然它的表现力好,但 DC 过高,性能代价过大。
我们转而采用GPU 序列帧方案。之所以叫 “GPU 序列帧”,是因为常规序列帧(如使用 Animate 组件或脚本轮询图集)是运行在 CPU 上的。而我们的优化思路是,在微信小程序这样的平台,CPU 压力本就很大,因此要把能转移到 GPU 的逻辑都转移到 GPU 上去执行,所以就定义它为 GPU 序列帧。

具体实现上,关键点是利用了GPU Instancing。我们将粒子的表现效果烘焙到图集或图片中,然后在 GPU 中进行计算和还原。优化后,原来 1500+ DrawCall 的场景可以通过 GPU Instancing 合批到 233 个 batches,简单而有效。

Part 3-2: GPU粒子
既然序列帧很有效,为什么还要做 GPU 粒子?因为序列帧有其局限性。当面对全屏播放、帧数很高的特效时,序列帧图集会变得异常庞大,得不偿失。另外不支持 3D,比如立体透视的龙卷风、拖尾等,而且存在填充率问题,overdraw 很高。

因此,我们引入了GPU 粒子的概念,使用VAT(Vertex Animation Texture,顶点动画贴图)。它的核心思想是,把大网格、ID 索引、粒子的属性和材质的属性都烘焙下来,装到顶点着色器里,在顶点着色器中用 SV_VertexID 去逐步解析出粒子和粒子系统的 ID,再去获取对应下标的各项数据,从而将计算压力完全转移到 GPU。
![]()
在实践中,我们克服了两个主要难点:
动态变化的粒子记录:粒子系统的一大特点是粒子随着时间会不断的生成和消失,常规 VAT 做法以横坐标为粒子数、纵坐标为帧数,会导致贴图空间浪费。我们在烘焙粒子信息时,舍弃了粒子的固定顺序,把贴图宽度压缩为同时存在的最大粒子数目。虽然放弃了粒子的严格先后顺序,不能采样相邻帧做动画的平滑,但极大地压缩了贴图大小,降低了内存和带宽消耗。
![]()
多粒子系统网格烘焙:另一个难点是,特效可能包含多个粒子系统,而每一个粒子系统可能又由多种网格组成。这时候可以以单个粒子系统的单种网格为最小单元,把每个单元的贴图进行水平拼接。
![]()
实现时,在将资源烘焙好之后,需要记录两张索引表来还原粒子 ID 和粒子系统 ID,再由此读出烘焙好的各类信息。其中粒子 ID 可以用来读取逐粒子的信息,比如每个粒子的 Transform;粒子系统 ID 可以用来读取逐粒子系统的信息,比如材质参数。最终在顶点渲染阶段完成所有计算,这样逻辑、渲染都在 GPU 层面。
![]()
这一优化不仅降低了 CPU 负荷,还让我们可以精确控制渲染层级,解决了原生粒子系统在层级穿插上难以把控的问题。

Part 3-3: 怪物 GPU 动画
三是怪物 GPU 动画,还是继续压榨 GPU。在这个场景里有同屏 200+ 的怪物,对于 2D 游戏,动画常用 Spine 组件。但 Spine 组件存在内存占用高、CPU 开销大、实例无法共享的问题(200 个相同怪物就要 200 份消耗)。
![]()
我们的思路依然是 “CPU 转移到 GPU”,即将怪物动画也变成顶点动画(VAT)形式。在将 Spine 动画烘焙为 VAT 的过程中,同样遇到了挑战:
动画部件显隐记录:Spine 动画播放过程中会实时改变部件的显示和隐藏,例如武器投掷,特效附件的出现和隐藏,部件的切换等,为解决该问题,我们在自制的 Spine 导出工具中,把所有附件预烘焙到一个静态 Tpose 网格中(右图),当某一帧某个附件需要隐藏时,VAT 会在对应位置记录一个非法值(如负无穷大),播放时读取到非法的顶点位置信息就会自动隐藏。

部位之间的层级变化:动画播放过程中还会出现部件之间渲染顺序变化的问题,如左图,渲染顺序从右手-->身体-->左手,变成身体-->左手-->右手。原生 Spine 对部件之间渲染顺序的控制是通过顶点的 Z 轴坐标实现的,因此在烘培 VAT 时,会根据 SlotIndex 和 zSpacing 计算出每帧对应部件的 Z 轴坐标。

最终导出的核心资源:
TPose 网格(包括所有部件)
vat 贴图(黑色部分就是写入的非法值)
同个 Spine 的多个实例可以合成一个 DC。所以我们实现了能合批的 spine GPU 动画,而且上游制作流程无感。
![]()
优化后,GPU Spine 对比原生 spine 在耗时、drawcall 和内存上都有很大的优化。

![]()
Part 4:高性能飘字
我们的旧版飘字存在一个问题,它是通过一个大网格管理同屏中所有出现的飘字,并通过网格通道传递飘字信息的,在每次生成飘字的时候需要重建整个大网格,一方面重建网格会造成 CPU 耗时,另一方面重建后需要把网格重新从 CPU 传到 GPU 上,会造成带宽浪费。

我们最初的优化方案是预分一个大网格,将所有字体烘焙进去。但这仍是“旧版方案”,每飘一个字就要重建一次网格,仍然会造成性能峰值,并且在 CPU 向 GPU 传递大量数据时会产生严重的带宽锯齿。
![]()
我们的“新版方案”核心是使用 CBuffer 传递飘字信息,抛弃了每次更新都重建网格的方式,只需要通过材质修改对应 Cbuffer 即可完成。
![]()
改版后,不再通过网格通道记录数据,现在的网格只使用了一个 4 字节大小的 SV_VertexID。以往逐顶点记录的数据现在可以逐文本记录,并且对数据进行了充分压缩,充分利用了总量为 16k 的 Cbuffer,大幅降低带宽浪费,极致地完成了飘字效果还原。
![]()
优化后,新版飘字保证了原有飘字数量 3000+ 的前提下,可以看到左上角的带宽更稳定了,带宽峰值从每帧 8mb 降到每帧 3mb。

不仅线条平滑,在游戏高压场景下(合作模式第十层)最低帧率从 32 上升到 48。
![]()
Part 5:团结引擎侧优化
团结引擎带来的直接收益
除了业务层面的优化,《永远的蔚蓝星球》也受益于团结引擎的优化升级。团结引擎为小游戏提供了针对性的优化,带来了立竿见影的性能收益:
il2cpp 虚拟机优化:减少约 30MB 内存占用。
内存分配器 overhead 优化:减少约 7-11MB 内存。
粒子系统内存减少约 15MB:剔除未使用的粒子系统模块内存占用,粒子系统播放结束释放内存,数量越多收益越大,该功能默认开启。
TypeTree 内存优化:小游戏可只加载 MonoBehaviour 对应的 TypeTree,节省序列化文件内存,减少约 25MB 内存。
此外,Slim Global-Metadata、String Intern Pool、Dynamic Buff Reuse 等优化也贡献了可观的增益。
总计,从 Unity 引擎切换到团结引擎,我们直接获得了约 100MB 的内存下降。这对于开发者是巨大的好处,并且线上闪退率也直接砍半。
![]()
WebGL Metal
我们在 iOS 平台上还使用了Metal渲染后端。Metal 通过直接调用 iOS 原生 API,避免了 WebGL 到 Metal 的中间转换层,减少了 GPU 带宽消耗和功耗。从实际测试数据看,在帧率和亮度一致的情况下,使用 Metal API 在 iOS 机上的功耗下降了 17%,带来了更好的体感和设备温度表现。
![]()
以上就是我今天分享的小游戏优化。虽然是小游戏,但其中的优化技术却并不“小”。我们对包体、渲染、引擎底层等多个层面进行了深度探索和实践。未来,我们也期待着像团结引擎正在开发的Infinity 粒子系统这样的新技术能尽快上线,这能极大地减轻我们手动定制 VAT 的工作负担。
最后,希望大家还是要以一个精品游戏探索者的角色,坚持做难而正确的事情。
以上就是我的分享,希望大家可以一起交流学习,谢谢。
UUG(Unity用户组织)是一个连接本地 Unity 开发者的社群网络。十年来,全国各地的志愿者们在北京、上海、广州、深圳、杭州、武汉、成都、厦门、沈阳等城市成功举办了 50+ 线下交流活动。2026 年,我们希望能构建一个具备更多可能性的场域,让创作的能量在这里流动。
深圳 UUG 的演讲资料已上传至网盘,点击阅读原文下载,持续关注学习。
Unity 官方微信
第一时间了解Unity引擎动向,学习进阶开发技能
每一个“点赞”、“在看”,都是我们前进的动力

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.