Files
zeling_v2/Docs/Design/27_PerformanceBudgetGuide.md
2026-05-08 11:04:00 +08:00

13 KiB
Raw Permalink Blame History

27 · 性能预算指南

适用范围 全部系统
所属文档集 ← 返回索引 · 总览


目录

  1. 帧时间预算
  2. GC 分配预算
  3. 每平台性能目标
  4. 对象池容量公式
  5. 物理配置规范
  6. 动画导入配置规范
  7. UI Canvas 批处理规范
  8. 音频预算
  9. Profiler 使用规范
  10. 性能检查清单PR 门禁)

1. 帧时间预算

目标帧率

平台 目标帧率 帧时间预算
PC高配 144 fps 6.9 ms
PC低配 60 fps 16.6 ms
Steam Deck 60 fps 16.6 ms
Nintendo Switch未来 30 fps 33.3 ms

主要基准:以 60 fps / 16.6 ms 为核心优化目标。

帧时间分配16.6 ms

16.6 ms 总预算分配:

  ├─ Unity 引擎开销渲染、物理、GC  ≤ 5.0 ms
  │   ├─ Physics2D.FixedUpdate         ≤ 1.5 ms
  │   ├─ Camera + Culling              ≤ 1.0 ms
  │   └─ 渲染Draw Call + Sort      ≤ 2.5 ms
  │
  ├─ 游戏逻辑                          ≤ 6.0 ms
  │   ├─ PlayerController + States     ≤ 1.0 ms
  │   ├─ EnemyAIBehaviorDesigner   ≤ 2.0 ms≤ 20 个活跃敌人)
  │   ├─ EnemyNavAgentPathBerserker≤ 1.0 ms
  │   ├─ AnimancerComponent 驱动       ≤ 1.0 ms
  │   └─ 其他系统SO 事件/UI/音频)   ≤ 1.0 ms
  │
  ├─ Feel / MMFeedbacks                ≤ 1.5 ms
  │
  └─ 余量                              ≈ 4.1 ms

超时警报阈值

系统 Profiler Marker 警报阈值
PlayerController [BaseGames] Player.Update > 1 ms
EnemyAI全部 [BaseGames] Enemies.AI > 2 ms
GlobalObjectPool.Prewarm [BaseGames] Pool.Prewarm > 50 ms仅启动一次
SaveManager.Save [BaseGames] Save.Write > 10 ms异步写入主线程触发

2. GC 分配预算

限制

范围 限制 说明
每帧 GC 分配(游戏进行中) ≤ 256 KB 超出可能导致 GC Pause
单次 GC Pause如需触发 ≤ 1 ms 使用 Incremental GC
游戏启动时(预热阶段) 不限制 仅启动一次,不影响游玩
场景加载时(加载画面覆盖) 不限制 加载画面覆盖,不可见

Unity 设置

Project Settings → Player → Other Settings:
  ✅ Use Incremental GC
  GC Incremental Time Slice: 1 ms

Project Settings → Quality:
  ✅ Enable V-Sync或手动 Application.targetFrameRate = 60

常见 GC 热点及替代方案

