您当前的位置: 首页 >  游戏

野奔在山外的猫

暂无认证

  • 5浏览

    0关注

    85博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【Unity】游戏对象池ObjectPool

野奔在山外的猫 发布时间:2021-03-30 10:48:32 ,浏览量:5

开发平台: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; } = 20;
	public List PoolLib { get; private set; }

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

	if (prefab.GetComponent() == null) prefab.AddComponent();
	PoolLib = new();
}
3.3 取用方法
public Transform Pop() where T : Component
{
    if (Pool.Count  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(string name, GameObject sample) where T : MonoBehaviour
{
    ObjectPool pool = new GameObject($"{name}").AddComponent();
    Library ??= new();

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

    pool.transform.SetParent(Instance.transform);
    pool.Init(sample);

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

五、更多说明

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

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

六、后记

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

关注
打赏
1659777066
查看更多评论
立即登录/注册

微信扫码登录

0.0372s