一、手游的性能预算:一杯"60 帧的水"
"小陈,手机游戏有个残酷的现实:性能是'预算',不是'无限资源'。"老周说:
手游性能预算(每帧 16.7ms @ 60 FPS):
渲染:8-10ms(画面绘制)
逻辑:3-5ms(游戏逻辑/AI/物理)
网络:1-2ms(收发包)
其他:1-2ms(UI、音频)
合计:约 16.7ms(超过 = 掉帧!)
低端手机预算更紧:
渲染:6ms
逻辑:3ms
→ 必须在"最差的手机"上跑 30 FPS 以上
→ 优化目标:让低端机也流畅
帧率标准:
60 FPS:流畅(高端机目标)
30 FPS:可玩(低端机底线)
20 FPS 以下:玩家骂街(不能接受)
性能分析工具:
Unity Profiler(看 CPU/渲染/内存)
Frame Debugger(看每帧绘制了什么)
真机 Profiling(手机上实测,电脑测不准!)
"先定预算,再优化——没有预算概念,就是瞎优化。目标:低端手机 30 FPS,中端 45+,高端 60。 这是手游性能的标准目标。"
二、Draw Call:渲染的"头号敌人"
"2D 游戏性能优化,第一个要打的大怪:Draw Call(绘制调用)。"老周说:
Draw Call 是什么:
每帧告诉 GPU"画这个东西"的次数
每次"画一个东西" = 一次 Draw Call
100 个精灵 = 100 次 Draw Call(如果不合并)
为什么 Draw Call 多会卡:
CPU 和 GPU 之间通信有开销(每次绘制要"提交命令")
切换纹理/材质更贵(GPU 要重新加载)
手机 GPU 尤其敏感:几百次 Draw Call 就可能卡
优化目标:
同屏 Draw Call 控制在 100 次以内(移动端)
2D 手游:50 次以下更稳
三大杀手锏:
① 图集(Sprite Atlas):多张小图拼一张大图
→ 同一张图里的精灵,可以"一次画完"(合批)
② 合批(Batching):相邻的精灵合并成一次绘制
→ Unity 自动做(相同材质/相同图集的能合)
→ 前提:用同一张图集、同一材质
③ 减少透明/独立材质:材质种类越少越好批
"记住第四章的图集吗?现在知道为什么重要了——图集直接决定 Draw Call 数量。 一张 1024 图集能放几百个小兵贴图,它们就只算 1 次绘制。"
三、对象池:别让对象"生生死死"
"小陈,小兵每 10 秒死一批、又生成一批——如果每次都'创建/销毁'对象,内存会抖成心电图。"老周说,"用对象池(Object Pool)。"
对象池(Object Pool):
不销毁,回收复用!
错误写法:
minion = new Minion() # 创建(分配内存)
... 小兵死了 ...
Destroy(minion) # 销毁(释放内存)
→ 频繁创建/销毁 = 内存碎片 + GC 卡顿
正确写法(对象池):
池子:预先创建 20 个"空闲小兵"
需要小兵:从池里"取出"一个(激活)
小兵死了:不是销毁,是"回收"到池里(隐藏)
下次要用:再取出(复用!)
→ 全程没有创建/销毁,性能稳定
对象池的应用:
小兵/野怪(大量生灭)
子弹/弹道(频繁发射)
伤害飘字(每击一个)
粒子特效(频繁触发)
音效对象
实现骨架(伪代码):
class ObjectPool:
def __init__(self, factory, size):
self.pool = [factory() for _ in range(size)]
def get(self):
obj = self.pool.pop() # 取出
obj.SetActive(True) # 激活
return obj
def release(self, obj):
obj.SetActive(False) # 隐藏
self.pool.append(obj) # 回收
"对象池是游戏性能的第一课——MOBA 里小兵、飘字、特效全是高频生灭对象,没有对象池,手机内存和 GC 分分钟爆炸。"
四、内存管理:别让内存"越用越多"
"手机内存有限,游戏内存要'精打细算'。"老周说:
内存管理要点:
① 资源加载后记得卸载:
场景切换时,卸载旧场景资源
不用的 AB 包:引用计数为 0 时卸载
② 避免泄漏:
事件监听用完要移除(OnDestroy 清理)
不再用的对象要释放引用
③ 纹理内存控制:
每张纹理都是显存(1024x1024 RGBA = 4MB)
用压缩格式(ASTC/ETC2,省 75% 内存)
限制同屏贴图大小(mipmap 按需)
④ 对象池(上一节)减少 GC:
GC(垃圾回收)是移动端卡顿元凶
避免每帧 new 对象(用对象池/复用)
⑤ 音频内存:
音乐用流式播放(不整个加载进内存)
Unity 内存监控:
Profiler 的 Memory 面板
Resources.UnloadUnusedAssets(卸载未用资源)
"内存优化的本质:'用多少、留多少、不浪费'——泄漏一个对象没事,泄漏几百个,游戏就 OOM 闪退了。'内存曲线平稳'是健康游戏的标志。"
五、帧率优化的"三张王牌":合批、LOD、降级
"除了 Draw Call 和对象池,还有三张组合王牌。"老周说:
① 合批(Batching):
静态合批:不动的对象提前合并
动态合批:动的对象自动合并(有开销限制)
图集 + 同材质是合批前提
② LOD(Level of Detail)多级细节:
近处的对象:高细节(粒子多、帧率高)
远处的对象:低细节(粒子少、帧率低)
不可见的对象:不更新(视野剔除 Culling)
③ 性能降级(Quality Scaling):
根据手机性能自动调整画质:
低端机:特效减半、分辨率降档、关抗锯齿
高端机:全特效、高分辨率
给玩家"画质设置"选项(低/中/高)
用帧率反馈动态调整(卡了自动降级)
视野剔除(Culling):
屏幕外的对象不渲染(相机看不到的不用画)
2D 游戏:只在视野范围内更新/渲染单位
→ 峡谷很大,但同一时刻玩家只看得到一小块
→ 视野外的小兵不用每帧更新!(第七章的分层更新也是)
MOBA 实战:
远处野怪:10 FPS 更新、低特效
近处战斗:60 FPS、全特效
→ 性能预算花在"玩家盯着的地方"
"性能优化的哲学:'把钱花在玩家看得见的地方'——同一时刻玩家只盯着屏幕中心那一小块,其他地方可以偷工减料(但别被发现)。LOD、剔除、降级,全是这个思想的落地。"
六、网络与逻辑的性能:别让"代码"拖后腿
"别以为性能优化只是渲染的事——逻辑和网络也可能成为瓶颈。"老周说:
逻辑性能优化:
① 用数据驱动(第五章)代替大量 if-else
② 避免每帧做"重活":
路径搜索:隔帧算 / 缓存结果(小兵走固定路线)
碰撞检测:用空间分区(网格划分,只查附近)
③ 批量处理:
同一类型的更新合并(一次性循环)
少用 foreach 每帧创建迭代器(GC 开销)
网络性能:
打包频率:20-30 tick 是平衡点(第八章)
压缩:位置用短整数(省带宽 = 省 CPU 解包)
避免频繁重连:连接池、心跳优化
Profiler 检查逻辑瓶颈:
CPU 里哪个函数耗时最多?
→ 找出热点 → 优化它(其他别管)
"二八原则":80% 的耗时在 20% 的代码里
"性能优化的流程:Profiler 找热点 → 只优化热点 → 再测。 别凭感觉乱优化——用数据说话。这和运维里'先定位瓶颈再动手'是一个道理(网络季讲过)。"
七、真机测试矩阵:性能优化最后一步
"优化完,要真机测试——不同手机表现天差地别。"老周说:
真机测试矩阵:
高端机(旗舰):目标 60 FPS 满帧
中端机(主流):目标 45 FPS 稳定
低端机(入门):目标 30 FPS 不卡
老机型(3-4 年前):目标 25-30 FPS
测试维度:
帧率(FPS)/ 掉帧次数
内存峰值 / 是否闪退
发热(手机温度)
耗电
弱网(网络延迟/抖动下的表现)
工具:
Unity Profiler 真机模式
Android:Systrace / 厂商性能工具
iOS:Instruments / Xcode
第三方:PerfDog(腾讯,游戏性能测试常用)
发布门槛:
最低支持机型必须 30 FPS
不允许闪退(崩溃率 < 0.1%)
发热不能烫手(< 45°C)
"性能优化不是'调完就行',是'在真机上验证达标'——PPT 上说 60 帧没用,得拿最差的那台测试机跑给你看。'最低配手机上的表现',才是手游性能的及格线。"
八、章末:老周的第十一层总结
第十一层:性能优化
├── 性能预算:每帧 16.7ms(渲染/逻辑/网络分账)
│ 目标:低端机 30 FPS 不卡
├── Draw Call:绘制调用(图集+合批+材质统一)
├── 对象池:高频对象回收复用(小兵/飘字/特效)
├── 内存:资源卸载/防泄漏/压缩纹理/减少 GC
├── LOD + 剔除:远处低细节,看不见不更新
├── 性能降级:按手机性能自动调画质
├── 逻辑优化:Profiler 找热点,只优化热点
└── 真机测试:不同机型达标才算数(不烫不卡不闪退)
下一章预告:
性能达标了!游戏终于做完了——
但要"上线"还有最后一关:测试、上架、运营、反外挂!
—— 终章:测试发布与问题对照表。
"小陈,摸一下手机——不烫了!帧率稳定 60,低端测试机也能 30 帧。'云间对决'终于像个'能上线的产品'了。"老周长舒一口气,"最后一步:测试、发布、运营、反外挂——这一关过了,'云间对决'就真的能和玩家见面了。走,终章见。"