13.缓存池优化:对象上限配置

一、本课目标

Lesson13 通过 GetObj 的参数设置对象数量上限:

csharp 复制代码
PoolMgr3.Instance.GetObj("Prefabs/Cube", 5);

这种方式要求调用者每次获取对象时都知道并传入正确的上限。Lesson14 将最大数量配置放到预制体自身,让每种对象独立保存自己的配置:

csharp 复制代码
PoolMgr4.Instance.GetObj("Prefabs/Cube");

缓存池创建对象时读取预制体上的 PoolObj.MaxNum,调用者只需要提供资源路径。

二、为什么把上限配置放到对象上

不同类型的对象通常需要不同的数量上限,例如:

对象类型 建议上限 原因
子弹 50 生成频率高、生命周期短
敌人 20 同屏数量有限,但单个对象开销较大
爆炸特效 10 存在时间短,可重复利用

如果由调用者传入上限,同一种对象可能在不同位置得到不同配置。把配置保存在预制体上具有以下优点:

  • 配置与对象放在一起,更容易查找和修改。
  • 每种预制体可以拥有独立的最大数量。
  • 获取对象的接口更简单,不需要重复传入 maxNum
  • 策划或开发者可以直接在 Inspector 中调整数值。

三、PoolObj 配置组件

PoolObj 是挂载在缓存对象预制体上的配置脚本:

csharp 复制代码
using UnityEngine;

public class PoolObj : MonoBehaviour
{
    public int MaxNum = 5;
}

MaxNum 表示这一类对象最多允许同时创建多少个实例。因为它是公共字段,所以可以直接在 Unity Inspector 中修改。

本课中的 Cube.prefab 已挂载 PoolObj,并将 MaxNum 设置为 5

四、使用步骤

  1. 选中需要放入缓存池的预制体。
  2. 为预制体添加 PoolObj 组件。
  3. 在 Inspector 中设置 Max Num
  4. 为对象添加对应的自动回收脚本,例如 HideMe4
  5. 通过 PoolMgr4.Instance.GetObj(resourcePath) 获取对象。

示例:

csharp 复制代码
if (Input.GetMouseButtonDown(0))
{
    PoolMgr4.Instance.GetObj("Prefabs/Cube");
}

资源必须位于 Resources 文件夹下。传入的是相对路径,不需要扩展名。Unity 中推荐使用正斜杠:

csharp 复制代码
PoolMgr4.Instance.GetObj("Prefabs/Cube");

五、读取对象上限配置

PoolMgr4.CreateObj 在实例化对象后获取它的 PoolObj 组件:

csharp 复制代码
private GameObject CreateObj(string name)
{
    GameObject prefab = Resources.Load<GameObject>(name);
    if (prefab == null)
    {
        Debug.LogError($"Resources 中没有找到对象:{name}");
        return null;
    }

    GameObject obj = Object.Instantiate(prefab);
    PoolObj poolObj = obj.GetComponent<PoolObj>();

    if (poolObj != null)
    {
        maxNum = poolObj.MaxNum;
    }
    else
    {
        Debug.LogError("缓存池物体没有挂载 PoolObj");
        return null;
    }

    obj.name = name;
    return obj;
}

处理流程如下:

text 复制代码
加载并实例化预制体
        ↓
获取 PoolObj 组件
        ↓
读取 MaxNum
        ↓
保存资源路径作为对象名称
        ↓
返回创建完成的对象

如果预制体没有挂载 PoolObj,管理器会输出错误并返回 null。因此,所有交给 PoolMgr4 创建的预制体都必须添加该组件。

六、获取对象时的四种情况

PoolMgr4.GetObj 不再接收 maxNum 参数,而是使用从 PoolObj 中读取的配置:

csharp 复制代码
public GameObject GetObj(string name)

获取对象时分为四种情况:

情况 处理方式
对应的对象池还不存在 创建第一个对象和对应的 PoolData3
存在空闲对象 objStack 中取出并复用
没有空闲对象,但未达到 MaxNum 创建新对象并加入 usedList
没有空闲对象,并且达到 MaxNum 复用 usedList 中最早使用的对象

核心判断:

csharp 复制代码
if (poolData.Count > 0)
{
    return poolData.Pop();
}

if (poolData.UsedCount < maxNum)
{
    GameObject newObj = CreateObj(name);
    poolData.PushUsedList(newObj);
    return newObj;
}

