chore: initial commit

This commit is contained in:
2026-05-08 11:04:00 +08:00
commit f55d2a57c3
6278 changed files with 866081 additions and 0 deletions

View File

@@ -0,0 +1,375 @@
# 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
│ ├─ 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 在热路径 | 预分配 `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 → 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 的位置
```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 统一释放(无泄漏)