Article

Unity WebGL 小游戏实战 09:性能分层优化(CPU/GPU/内存/加载全链路)

路线阶段:Unity WebGL 小游戏实战第 9 章。
本章目标:从“卡了再修”转为“预算驱动的持续优化”。

学习目标

完成本章后,你应该能做到:

  1. 为 WebGL 项目建立 CPU/GPU/内存/加载四维性能预算。
  2. 快速定位热点并制定分层优化动作。
  3. 通过对象池、批处理、资源裁剪稳定帧时间。
  4. 建立性能回归基线,避免版本迭代回退。

性能预算(首版建议)

目标设备:中端笔记本浏览器 + 主流桌面浏览器。

  1. 帧率目标:60fps,最低不低于 45fps
  2. CPU 主线程预算:< 10ms(稳态),峰值 < 16ms
  3. Draw Calls:常态 < 220,高峰 < 320
  4. 内存峰值:< 420MB
  5. 首屏可交互时间:< 8s(冷启动)。

分层诊断方法

1. CPU

重点看:

  1. AI 决策循环
  2. 波次生成与销毁
  3. UI 重建
  4. 物理查询

2. GPU

重点看:

  1. 透明层过绘
  2. 材质切换导致批次拆分
  3. 阴影和后处理开销

3. 内存

重点看:

  1. 临时分配(每帧new)
  2. 资源驻留上限
  3. 场景切换后是否回落

4. 加载

重点看:

  1. 首包体积
  2. 首战资源预热缺失
  3. 网络失败重试与缓存策略

CPU 热路径优化

1. 降频更新

public sealed class ThrottledSystem : IUpdatable
{
    public int Order { get { return 280; } }

    private readonly float _interval;
    private float _acc;
    private readonly Action<float> _onTick;

    public ThrottledSystem(float interval, Action<float> onTick)
    {
        _interval = interval;
        _onTick = onTick;
    }

    public void Tick(float dt, float unscaledDt)
    {
        _acc += dt;
        if (_acc < _interval) return;

        var step = _acc;
        _acc = 0f;
        _onTick(step);
    }
}

适用:远距离敌人 AI、低频统计、活动倒计时刷新。

2. 批量物理查询

避免每个敌人单独射线,改为帧内分批查询或共享结果缓存。

3. 禁止每帧 LINQ/字符串拼接

日志、UI 文案统一节流和缓存格式化。

GPU 优化

1. 合批策略

  1. 同类敌人统一材质。
  2. UI 图集化,减少 Canvas 切割。
  3. 特效分层,控制同时激活数量。

2. 过绘治理

  1. 减少全屏透明特效持续叠加。
  2. 降低大面积半透明UI层级。
  3. 远景特效改低配版本。

3. 阴影策略

WebGL 首版建议:

  1. 主角色保留关键阴影。
  2. 普通敌人关闭实时阴影,使用投影贴图。

内存治理

1. 临时对象池化

public sealed class TempListScope<T> : IDisposable
{
    public List<T> List;

    private TempListScope(List<T> list)
    {
        List = list;
    }

    public static TempListScope<T> Rent()
    {
        return new TempListScope<T>(ListPool<T>.Get());
    }

    public void Dispose()
    {
        ListPool<T>.Release(List);
        List = null;
    }
}

2. 资源回收窗口

  1. 关卡结算时 UnloadUnusedAssets
  2. 切场景后延迟一帧再触发 GC。
  3. 长局中按阈值触发轻量回收。

3. 对象池容量上限

池化不是无限扩张,需有上限与裁剪规则。

加载优化

  1. 关键资源前置预热(技能特效、命中音效、结算UI)。
  2. 非关键资源延迟加载。
  3. 分阶段进度展示,减少“假卡死”感知。

性能埋点

每 5 秒记录一次:

  1. 平均帧时长
  2. 95分位帧时长
  3. Draw Calls
  4. 活跃敌人数
  5. 内存估计值
FoundationLog.Info("Perf",
    "fps=" + fps +
    " p95=" + p95Ms +
    " draw=" + drawCalls +
    " aliveEnemy=" + aliveEnemy +
    " mem=" + memMb);

性能回归测试

建立 3 个固定压测场景:

  1. 普通局(20敌人)
  2. 高压局(40敌人+密集特效)
  3. Boss局(演出+召唤)

每次版本发版前自动记录指标并对比基线。

与前面系统联动

  1. 导演系统:依据性能预算动态限制 MaxAliveEnemy
  2. 资源系统:按场景分组加载和释放。
  3. UI系统:关键面板节流更新。
  4. 埋点系统:输出性能趋势到看板。

WebGL 特殊注意

  1. 浏览器标签页切换会影响帧率统计,需要过滤无效样本。
  2. 不同浏览器性能差异大,基线要分浏览器类型。
  3. 避免依赖编辑器下的性能表现做最终结论。

验收清单

  1. 高压场景帧率达到预算目标。
  2. 版本升级后性能基线未明显回退。
  3. 内存峰值可控且关卡结束后可回落。
  4. 加载阶段用户可感知进度,不出现长时间无反馈。

常见坑

坑 1:只看平均帧率

P95/P99 才能反映卡顿体感。

坑 2:一次性做大量优化不验证

应分批验证,避免功能回归。

坑 3:优化与玩法系统强耦合

建议通过参数和中间层控制,减少业务侵入。

本月作业

完成“性能预算守门器”:

  1. 为三种压测场景设定阈值。
  2. 自动导出性能报告与版本对比。
  3. 若超阈值,构建流程给出阻断提示。

下一章进入 Unity WebGL 小游戏实战 10:发布运维与版本回滚(灰度、监控、快速止损)。