【案例总结】Unity 对象池设计思路应用

开发平台:Unity 2018 以上
编程语言:CSharp 6.0
编译平台:Visual Studio 2019

  • 20210330 首次更新
  • 20230503 更新整理

摘要


  “对象池” 思想在程序领域中广泛应用。其目的性是控制资源消耗,提高运行性能。在 Unity 中,作为新人开发者首次接触的基础内容。需要明白 Unity 中对象池重心 以及如何应用对象池。

一、思考:为什么开发中提倡使用对象池?


答:运行过程中,为了管理或传递变量信息,经常使用 newcreate 这类蕴含创造性的方法实例对象。每当一个对象创建时,伴随内存占用的短时提升。频繁无厘头创建造成计算机运行性能大打折扣。无法满足运行标准。对象池强调对象重复利用率,可视化数量始终是有限的,不可见对象群将对使用者而言毫无意义与影响。

关键词:利用率提高,管理场景内对象数量。

二、了解:Unity 对象池使用范围


Unity 构建 GameObject 对象流程图

  如上图所示,是 Unity 构建 GameObject 对象的基础流程图。每一次创建均细化到对象上的各组件成员。例如:

  1. Create Empty 默认创建对象仅 Transform 组件。
    • Transform 目的:提供在 Scene/Game 视窗可见的位置信息。
  2. 关于 Craete Cube 或其他。
    • MeshFilter 目的:提供对象上的顶点位置数据。
    • MeshRenderer 目的:将对象轮廓与颜色绘制至 Scene/Game 视窗,让开发者了解该对象的外貌。

经典案例:FPS 子弹池

  在 FPS 射击游戏中,创建-发射-销毁 是频繁的过程。对于一些高射速的武器,子弹的创建频率将大幅度提高。即每次发射时,创建子弹对象 => 初始化属性 => 添加运动逻辑 => 命中/未命中销毁。回顾之前的 Unity 创建流程,这种性能占用显然是不可取用的。


对象池思想

  1. 构建默认数量的游戏对象存储至池中。隐藏禁用对象及其组件。
  2. 构建调用入口,每次调用从现有对象池中获取可用对象,若短时间内,对象池内无可用对象,临时创建新对象使用。
  3. 销毁对象方式,Destroy() 替换为禁用对象,并更新至对象池中,提高对象的反复利用率。


优势:无需反复进行创建流程。一次创建满足所有需求。
劣势:仅适用于频繁创建-销毁的流程优化,应对大体量对象的场景环境需要其他设计方式进行处理与优化。

三、参考:对象池设计


在这里插入图片描述

四、程序:对象池 ObjectPool


public class ObjectPool : MonoBehaviour
{
	public GameObject Sample { get; private set; }
	public uint DefaultCount { get; private set; } = 20public List<Transform> PoolLib { get; private set; }

	public void Init<T>(GameObject prefab, uint defaultCount = 10) { /*...*/ }
	public Transform Pop<T>() where T: Component { /*...*/ }
	public void Push(Transform obj) { /*...*/ }
}
  • Sample:当池内无对象可用时,提供样本以克隆新对象继续使用。
  • DefaultCount:定义默认数量。当对象池被建立时,会预先填充若干数量以补充池内数量等待使用。
  • PoolLib:池内缓存对象(未使用)

3.2 初始化

public void Init<T>(GameObject prefab, uint defaultCount)
{
	this.Sample = prefab;
	this.DefaultCount = defaultCount;

	if (prefab.GetComponent<T>() == null) prefab.AddComponent<T>();
	PoolLib = new();
}

3.3 取用方法

public Transform Pop<T>() where T : Component
{
    if (Pool.Count <= 0)
    {
        var theTransform = GameObject.Instantiate(Sample).transform;
        theTransform.gameObject.SetActive(false);
        Push(theTransform);

        return Pop<T>();
    }

    Transform theObject = Pool[0];
    if (theObject.parent != null)  theObject.SetParent(null);

    theObject.gameObject.SetActive(true);
    Pool.RemoveAt(0);
    Pool.RemoveAll(e => e == null);
    return theObject;
}
  • 应对池内数目不足以取用的情况,可直接创建并给到返回。此处是为了清晰思路使用 Push 先存再取。