return poolData.Pop();

这样可以保证对象数量达到上限后不再继续实例化。

七、PoolData3 的职责

PoolData3 管理同一类型对象的空闲状态和使用顺序:

csharp 复制代码
public Stack<GameObject> objStack = new();
private List<GameObject> usedList = new();

public int Count => objStack.Count;
public int UsedCount => usedList.Count;
  • objStack:保存已经回收、当前空闲的对象。
  • usedList:按开始使用的先后顺序保存正在使用的对象。
  • Count:空闲对象数量。
  • UsedCount:使用中对象数量。

达到数量上限且没有空闲对象时,会取出 usedList[0],也就是使用时间最久的对象:

csharp 复制代码
obj = usedList[0];
usedList.RemoveAt(0);
usedList.Add(obj);

将它重新添加到列表尾部,表示该对象开始了新一轮使用。

八、对象回收

HideMe4 在对象启用两秒后将其归还给 PoolMgr4

csharp 复制代码
public class HideMe4 : MonoBehaviour
{
    private void OnEnable()
    {
        Invoke(nameof(HideSelf), 2f);
    }

    private void HideSelf()
    {
        PoolMgr4.Instance.PushObj(gameObject);
    }
}

回收时,PoolData3.Push 会:

  1. 将对象设置为不激活状态。
  2. 开启布局功能时,将对象移动到对应的分类节点下。
  3. 把对象压入空闲栈 objStack
  4. 从使用列表 usedList 中移除。

九、完整生命周期

text 复制代码
首次获取
  ↓
创建对象并读取 PoolObj.MaxNum
  ↓
加入 usedList
  ↓
对象开始使用
  ↓
调用 PushObj 回收
  ↓
从 usedList 移除并压入 objStack
  ↓
再次获取时从 objStack 取出

达到数量上限后的流程:

text 复制代码
objStack 为空
  ↓
UsedCount >= MaxNum
  ↓
取出 usedList[0]
  ↓
移动到列表尾部
  ↓
重新使用,不再创建新对象

十、与 Lesson13 的区别

对比项 Lesson13 Lesson14
上限来源 调用 GetObj 时传入 从预制体的 PoolObj 读取
调用方式 GetObj(path, maxNum) GetObj(path)
配置位置 调用代码 预制体 Inspector
调整方式 修改调用参数 修改 PoolObj.MaxNum
调用者职责 知道对象上限 只需要知道资源路径

十一、注意事项

  • MaxNum 应设置为大于 0 的数值,否则对象池无法正常扩充。
  • 每个通过 PoolMgr4 创建的预制体都必须挂载 PoolObj
  • 对象回收脚本必须调用 PoolMgr4,不能混用 lesson12 或 lesson13 的管理器。
  • 对象名称会被改为资源路径,用作 poolDic 的键,不要在使用期间随意修改。
  • 达到上限后会抢占最早使用的对象,因此每次获取后都应重新设置位置、旋转、动画等运行状态。
  • 当前 maxNumPoolMgr4 的共享字段,适合本课单一预制体测试。若同时管理多种不同上限的对象,更合理的做法是把上限保存在各自的 PoolData3 中。
相关推荐
唐青枫1 小时前
C#.NET StructureMap 从依赖注入到项目实战
c#·.net
李迟2 小时前
Golang实践录:工程框架实践:搭建工程模板
开发语言·后端·golang
纪伊路上盛名在2 小时前
Kabsch算法的Julia实现
开发语言·算法·julia·序列分析·蛋白质·rmsd
玖玥拾2 小时前
Lua 基础语法(三) 元表 metatable、元方法、模拟面向对象
开发语言·unity·lua
维天说2 小时前
Agent会听人话,反而更难管
java·开发语言·人工智能
fujisheng6612 小时前
FUI 编译期装配实践:从反射注册到 Source Generator
c#·unity3d
智码看视界2 小时前
.NET 10 推理大模型TensorSharp 3.3.0 部署实测:纯.NET推理引擎反超llama.cpp 1.5倍,DFlash2提速62%
c#·.net·llama.cpp·.net 10·tensorsharp·本地大模型推理·开源推理引擎
会飞的拖把2 小时前
Python 面向对象编程详解:从类与对象到动态属性方法
开发语言·python
大牧师2 小时前
Nest.js 微服务入门教程
开发语言·javascript·后端·微服务·node.js·nest.js·nest