第十一章

性能优化:让手机不烫不卡

第十一层 · DrawCall/对象池/内存 从 60 帧掉到 20 帧,怎么救回来

一、手游的性能预算:一杯"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 帧。'云间对决'终于像个'能上线的产品'了。"老周长舒一口气,"最后一步:测试、发布、运营、反外挂——这一关过了,'云间对决'就真的能和玩家见面了。走,终章见。"

✌ 语言