Unity3D在2021.1版本新增了内置的对象池系统
这是一项对大多数开发者来说非常好用且常用的功能
目录
三、Unity3D额外内置不同数据结构实现的Object Pool(大概描述)
四、实现Object Pool的小案例(案例以及源码会放到最后供大家参考)
1、使用前请先导入命名空间 —— using UnityEngine.Pool;
1、节省大量的学习时间,大幅提升开发效率。不需要去网络中筛选解决方案或是搜寻教程案例,又或是去掏腰包购买插件,然后再学习插件怎么用。
3、可以通过自己的理解进行池优化,通过一定的额外扩展以及维护达到自己想要的效果。
一、Object Pool的定义
1、将Object Pool在想象中抽象的比喻为 —— 容器:银河系 单个对象:星球
2、将Object Pool在生活中形象的比喻为 —— 容器:台球桌 单个对象:带有数字的球
3、定义:
我觉得通俗易懂的来说就是将已经实例化的大量对象重复循环利用,而不是在每次需要用时都去重复的创建和销毁它们(频繁的反复操作会导致大量的GC垃圾回收),通过预先创建和管理对象的集合,实现对象的重复利用,从而提高游戏性能和资源利用效率。这个对象可以是任何内容,它可以是 GameObject,也可以是 Component。(自我理解总结)
系统官方的来说对象池是一种优化项目并减轻 CPU 在必须快速创建和销毁新对象时负担的方法。这是一种很好的做法和设计模式,以帮助减轻 CPU 的处理能力,以处理更重要的任务,而不会被重复的创建和销毁调用所淹没。 对象池使用堆栈来保存对象实例的集合以供重用,并且不是线程安全的。(官方话术)
二、在游戏编程中我们为什么要使用Object Pool?
优点:
1、提高性能
对象池避免了频繁地创建和销毁对象的开销,减少了内存分配和垃圾回收的压力,从而提高了游戏的性能。
2、减少内存碎片
由于对象在重复使用过程中不断被创建和销毁,可能会导致内存碎片问题。对象池可以通过预先创建一定数量的对象来避免这个问题,减少内存碎片的产生。
3、控制对象创建的时间和成本
对象池在游戏开始之前就可以预先创建好一定数量的对象,避免了在游戏运行时动态创建对象的开销。这样可以提前分配对象所需的资源,并且可以更好地控制对象创建的时间和成本。
4、简化对象管理
使用对象池可以统一管理对象的生命周期和状态,简化了对象的创建、初始化、销毁等操作,提高了代码的可维护性和可读性。
凡事都有两面性,ObjectPool也是一把双刃剑
缺点:
1、会持久化占用内存
对象池需要预先创建一定数量的对象,占用了一定的内存空间。如果对象池设置得过大,可能会浪费内存;设置得过小,则可能频繁地创建和销毁对象。
2、对象状态的初始化管理
由于对象在重复使用过程中可能会保存其被收回池中之前的状态,需要在对象重新获取和归还时正确地管理和重置对象的初始化状态,否则可能出现意料之外的行为。
三、Unity3D额外内置不同数据结构实现的Object Pool(大概描述)
1.CollectionPool<T0,T1>
集合池,可以放List、HashSet、Dictionary非线程安全
2.DictionaryPool<T0,T1>
字典池,继承自CollectionPool
3.GenericPool<T0 >
通用池,ObjectPool的静态实现,启用了集合检查,以确保不会重复回收。非线程安全
4.HashSetPool<T0>
哈希集池,CollectionPool<T0,T1>的HashSet版本
5.LinkedPool<T0>
链表池,IObjectPool的链表版本,池子内的对象是以链表形式保存的,非线程安全
6.ListPool<T0>
列表池,CollectionPool<T0,T1>的list版本。
7.ObjectPool<T0>
对象池,基于堆栈的IObjectPool< T0>,池子内的对象是以堆栈的形式保存的,非线程安全
8.UnsafeGenericPool<T0>
不安全通用池,提供ObjectPool的静态实现,这是禁用集合检查的GenericPool的替代方法。某些对象在收集检查期间进行比较时会产生垃圾。此版本不执行任何收集检查,因此不会产生垃圾。注意这不是线程安全的。
感兴趣的同学可以私下去研究一下
Unity3D官方相关Api详解网址——Pool.ObjectPool<T0> - Unity 脚本 API (unity3d.com)
四、实现Object Pool的小案例(案例以及源码会放到最后供大家参考)
1、是内存消耗差距比较的案例——桌球满天飞
在这个项目中,只需要用到两个非常简单的脚本 TempObjTest.cs 以及 ObjectPoolManager.cs,TempObjTest.cs 将会挂载到桌球上,通过快速且不断的Instantiate游戏对象和Destroy游戏对象来对比开启Object Pool和不开启Object Pool时两者的内存展示
2、是差距更加直观的案例——Biu,Biu,Biu
在这个项目中,只需要用到两个非常简单的脚本 TempObjTest.cs 以及 FPSObjectPoolManager.cs,TempObjTest.cs 将会挂载到子弹上,通过不断的Instantiate游戏对象和Destroy游戏对象来对比开启Object Pool和不开启Object Pool时两者的内存展示
未正确重置状态以及引用(一个自己在写项目时遇到的小Bug)
PS:材质球在生成释放后会丢失,因为会有可能多个对象用到同一个材质球,当一个材质球正在被多个对象使用时,前面的对象先释放当前材质球会导致后续还正在使用当前材质球的对象的材质球丢失变为洋红色。所以在释放对象时后需要对被释放的对象进行一个基本的重置。
PS:怎么查看内存GC变化——在Unity3D编辑器中快捷键 Ctrl+7 打开Profiler窗口找到对应的脚本查看其Updata中的GC变化
此案例中展示:
未开启对象池
已开启对象池
五、如何使用Unity3D内置ObjectPool?
使用前请先导入命名空间 —— using UnityEngine.Pool;
简要概括 —— 实现接口:IObjectPoolhttps://docs.unity3d.com/cn/current/ScriptReference/Pool.IObjectPool_1.html
此接口中有一个变量:CountInactive——池中当前项目的总数。
三个供继承接口的类使用的公共函数:
1——Clear(); 删除所有合并的项目。如果池包含销毁回调,则将为池中的每个项目调用该回调。
2——Get(); 从池中获取实例。如果池为空,则将创建一个新实例。
3——Release(); 将实例返回到池中。
2、详解 ObjectPool API
(1)变量:
CountActive
池已创建但当前正在使用且尚未返回的对象数。
CountAll
活动和非活动对象的总数。
CountInactive
池中当前可用的对象数
(2)函数:
Clear() / Dispose()
删除所有合并的项目。如果池包含销毁回调,则将为池中的每个项目调用该回调。
Get()
从池中获取实例。如果池为空,则将创建一个新实例。
Release()
将对象实例返回到池中。
示例代码
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
public class FPSObjectPoolManager : MonoBehaviour
{
[Header("存储对象的容器")]
public GameObject bulletEffect;
/// <summary>
/// 控制对象池的开启或关闭
/// </summary>
[Header("开启子弹对象池")]
public bool m_OpenDebug;
//池对象
public ObjectPool<GameObject> tempPool;
private void OnEnable()
{
//=====================================子弹=======================================
//初始化游戏对象池
//构造函数中需要传递四个回调(有额外参数设置)
tempPool = new ObjectPool<GameObject>(
//createFunc——在创建新对象时调用此回调
() =>
{
//实例化对象
var tempObj = Instantiate(bulletEffect);
/*以下实现对实例化对象的操作*/
tempObj.transform.position = gunPos.position;
tempObj.transform.rotation = gunPos.rotation;
//注册销毁事件——不是真正意义上的销毁对象而是调用对象池的方法将对象放回对象池
tempObj.AddComponent<TempObjTest>().destroyEvent.AddListener(() =>
{
//释放返回对象
if (m_OpenMaterial)
materialPoll.Release(tempObj.GetComponent<MeshRenderer>().material);
tempPool.Release(tempObj);
});
//返回的是放进对象池的游戏对象
return tempObj;
},
//actionOnGet——在通过对象池获取对象时调用
(tempObj) =>
{
//激活游戏对象
tempObj.SetActive(true);
/*以下实现对获取对象的操作*/
tempObj.transform.position = gunPos.position;
tempObj.transform.rotation = gunPos.rotation;
},
//actionOnRelease——在游戏对象返回对象池时调用
(tempObj) =>
{
tempObj.SetActive(false);
},
//actionOnDestroy——在你释放对象或者内部空间无法存储返回对象时调用
(tempObj) =>
{
Destroy(tempObj);
});
}
六、Unity3D内置Object Pool的小优势
1、节省大量的学习时间,大幅提升开发效率。不需要去网络中筛选解决方案或是搜寻教程案例,又或是去掏腰包购买插件,然后再学习插件怎么用。
2、内置对象池类型较多,可以用不同数据结构去构建对象池。
3、可以通过自己的理解进行池优化,通过一定的额外扩展以及维护达到自己想要的效果。
感谢大家的观看,您的点赞和关注是我最大的动力
强调:未经许可禁止转载!【免费】ObjectPool-Demo资源-CSDN文库