3.4 放回方法

public void Push(Transform obj) {
    Pool.Add(obj);
    obj.SetParent(transform);
    obj.gameObject.SetActive(false);
}
  • 未使用对象理应禁用或放置于不可见位置。

四、扩展:多对象池下的管理 PoolCTR


在存储不同对象下,与其一个一个挂载脚本,反不如程序化对象池过程。

4.1 创建池对象

public static ObjectPool CreatePool<T>(string name, GameObject sample) where T : MonoBehaviour
{
    ObjectPool pool = new GameObject($"{name}").AddComponent<ObjectPool>();
    Library ??= new();

    var theKEY = typeof(T).Name;
    if (!Library.ContainsKey(theKEY))   Library.Add(theKEY, pool);

    pool.transform.SetParent(Instance.transform);
    pool.Init<T>(sample);

    return pool;
}
  • typeof(T).Name:作为 Dictionary<string, ObjectPool> Lib 的 KEY 存储。用于后续获取特定池对象。
    一般的作为池对象使用,被使用对象会挂在部分功能脚本。详细见 ObjectPool.Init<T>() 。若无实际使用,建议参考上述思路,另构建 Lib 以存储供后续 GetPool() 方法使用。
  • 此处未将对象池配置参数添加至更新方法中,若有需求,可自行构建方法。

4.2 获取池对象

public static ObjectPool GetPool<T>() where T : Component
{
    if (Library.ContainsKey(typeof(T).Name))  return Library[typeof(T).Name];
    return default;
}

五、更多说明


  一般情况,对象池取出与放回仅需要 Transform 组件即可 =》 PoolCTR.CreatePool<Transfor>("Example Object")。但为了区别特定预制体对象的应用,会添加脚本用于初始化对象,并提供入口方法。例如:

  1. 列表行列元素添加等。建议使用 PoolCTR.CreatePool<ExampleTest>("Eample Test")
  2. 调用入口方法时通过 PoolCTR.GetPool<ExampleTest>().Pop().InitMethod() 实现。
    或构建局部变量,以 thisPool.Pop().InitMethod() 实现

六、后记


  如有文章错误,请留言指明,笔者将在第一时间内修正错误。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity 中的对象池是一种资源管理技术,用于在游戏运行过程中高效地创建、管理和回收对象。对象池的主要目的是减少频繁创建和销毁对象带来的性能开销,尤其是在有大量短期使用对象(如小敌人、项目等)的情况下。 下面是使用 Unity 对象池的基本步骤: 1. 创建对象池:首先,你需要创建一个包含所需对象类型的新对象池。这通常是一个静态类或专用脚本,负责管理对象的生命周期。 ```csharp public class ObjectPool<T> where T : Component { private List<T> poolObjects; private Stack<T> availableObjects; // 初始化方法 public ObjectPool(int initialSize) { poolObjects = new List<T>(); for (int i = 0; i < initialSize; i++) { T obj = Instantiate<T>(); obj.SetActive(false); // 设置对象为非活动状态,直到需要时才激活 poolObjects.Add(obj); } availableObjects = new Stack<T>(poolObjects); } // 获取对象 public T BorrowObject() { if (availableObjects.Count > 0) { T obj = availableObjects.Pop(); obj.SetActive(true); return obj; } else { T obj = Instantiate<T>(); return obj; } } // 归还对象 public void ReturnObject(T obj) { obj.SetActive(false); availableObjects.Push(obj); } } ``` 2. 使用对象池:当你需要一个新对象时,从池中借用一个,用完后记得归还。这样,当对象不再被使用时,它会被放回池而不是直接销毁,以便后续其他地方可能需要它。 ```csharp private ObjectPool<MyObject> objectPool; void Start() { objectPool = new ObjectPool<MyObject>(10); } void Update() { MyObject newObj = objectPool.BorrowObject(); // 使用 newObj ... newObj.gameObject.SetActive(false); // 当对象不再需要时,归还给池子 // 如果对象池已满,考虑创建更多对象 if (objectPool.availableObjects.Count == 0 && poolSize < MaxPoolSize) { // 添加新对象到池中 objectPool.poolObjects.Add(Instantiate<MyObject>()); } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值