反模式 替代方案
GetComponent<T>() 在 Update 中 Awake 缓存引用
string 拼接/Format 在热路径 预分配 StringBuilderstring.Format 仅在非热路径
LINQ.Where().ToList())在 Update 手写 for 循环 + 预分配 List<T>
事件 += lambda 每帧注册 OnEnable/OnDisable 订阅/取消
new Vector2/3() 频繁在 Update 复用局部变量,不 new
Physics2D.RaycastAll() Physics2D.RaycastNonAlloc() + 预分配数组
Coroutine yield return new WaitForSeconds() 每帧 缓存 WaitForSeconds 实例(static readonly

推荐 Profiler 内存标记

// 每个系统的 Update/FixedUpdate 包裹 ProfilerMarker
static readonly ProfilerMarker s_Marker = new ProfilerMarker("[BaseGames] System.Update");

void Update()
{
    using var _ = s_Marker.Auto();
    // 逻辑
}

3. 每平台性能目标

渲染

指标 PC 低配 目标 Steam Deck 目标
Draw Calls / 帧 ≤ 150 ≤ 100
Batches / 帧 ≤ 80 ≤ 60
SetPass Calls / 帧 ≤ 20 ≤ 15
Sprite Atlas 最大尺寸 2048×2048 1024×1024
像素填充率(全屏四边形) 避免超过 3 层透明叠加

活跃对象上限

对象类型 同屏上限 超出时行为
活跃敌人 20 超出范围的敌人进入"休眠"(禁用 AI保留碰撞
活跃弹射物 64 对象池:超出上限时最老的 Projectile 自动回收
活跃粒子系统 32 Feel MMF_Player 内置最大粒子预算
活跃 MMF_Player 16 低优先级 Feedback 在帧预算紧张时跳过

4. 对象池容量公式

弹射物对象池

池容量 = 最大活跃敌人数 × 每敌人最大弹射物数 × 齐射数 × 安全余量
       = 20 × 4 × 2 × 1.5
       = 240取整为 256
参数 说明
最大活跃敌人 20 见活跃对象上限
每敌人最大弹射物 4 Boss 弹幕模式最大同时弹射物
齐射数 2 一轮连发的发射次数
安全余量 1.5 防止池枯竭
推荐池容量 256 向上取 2 的幂次

粒子/特效对象池

池容量 = 弹射物命中率估计 × 命中特效数量 × 并发系数
       = 64活跃弹射物× 0.3(命中率) × 2特效种类
       = 38取整为 48

敌人对象池

  • Boss 敌人:不使用对象池(唯一实例,场景卸载时销毁)
  • 普通敌人:预热数量 = 房间最大敌人数 × 2(支持密集战斗)

池枯竭策略

// GlobalObjectPool 当池空时的回退策略
public enum PoolExhaustPolicy
{
    ReturnNull,       // 返回 null调用方需处理推荐用于弹射物
    RecycleOldest,    // 强制回收最老实例(推荐,用于粒子特效)
    ExpandPool,       // 动态扩容(仅允许在加载/过渡场景时使用)
}

5. 物理配置规范

Fixed Timestep

Project Settings → Time:
  Fixed Timestep:    0.02 50 Hz即 50 次/秒 FixedUpdate
  Maximum Allowed Timestep: 0.1(防止 Spiral of Death

不要使用默认 0.02 以外的值除非特别测试验证。60fps 游戏中 FixedUpdate 每帧约调用 0.83 次(略低于每帧 1 次),游戏逻辑不依赖精确的 FixedUpdate 频率。

Physics2D 配置

Project Settings → Physics 2D:
  Velocity Iterations:  6默认 8降低以减少 CPU 开销)
  Position Iterations:  2默认 3
  Queries Hit Triggers: 仅在需要时开启(逐 Collider 使用 ContactFilter2D
  Layer Collision Matrix: 精确配置,禁用不需要的层对碰撞

推荐碰撞层矩阵

Player Enemy Projectile Wall OneWay Trigger
Player
Enemy
Projectile
HitBox
HurtBox

HitBox/HurtBox 仅与对应目标层碰撞,减少不必要的碰撞查询。

Rigidbody2D 最佳实践

  • 玩家/敌人:CollisionDetectionMode = Continuous(防止穿墙)
  • 弹射物:CollisionDetectionMode = Continuous(高速物体必须)
  • 静态平台:使用 StaticRigidbody2D 或仅 Collider2D(无 Rigidbody2D

6. 动画导入配置规范

帧率策略

动画类型 推荐帧率 说明
Idle待机循环 12 fps 动作缓慢,低帧率不明显
Run奔跑循环 20 fps 需要流畅感
Jump / Fall 12 fps 空中动作简单
Attack精确帧数 60 fps HitBox 需要精确到帧
Boss 攻击预警 60 fps 电报动作需要精确控制
UI 动画 30 fps 界面元素不需要极高帧率
Cutscene 24 fps 电影帧率

Animancer 支持动画混合,帧率差异在混合时自动处理,无需担心不同帧率动画之间的转换问题。

导入设置

AnimationClip Import:
  ✅ Resample Curves      → true提升采样精度
  Loop Time               → 循环动画 true一次性动画 false
  Compression             → OptimalUnity 自动选择最佳压缩)
  Anim. Compression       → Keyframe Reduction减少关键帧存储
  Error (Position/Rotation/Scale): 0.5 / 0.5 / 0.5(默认即可)

7. UI Canvas 批处理规范

Canvas 分层策略

Canvas 更新频率 推荐类型
主 HUDHP/Soul/Geo 按需(事件驱动) Screen Space - Camera
Boss HP 条 按需 与主 HUD 同 Canvas
小地图 按需 独立 Canvas复杂更新
暂停菜单 静态(打开时) Screen Space - Overlay
对话 UI 按需 独立 Canvas
加载画面 静态 独立 Canvas最高层级

Draw Call 限制

规则
单个 Canvas 最大 Draw Calls 8
共享 Sprite Atlas 尺寸 512×512HUD/ 1024×1024全屏 UI
透明 UI 元素叠加层数 ≤ 4 层(超出会导致 Overdraw

避免 Canvas 脏标记

  • 动态数值HP、Geo使用 TextMeshPro,仅在数值变化时更新 text 属性
  • 动画元素Blood flash、Soul 充能)使用独立子 Canvas 隔离脏标记
  • 不在 Update 中每帧刷新 UI 文本

8. 音频预算

指标 限制
同时播放的 AudioSource 数量 ≤ 32平台限制通常为 32~256保守取低值
BGM 同时层数(自适应音乐) ≤ 4 层(探索基础层 + 3 个叠加层)
单个 SFX 最大并发实例 ≤ 4同一 SFX 类型,超出时最老实例停止)
音频剪辑内存预算 ≤ 50 MB所有已加载音频资产总大小

音频导入规范

类型 格式 Load Type Compression
BGM长曲 .ogg Streaming Vorbis Q50
SFX短促 .wav Decompress on Load PCM
Ambient环境 .ogg Streaming Vorbis Q40
UI 点击音效 .wav Decompress on Load PCM

9. Profiler 使用规范

必须加 ProfilerMarker 的位置

using Unity.Profiling;

// 格式:[BaseGames] 命名空间.类名.方法名
static readonly ProfilerMarker s_PlayerUpdate      = new("[BaseGames] Player.Update");
static readonly ProfilerMarker s_EnemyAIUpdate     = new("[BaseGames] Enemies.AI.Update");
static readonly ProfilerMarker s_PoolPrewarm       = new("[BaseGames] Pool.Prewarm");
static readonly ProfilerMarker s_SaveWrite         = new("[BaseGames] Save.Write");
static readonly ProfilerMarker s_SaveRead          = new("[BaseGames] Save.Read");
static readonly ProfilerMarker s_SceneLoad         = new("[BaseGames] Scene.Load");
static readonly ProfilerMarker s_DialogueParse     = new("[BaseGames] Dialogue.Parse");

性能分析 SOP每里程碑

  1. 深度模式录制 300 帧在最复杂场景Boss 战 + 大量弹射物 + Feedback
  2. 关注热点CPU Usage 视图,过滤 [BaseGames] 标签,检查超时项
  3. GC 检查Memory 视图,确认每帧 GC 分配 ≤ 256 KB
  4. 物理热点Physics 2D 检查 ContactCount,确认碰撞层矩阵设置正确
  5. 渲染Frame Debugger 检查 Draw Call确认 Atlas 打包正确

内存快照 SOP

  • 每个主要场景加载后各取一次内存快照
  • 对比两次快照的内存增量确认无场景加载内存泄漏Addressable Handle 是否全部释放)

10. 性能检查清单PR 门禁)

