开发平台:Unity 2018 以上 编程语言:CSharp 6.0 编译平台:Visual Studio 2019
- 20210330 首次更新
- 20230503 更新整理
“对象池” 思想在程序领域中广泛应用。其目的性是控制资源消耗,提高运行性能。在 Unity 中,作为新人开发者首次接触的基础内容。需要明白 Unity 中对象池重心 以及如何应用对象池。
一、思考:为什么开发中提倡使用对象池?答:运行过程中,为了管理或传递变量信息,经常使用 new
或 create
这类蕴含创造性的方法实例对象。每当一个对象创建时,伴随内存占用的短时提升。频繁无厘头创建造成计算机运行性能大打折扣。无法满足运行标准。对象池强调对象重复利用率,可视化数量始终是有限的,不可见对象群将对使用者而言毫无意义与影响。
关键词:利用率提高,管理场景内对象数量。
二、了解:Unity 对象池使用范围如上图所示,是 Unity 构建 GameObject 对象的基础流程图。每一次创建均细化到对象上的各组件成员。例如:
- Create Empty 默认创建对象仅 Transform 组件。
- Transform 目的:提供在 Scene/Game 视窗可见的位置信息。
- 关于 Craete Cube 或其他。
- MeshFilter 目的:提供对象上的顶点位置数据。
- MeshRenderer 目的:将对象轮廓与颜色绘制至 Scene/Game 视窗,让开发者了解该对象的外貌。
经典案例:FPS 子弹池 在 FPS 射击游戏中,创建-发射-销毁 是频繁的过程。对于一些高射速的武器,子弹的创建频率将大幅度提高。即每次发射时,创建子弹对象 => 初始化属性 => 添加运动逻辑 => 命中/未命中销毁。回顾之前的 Unity 创建流程,这种性能占用显然是不可取用的。 对象池思想
- 构建默认数量的游戏对象存储至池中。隐藏禁用对象及其组件。
- 构建调用入口,每次调用从现有对象池中获取可用对象,若短时间内,对象池内无可用对象,临时创建新对象使用。
- 销毁对象方式,
Destroy()
替换为禁用对象,并更新至对象池中,提高对象的反复利用率。
优势:无需反复进行创建流程。一次创建满足所有需求。 劣势:仅适用于频繁创建-销毁的流程优化,应对大体量对象的场景环境需要其他设计方式进行处理与优化。
三、参考:对象池设计
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
:池内缓存对象(未使用)
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
先存再取。
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()
方法使用。- 此处未将对象池配置参数添加至更新方法中,若有需求,可自行构建方法。
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")
。但为了区别特定预制体对象的应用,会添加脚本用于初始化对象,并提供入口方法。例如:
- 列表行列元素添加等。建议使用
PoolCTR.CreatePool("Eample Test")
。 - 调用入口方法时通过
PoolCTR.GetPool().Pop().InitMethod()
实现。 或构建局部变量,以thisPool.Pop().InitMethod()
实现
六、后记
如有文章错误,请留言指明,笔者将在第一时间内修正错误。