723 lines
14 KiB
Markdown
723 lines
14 KiB
Markdown
# GameObjectPool 最优架构设计文档
|
||
|
||
## 1. 文档目标
|
||
|
||
本文档定义 `GameObjectPool` 模块的目标形态,用于支撑游戏内所有 `GameObject` 相关对象的统一池化,包括但不限于:
|
||
|
||
- 怪物 / NPC
|
||
- 子弹 / 投射物
|
||
- 特效 / 粒子 / VFXGraph
|
||
- 掉落物 / 临时交互物
|
||
- 飘字 / World UI
|
||
- 音效壳对象
|
||
- 可复用的场景装饰物
|
||
|
||
目标不是做一个“能复用 prefab 的基础对象池”,而是做一个“可覆盖全项目高频对象生命周期管理”的统一池化体系。
|
||
|
||
---
|
||
|
||
## 2. 现状结论
|
||
|
||
当前实现可以作为基础版对象池使用,但不是最优方案,主要原因如下:
|
||
|
||
- 只处理 `SetActive`、`SetParent`、Transform 复位,不具备统一的业务状态重置协议
|
||
- 池满时只会返回 `null`,没有按对象类型区分溢出策略
|
||
- 预热只支持静态固定值,不支持场景级、战斗级、波次级预热
|
||
- 淘汰策略只基于时间,不包含优先级、预算、低内存策略
|
||
- 清理调度依赖全量扫描,不适合大量池并存
|
||
- 配置维度不足,无法精细支撑怪物、特效、子弹等不同对象类型
|
||
- 缺少自动回收能力
|
||
- 缺少运行时统计闭环,无法逼近最优容量配置
|
||
|
||
结论:
|
||
|
||
- 当前实现可以继续作为底层雏形
|
||
- 不建议直接扩写成最终方案
|
||
- 最优方向应升级为“分层池化架构”
|
||
|
||
---
|
||
|
||
## 3. 设计目标
|
||
|
||
### 3.1 功能目标
|
||
|
||
- 统一管理所有 `GameObject` 池化对象
|
||
- 支持同步获取、异步获取、预热、回收、自动回收
|
||
- 支持对象在复用前后执行自定义重置
|
||
- 支持按对象类型定义不同的容量与淘汰策略
|
||
- 支持运行时统计与调优建议导出
|
||
- 支持低内存场景下的激进回收
|
||
|
||
### 3.2 性能目标
|
||
|
||
- 高频借还路径保持 O(1) 或近似 O(1)
|
||
- 避免重复加载同一 prefab
|
||
- 避免单帧大量 Instantiate / Destroy
|
||
- 清理与预热都采用预算驱动,而不是无上限循环
|
||
- 复杂对象允许使用轻量休眠,而非强制全量 `SetActive`
|
||
|
||
### 3.3 工程目标
|
||
|
||
- 配置清晰,可由策划 / TA / 程序共同维护
|
||
- 业务接入方式统一,不让每个系统各自发明对象池
|
||
- 支持逐步迁移,兼容现有 `GameObjectPoolManager`
|
||
|
||
---
|
||
|
||
## 4. 总体架构
|
||
|
||
推荐采用三层结构。
|
||
|
||
### 4.1 第一层:Prefab 池层
|
||
|
||
职责:
|
||
|
||
- prefab 资源加载与引用持有
|
||
- 单 prefab 实例池管理
|
||
- 容量控制
|
||
- 预热
|
||
- 淘汰
|
||
- 调度
|
||
|
||
建议保留当前 `RuntimePrefabPool` 的职责方向,但增强其策略能力。
|
||
|
||
### 4.2 第二层:实例生命周期层
|
||
|
||
职责:
|
||
|
||
- 统一对象创建后初始化
|
||
- 对象借出前重置
|
||
- 对象归还前清理
|
||
- 对象销毁前释放内部资源
|
||
- 管理自动回收与上下文注入
|
||
|
||
该层是当前模块最缺失的部分,也是怪物、子弹、特效能够统一纳入池化的关键。
|
||
|
||
### 4.3 第三层:业务门面层
|
||
|
||
职责:
|
||
|
||
- 向业务提供强类型 API
|
||
- 隐藏字符串路径、组名、预热细节
|
||
- 承载特定对象的默认策略
|
||
|
||
示例:
|
||
|
||
```csharp
|
||
Enemy enemy = enemyPool.Spawn(enemyId, spawnContext);
|
||
Bullet bullet = bulletPool.Spawn(bulletConfigId, spawnContext);
|
||
FxHandle fx = fxPool.Play(fxId, position, rotation, autoRecycle: true);
|
||
```
|
||
|
||
不建议把字符串 `assetPath` 作为业务层唯一入口。
|
||
字符串路径池应作为底层资源定位方式,而不是最终业务接口。
|
||
|
||
---
|
||
|
||
## 5. 生命周期协议设计
|
||
|
||
统一池化必须提供明确的生命周期协议。
|
||
|
||
### 5.1 核心接口
|
||
|
||
```csharp
|
||
public interface IGameObjectPoolable
|
||
{
|
||
void OnPoolCreate();
|
||
void OnPoolGet(in PoolSpawnContext context);
|
||
void OnPoolRelease();
|
||
void OnPoolDestroy();
|
||
}
|
||
```
|
||
|
||
语义建议:
|
||
|
||
- `OnPoolCreate`
|
||
仅在实例第一次创建后调用一次
|
||
- `OnPoolGet`
|
||
每次借出时调用,用于注入上下文、重置运行态
|
||
- `OnPoolRelease`
|
||
每次归还时调用,用于停止逻辑、清理状态
|
||
- `OnPoolDestroy`
|
||
实例真正销毁前调用,用于释放自持资源
|
||
|
||
### 5.2 可选扩展接口
|
||
|
||
```csharp
|
||
public interface IPoolAutoRecycle
|
||
{
|
||
bool TryGetAutoRecycleDelay(out float delaySeconds);
|
||
}
|
||
|
||
public interface IPoolResettablePhysics
|
||
{
|
||
void ResetPhysicsState();
|
||
}
|
||
|
||
public interface IPoolResettableVisual
|
||
{
|
||
void ResetVisualState();
|
||
}
|
||
|
||
public interface IPoolResettableAnimation
|
||
{
|
||
void ResetAnimationState();
|
||
}
|
||
|
||
public interface IPoolSleepable
|
||
{
|
||
void EnterSleep();
|
||
void ExitSleep(in PoolSpawnContext context);
|
||
}
|
||
```
|
||
|
||
说明:
|
||
|
||
- `IPoolSleepable` 主要给怪物 / NPC 这类重对象使用
|
||
- `IPoolResettablePhysics` 适合子弹、投射物、掉落物
|
||
- `IPoolResettableVisual` 适合特效、Trail、VFXGraph、Renderer 状态清理
|
||
|
||
### 5.3 PoolSpawnContext
|
||
|
||
`OnPoolGet` 需要统一上下文,避免靠外部脚本到处手动赋值。
|
||
|
||
建议结构:
|
||
|
||
```csharp
|
||
public readonly struct PoolSpawnContext
|
||
{
|
||
public readonly string AssetPath;
|
||
public readonly string Group;
|
||
public readonly Transform Parent;
|
||
public readonly Vector3 Position;
|
||
public readonly Quaternion Rotation;
|
||
public readonly object UserData;
|
||
public readonly int OwnerId;
|
||
public readonly int TeamId;
|
||
public readonly uint SpawnFrame;
|
||
}
|
||
```
|
||
|
||
说明:
|
||
|
||
- `UserData` 用于挂通用扩展参数
|
||
- 高频场景可以进一步拆成强类型上下文
|
||
|
||
---
|
||
|
||
## 6. 池实例状态机
|
||
|
||
每个实例建议具备明确状态:
|
||
|
||
- `Uninitialized`
|
||
- `Inactive`
|
||
- `Active`
|
||
- `Releasing`
|
||
- `Destroying`
|
||
|
||
状态转换:
|
||
|
||
```text
|
||
Create -> Inactive -> Active -> Releasing -> Inactive
|
||
Create -> Inactive -> Destroying -> Destroy
|
||
Active -> Destroying -> Destroy
|
||
```
|
||
|
||
价值:
|
||
|
||
- 避免重复回收
|
||
- 避免回收中再次借出
|
||
- 便于统计与调试
|
||
- 便于做自动回收与异步保护
|
||
|
||
---
|
||
|
||
## 7. 配置模型设计
|
||
|
||
当前 `PoolConfig` 维度不够,建议升级为两层配置:
|
||
|
||
- `PoolProfile`
|
||
- `PoolRule`
|
||
|
||
### 7.1 PoolProfile
|
||
|
||
定义一类对象的默认策略,例如:
|
||
|
||
- `BulletProfile`
|
||
- `FxProfile`
|
||
- `EnemyProfile`
|
||
- `UiWorldProfile`
|
||
|
||
示例字段:
|
||
|
||
```csharp
|
||
public sealed class PoolProfile
|
||
{
|
||
public string profileName;
|
||
public PoolObjectKind objectKind;
|
||
public PoolOverflowPolicy overflowPolicy;
|
||
public PoolTrimPolicy trimPolicy;
|
||
public PoolActivationMode activationMode;
|
||
public PoolResetMode resetMode;
|
||
|
||
public int minRetained;
|
||
public int softCapacity;
|
||
public int hardCapacity;
|
||
|
||
public float idleTrimDelay;
|
||
public float prefabUnloadDelay;
|
||
public float autoRecycleDelay;
|
||
|
||
public int trimBatchPerTick;
|
||
public int warmupBatchPerFrame;
|
||
public float warmupFrameBudgetMs;
|
||
|
||
public bool allowRuntimeExpand;
|
||
public bool preloadOnInitialize;
|
||
public bool keepPrefabResident;
|
||
public bool aggressiveTrimOnLowMemory;
|
||
}
|
||
```
|
||
|
||
### 7.2 PoolRule
|
||
|
||
定义具体资源命中规则。
|
||
|
||
```csharp
|
||
public sealed class PoolRule
|
||
{
|
||
public string group;
|
||
public string assetPath;
|
||
public PoolMatchMode matchMode;
|
||
public PoolResourceLoaderType loaderType;
|
||
public string profileName;
|
||
|
||
public bool overrideCapacity;
|
||
public int minRetained;
|
||
public int softCapacity;
|
||
public int hardCapacity;
|
||
|
||
public bool overridePrewarm;
|
||
public int prewarmCount;
|
||
public PoolPrewarmPhase prewarmPhase;
|
||
|
||
public int priority;
|
||
}
|
||
```
|
||
|
||
设计原则:
|
||
|
||
- `Profile` 管类型策略
|
||
- `Rule` 管具体命中
|
||
- 高频热点对象优先使用 `Exact`
|
||
- `Prefix` 只做默认兜底
|
||
|
||
---
|
||
|
||
## 8. 核心枚举草案
|
||
|
||
### 8.1 对象类型
|
||
|
||
```csharp
|
||
public enum PoolObjectKind
|
||
{
|
||
Default = 0,
|
||
Bullet = 1,
|
||
Effect = 2,
|
||
Enemy = 3,
|
||
Npc = 4,
|
||
Pickup = 5,
|
||
WorldUi = 6,
|
||
AudioProxy = 7,
|
||
SceneProp = 8
|
||
}
|
||
```
|
||
|
||
### 8.2 池满策略
|
||
|
||
```csharp
|
||
public enum PoolOverflowPolicy
|
||
{
|
||
FailFast = 0,
|
||
InstantiateOneShot = 1,
|
||
AutoExpand = 2,
|
||
RecycleOldestInactive = 3,
|
||
DropNewestRequest = 4
|
||
}
|
||
```
|
||
|
||
### 8.3 清理策略
|
||
|
||
```csharp
|
||
public enum PoolTrimPolicy
|
||
{
|
||
None = 0,
|
||
IdleOnly = 1,
|
||
IdleAndPriority = 2,
|
||
AggressiveOnLowMemory = 3
|
||
}
|
||
```
|
||
|
||
### 8.4 激活模式
|
||
|
||
```csharp
|
||
public enum PoolActivationMode
|
||
{
|
||
SetActive = 0,
|
||
SleepWake = 1,
|
||
Custom = 2
|
||
}
|
||
```
|
||
|
||
### 8.5 重置模式
|
||
|
||
```csharp
|
||
public enum PoolResetMode
|
||
{
|
||
TransformOnly = 0,
|
||
PoolableCallbacks = 1,
|
||
FullReset = 2,
|
||
Custom = 3
|
||
}
|
||
```
|
||
|
||
### 8.6 预热阶段
|
||
|
||
```csharp
|
||
public enum PoolPrewarmPhase
|
||
{
|
||
None = 0,
|
||
AppInitialize = 1,
|
||
SceneLoad = 2,
|
||
BattlePrepare = 3,
|
||
WavePrepare = 4,
|
||
OnDemand = 5
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 不同对象类型的推荐策略
|
||
|
||
### 9.1 子弹
|
||
|
||
建议:
|
||
|
||
- `softCapacity` 较高
|
||
- `hardCapacity` 可比 `softCapacity` 高 1.5 到 2 倍
|
||
- 默认支持 `AutoExpand` 或 `InstantiateOneShot`
|
||
- 必须支持自动回收
|
||
- 必须清理 `Rigidbody / Collider / TrailRenderer`
|
||
- prefab 通常常驻,不建议频繁 unload
|
||
|
||
### 9.2 特效 / 粒子
|
||
|
||
建议:
|
||
|
||
- 区分关键特效与普通特效
|
||
- 普通特效允许 `DropNewestRequest`
|
||
- 回收时必须清理所有粒子系统、Trail、音效播放状态
|
||
- 支持“播完自动回池”
|
||
- 预热由场景和技能装配共同驱动
|
||
|
||
### 9.3 怪物 / NPC
|
||
|
||
建议:
|
||
|
||
- 不建议只依赖通用 `SetActive`
|
||
- 必须实现 `IGameObjectPoolable`
|
||
- 推荐支持 `SleepWake`
|
||
- 必须有 `minRetained`
|
||
- 池满时不建议直接 `null`
|
||
- 场景切换 / 波次结束后再做结构性收缩
|
||
|
||
### 9.4 掉落物 / 临时交互物
|
||
|
||
建议:
|
||
|
||
- 适合通用池
|
||
- 支持超时自动回收
|
||
- 支持来源信息重置
|
||
|
||
### 9.5 飘字 / World UI
|
||
|
||
建议:
|
||
|
||
- 高度适合池化
|
||
- 必须自动回收
|
||
- 必须重置 tween / alpha / scale / text / follow target
|
||
|
||
---
|
||
|
||
## 10. 预热策略设计
|
||
|
||
### 10.1 预热阶段
|
||
|
||
预热必须是分阶段的,而不是只靠初始化时一次性完成。
|
||
|
||
建议阶段:
|
||
|
||
- `AppInitialize`
|
||
- `SceneLoad`
|
||
- `BattlePrepare`
|
||
- `WavePrepare`
|
||
- `OnDemand`
|
||
|
||
### 10.2 预热预算
|
||
|
||
预热不允许无限制同步创建。
|
||
|
||
建议加入:
|
||
|
||
- 每帧最大创建数 `warmupBatchPerFrame`
|
||
- 每帧时间预算 `warmupFrameBudgetMs`
|
||
- 大对象延迟到非关键帧创建
|
||
|
||
### 10.3 自适应预热
|
||
|
||
建议收集以下统计并用于反哺配置:
|
||
|
||
- `peakActive`
|
||
- `peakTotal`
|
||
- `missCount`
|
||
- `expandCount`
|
||
- `poolExhaustedCount`
|
||
|
||
由此生成:
|
||
|
||
- 推荐 `softCapacity`
|
||
- 推荐 `prewarmCount`
|
||
- 推荐 `minRetained`
|
||
|
||
---
|
||
|
||
## 11. 淘汰策略设计
|
||
|
||
### 11.1 默认原则
|
||
|
||
淘汰不能只看时间,还应结合:
|
||
|
||
- 空闲时间
|
||
- 对象优先级
|
||
- 当前是否战斗中
|
||
- 当前内存压力
|
||
- 当前池容量是否超过软上限
|
||
|
||
### 11.2 推荐容量模型
|
||
|
||
- `minRetained`
|
||
最低保有量,正常回收不低于该值
|
||
- `softCapacity`
|
||
常态推荐容量
|
||
- `hardCapacity`
|
||
绝对上限
|
||
|
||
### 11.3 销毁预算
|
||
|
||
回收策略必须预算化:
|
||
|
||
- 每 tick 最多回收 `trimBatchPerTick`
|
||
- 必要时可以带时间预算,例如每帧不超过 `X ms`
|
||
|
||
这样可避免同一帧大量 `Destroy`
|
||
|
||
### 11.4 低内存策略
|
||
|
||
当收到 `Application.lowMemory` 时:
|
||
|
||
- 忽略部分 `minRetained`
|
||
- 优先回收低优先级池
|
||
- 优先卸载不常用 prefab
|
||
- 进入短时激进回收模式
|
||
|
||
---
|
||
|
||
## 12. 调度策略设计
|
||
|
||
当前全量扫描策略不适合作为最终版。
|
||
|
||
推荐:
|
||
|
||
- 用最小堆维护池的下次清理时间
|
||
- 池状态变化时只更新自己的 `nextDueTime`
|
||
- 调度器只处理到期池
|
||
- 清理和预热都按 budget 驱动
|
||
|
||
这样可以避免:
|
||
|
||
- 每次回收都扫描所有池
|
||
- 大量池同时存在时的调度浪费
|
||
|
||
---
|
||
|
||
## 13. 自动回收设计
|
||
|
||
推荐提供一组通用 helper / component:
|
||
|
||
- `ReturnToPoolAfterSeconds`
|
||
- `ReturnToPoolOnParticleStopped`
|
||
- `ReturnToPoolOnAnimatorStateExit`
|
||
- `ReturnToPoolOnAudioFinished`
|
||
- `ReturnToPoolOnDistanceExceeded`
|
||
- `ReturnToPoolOnCollision`
|
||
|
||
目标:
|
||
|
||
- 业务不重复造轮子
|
||
- 自动回收行为可视化配置
|
||
- 减少“忘记 Release”导致的泄漏
|
||
|
||
---
|
||
|
||
## 14. 运行时统计设计
|
||
|
||
最优方案必须带统计闭环。
|
||
|
||
每个池建议统计:
|
||
|
||
- `acquireCount`
|
||
- `releaseCount`
|
||
- `hitCount`
|
||
- `missCount`
|
||
- `peakActive`
|
||
- `peakTotal`
|
||
- `expandCount`
|
||
- `exhaustedCount`
|
||
- `autoRecycleCount`
|
||
- `destroyCount`
|
||
- `avgLifetime`
|
||
- `avgIdleTime`
|
||
- `avgAcquireCostMs`
|
||
- `avgReleaseCostMs`
|
||
|
||
调试用途:
|
||
|
||
- Inspector 面板
|
||
- 运行时快照
|
||
- CSV / JSON 导出
|
||
- 自动生成调优建议
|
||
|
||
---
|
||
|
||
## 15. 推荐默认配置区间
|
||
|
||
以下是默认建议值,不是硬编码结论,应由运行时统计修正。
|
||
|
||
### 15.1 通用默认
|
||
|
||
- `minRetained = 2 ~ 8`
|
||
- `softCapacity = 8 ~ 32`
|
||
- `hardCapacity = softCapacity * 1.5 ~ 2`
|
||
- `idleTrimDelay = 15s ~ 60s`
|
||
- `prefabUnloadDelay = 60s ~ 300s`
|
||
- `trimBatchPerTick = 1 ~ 4`
|
||
|
||
### 15.2 子弹默认
|
||
|
||
- `minRetained = 16`
|
||
- `softCapacity = 64`
|
||
- `hardCapacity = 128`
|
||
- `overflowPolicy = AutoExpand`
|
||
- `prefabUnloadDelay = 300s`
|
||
|
||
### 15.3 普通特效默认
|
||
|
||
- `minRetained = 4`
|
||
- `softCapacity = 16`
|
||
- `hardCapacity = 32`
|
||
- `overflowPolicy = DropNewestRequest`
|
||
- `idleTrimDelay = 20s`
|
||
|
||
### 15.4 怪物默认
|
||
|
||
- `minRetained = 2`
|
||
- `softCapacity = 8`
|
||
- `hardCapacity = 16`
|
||
- `overflowPolicy = AutoExpand`
|
||
- `activationMode = SleepWake`
|
||
|
||
---
|
||
|
||
## 16. 与当前实现的映射关系
|
||
|
||
当前模块中可保留的部分:
|
||
|
||
- `GameObjectPoolManager`
|
||
继续作为总入口和调度器雏形
|
||
- `RuntimePrefabPool`
|
||
继续作为单 prefab 池雏形
|
||
- `PoolConfig`
|
||
升级为新配置模型的一部分
|
||
- `IResourceLoader`
|
||
可继续作为资源加载抽象
|
||
|
||
需要重点重构的部分:
|
||
|
||
- 实例生命周期协议
|
||
- 池满策略
|
||
- 预热与清理预算调度
|
||
- 统计系统
|
||
- 自动回收组件
|
||
- 怪物/特效/子弹的分类默认策略
|
||
|
||
---
|
||
|
||
## 17. 迁移路线
|
||
|
||
建议分四个阶段落地。
|
||
|
||
### 阶段 1:补生命周期协议
|
||
|
||
目标:
|
||
|
||
- 引入 `IGameObjectPoolable`
|
||
- Acquire / Release 时统一回调
|
||
- 允许对象自定义重置
|
||
|
||
### 阶段 2:补配置模型
|
||
|
||
目标:
|
||
|
||
- 引入 `PoolProfile + PoolRule`
|
||
- 加入 `minRetained / softCapacity / hardCapacity / overflowPolicy`
|
||
- 兼容旧 `PoolConfig`
|
||
|
||
### 阶段 3:补自动回收与预算调度
|
||
|
||
目标:
|
||
|
||
- 自动回收 helper
|
||
- 分帧 warmup
|
||
- 预算式 trim
|
||
- 低内存模式
|
||
|
||
### 阶段 4:补统计与业务门面
|
||
|
||
目标:
|
||
|
||
- 运行时统计面板
|
||
- 容量推荐导出
|
||
- `EnemyPool / BulletPool / FxPool` 业务门面
|
||
|
||
---
|
||
|
||
## 18. 最终结论
|
||
|
||
最优对象池方案不是单一 `GameObject` 缓存容器,而是:
|
||
|
||
- 以 prefab 池为底层
|
||
- 以生命周期协议为核心
|
||
- 以分类策略为手段
|
||
- 以预算调度为性能保障
|
||
- 以统计闭环为调优依据
|
||
|
||
如果后续按本文档实施,建议优先级如下:
|
||
|
||
1. `IGameObjectPoolable` 与 `PoolSpawnContext`
|
||
2. `PoolProfile + PoolRule`
|
||
3. `PoolOverflowPolicy`
|
||
4. 自动回收 helper
|
||
5. 分帧预热与预算清理
|
||
6. 运行时统计与调优导出
|
||
|