提交 PR 前必须自查以下所有项,全部通过方可合并:

代码层

  • Update / FixedUpdate 中无 GetComponent<T>()FindObjectOfType<T>()
  • Update / FixedUpdate 中无 new 分配(除 ValueType struct 栈分配外)
  • LINQ 仅在初始化路径使用,运行时热路径不使用
  • 所有 Coroutine WaitForSeconds 使用 static readonly 缓存
  • Physics2D.RaycastAll / OverlapCircleAll 替换为 NonAlloc 版本
  • 新增的 MonoBehaviour 有 ProfilerMarker 包裹(若在热路径)

对象池

  • 新增弹射物/粒子使用 GlobalObjectPool 而非 Instantiate/Destroy
  • 池容量根据 §4 公式计算并配置在 SO 中
  • 池枯竭策略已选择并测试

物理

  • 新增 Collider 的碰撞层已在 Layer Collision Matrix 中配置
  • 高速移动对象(弹射物/玩家冲刺)使用 Continuous 碰撞检测

UI

  • 新增 UI 组件不在 Update 中每帧刷新 Text
  • Canvas 层级合理(不频繁更新的元素不与频繁更新的元素同 Canvas

音频

  • 新增 SFX 格式为 .wavBGM 格式为 .ogg
  • 高频 SFX脚步/攻击)设置了并发实例上限

Addressables

  • LoadAssetAsync 的 Handle 在合适时机 Release
  • 场景卸载时 Handle 统一释放(无泄漏)