376 lines
13 KiB
Markdown
376 lines
13 KiB
Markdown
# 27 · 性能预算指南
|
||
|
||
> **适用范围** 全部系统
|
||
> **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md)
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [帧时间预算](#1-帧时间预算)
|
||
2. [GC 分配预算](#2-gc-分配预算)
|
||
3. [每平台性能目标](#3-每平台性能目标)
|
||
4. [对象池容量公式](#4-对象池容量公式)
|
||
5. [物理配置规范](#5-物理配置规范)
|
||
6. [动画导入配置规范](#6-动画导入配置规范)
|
||
7. [UI Canvas 批处理规范](#7-ui-canvas-批处理规范)
|
||
8. [音频预算](#8-音频预算)
|
||
9. [Profiler 使用规范](#9-profiler-使用规范)
|
||
10. [性能检查清单(PR 门禁)](#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
|
||
│ ├─ EnemyAI(BehaviorDesigner) ≤ 2.0 ms(≤ 20 个活跃敌人)
|
||
│ ├─ EnemyNavAgent(PathBerserker)≤ 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 在热路径 | 预分配 `StringBuilder` 或 `string.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 内存标记
|
||
|
||
```csharp
|
||
// 每个系统的 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`(支持密集战斗)
|
||
|
||
### 池枯竭策略
|
||
|
||
```csharp
|
||
// 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 → Optimal(Unity 自动选择最佳压缩)
|
||
Anim. Compression → Keyframe Reduction(减少关键帧存储)
|
||
Error (Position/Rotation/Scale): 0.5 / 0.5 / 0.5(默认即可)
|
||
```
|
||
|
||
---
|
||
|
||
## 7. UI Canvas 批处理规范
|
||
|
||
### Canvas 分层策略
|
||
|
||
| Canvas | 更新频率 | 推荐类型 |
|
||
|--------|---------|---------|
|
||
| 主 HUD(HP/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×512(HUD)/ 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 的位置
|
||
|
||
```csharp
|
||
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 格式为 `.wav`,BGM 格式为 `.ogg`
|
||
- [ ] 高频 SFX(脚步/攻击)设置了并发实例上限
|
||
|
||
### Addressables
|
||
|
||
- [ ] `LoadAssetAsync` 的 Handle 在合适时机 `Release`
|
||
- [ ] 场景卸载时 Handle 统一释放(无泄漏)
|