com.alicizax.unity.framework/Runtime/ABase/GameObjectPool/fe.md
2026-04-17 21:01:20 +08:00

14 KiB
Raw Permalink Blame History

GameObjectPool 最优架构设计文档

1. 文档目标

本文档定义 GameObjectPool 模块的目标形态,用于支撑游戏内所有 GameObject 相关对象的统一池化,包括但不限于:

  • 怪物 / NPC
  • 子弹 / 投射物
  • 特效 / 粒子 / VFXGraph
  • 掉落物 / 临时交互物
  • 飘字 / World UI
  • 音效壳对象
  • 可复用的场景装饰物

目标不是做一个“能复用 prefab 的基础对象池”,而是做一个“可覆盖全项目高频对象生命周期管理”的统一池化体系。


2. 现状结论

当前实现可以作为基础版对象池使用,但不是最优方案,主要原因如下:

  • 只处理 SetActiveSetParent、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
  • 隐藏字符串路径、组名、预热细节
  • 承载特定对象的默认策略

示例:

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 核心接口

public interface IGameObjectPoolable
{
    void OnPoolCreate();
    void OnPoolGet(in PoolSpawnContext context);
    void OnPoolRelease();
    void OnPoolDestroy();
}

语义建议:

  • OnPoolCreate 仅在实例第一次创建后调用一次
  • OnPoolGet 每次借出时调用,用于注入上下文、重置运行态
  • OnPoolRelease 每次归还时调用,用于停止逻辑、清理状态
  • OnPoolDestroy 实例真正销毁前调用,用于释放自持资源

5.2 可选扩展接口

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 需要统一上下文,避免靠外部脚本到处手动赋值。

建议结构:

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

状态转换:

Create -> Inactive -> Active -> Releasing -> Inactive
Create -> Inactive -> Destroying -> Destroy
Active -> Destroying -> Destroy

价值:

  • 避免重复回收
  • 避免回收中再次借出
  • 便于统计与调试
  • 便于做自动回收与异步保护

7. 配置模型设计

当前 PoolConfig 维度不够,建议升级为两层配置:

  • PoolProfile
  • PoolRule

7.1 PoolProfile

定义一类对象的默认策略,例如:

  • BulletProfile
  • FxProfile
  • EnemyProfile
  • UiWorldProfile

示例字段:

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

定义具体资源命中规则。

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 对象类型

public enum PoolObjectKind
{
    Default = 0,
    Bullet = 1,
    Effect = 2,
    Enemy = 3,
    Npc = 4,
    Pickup = 5,
    WorldUi = 6,
    AudioProxy = 7,
    SceneProp = 8
}

8.2 池满策略

public enum PoolOverflowPolicy
{
    FailFast = 0,
    InstantiateOneShot = 1,
    AutoExpand = 2,
    RecycleOldestInactive = 3,
    DropNewestRequest = 4
}

8.3 清理策略

public enum PoolTrimPolicy
{
    None = 0,
    IdleOnly = 1,
    IdleAndPriority = 2,
    AggressiveOnLowMemory = 3
}

8.4 激活模式

public enum PoolActivationMode
{
    SetActive = 0,
    SleepWake = 1,
    Custom = 2
}

8.5 重置模式

public enum PoolResetMode
{
    TransformOnly = 0,
    PoolableCallbacks = 1,
    FullReset = 2,
    Custom = 3
}

8.6 预热阶段

public enum PoolPrewarmPhase
{
    None = 0,
    AppInitialize = 1,
    SceneLoad = 2,
    BattlePrepare = 3,
    WavePrepare = 4,
    OnDemand = 5
}

9. 不同对象类型的推荐策略

9.1 子弹

建议:

  • softCapacity 较高
  • hardCapacity 可比 softCapacity 高 1.5 到 2 倍
  • 默认支持 AutoExpandInstantiateOneShot
  • 必须支持自动回收
  • 必须清理 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. IGameObjectPoolablePoolSpawnContext
  2. PoolProfile + PoolRule
  3. PoolOverflowPolicy
  4. 自动回收 helper
  5. 分帧预热与预算清理
  6. 运行时统计与调优导出