Unity3d中对象池(ObjectPool)的实现思路
Unity3d中的对象池 在使用unity3d做游戏时,经常有同一个Prefab用到多次,需要反复实例化(Instantiate)。但实例化是很消耗资源的,所以在游戏加载时把一批Prefab实例化好放在对象池中,游戏中用的时候拿出来,不用的时候放回去,避免反复申请和销毁。 存入对象池的元素应具有如下特征:1>场景中大量使用 2>存在一定的生命周期,会较为频繁的申请和释放。
关于多线程的考虑 因为Unity的API只能被主线程调用,我理解Unity提供的用户空间是单线程的(脚本中写While(true)挂在GameObject上,点运行整个Unity会卡死)。所以我们不需要将池实现支持多线程。在支持多线程的应用中,单例的初始化通常要加一个锁,在这里也没有必要。
希望实现具有以下特征的对象池 1、新增对象种类时操作简单,能够灵活控制每个对象池中生成的对象数量。 2、接口简单,易于申请和回收。 3、模块结构清晰,耦合度低。 4、申请和回收时,可以根据具体的对象类型做个性化操作。
实现 1、模块设计 *UML规则参考自 UML 基础: 类图 模块中主要使用以下类 ObjectPoolMgr是对象池管理类。它对外提供接口,对内管理对象池实例。外部进行申请和释放操作都是通过调用ObjectPoolMgr中的接口进行的。 ObjectPool是对象池基类,它是抽象类,包含了通用的成员和方法。作为一个基类,它提可被子类继承的方法是virtual的,便于实现多态。 CubePool, SpharePool是具体的对象池实现类,继承自ObjectPool,可以override基类的alloc和recycle等方法实现个性化的操作。 PrefabInfo挂在对象池中的每一个对象上,用来记录对象类型等信息。
2、数据管理方式 数据管理方式如图,单例ObjectPoolMgr中使用字典管理着多种对象池实例,每种对象池实例中使用队列管理一定数量的同类型对象。
3、主要接口和调用流程 对象池管理类ObjectPoolMgr类中提供对外接口,主要是alloc和recycle,声明如下
1 2 |
//回收接口。参数是待回收GameObject
public static void recycle(GameObject
recycleObj);
|
游戏在使用申请到的GameObject时可能会在其中添加子物体,回收前会判断一下recycleObj中是否嵌套有其它属于对象池的Prefab,如果存在就分别进行回收。 另外如果对象池中待分配对象数量超过了用户设置的个数,直接销毁recycleObj而不再放回对象池。
4、对象池的创建时机 过早创建对象池,池中的对象会占用大量内存。若等到游戏使用对象时再创建对象池,可能因为大量实例化造成掉帧。所以,我认为在Loading界面创建下一个场景需使用的对象池是较为合理的。比如天天飞车中的NPC车,金币,赛道,在进入单局比赛后才用到。可以在进入比赛的Loading界面预先创建金币,NPC车,赛道的对象池,比赛中直接申请使用。
5、具体实现 1>ObjectPoolMgr ObjectPoolMgr是对象池的管理类,提供接口,
1 2 |
public static GameObject
alloc( string type,
float lifetime
= 0);
public static void recycle(GameObject
recycleObj);
|
参数lifeTime是存活时间,以秒为单位,定义如下
lifeTime > 0 lifeTime秒后自动回收对象。
lifeTime = 0 不自动回收对象,需游戏主动调用recycle回收。
lifeTime < 0 创建Pool实例并实例化Pool中的对象,但不返回对象,返回值null。
当lifeTime>0时,分配出去的GameObject上挂的PrefabInfo脚本会执行倒计时协程,计时器为0时调用recycle方法回收自己。它的适用对象如射击游戏中的子弹,申请时设定了lifeTime后不必关心回收的问题,当然游戏可以计时器在到时前主动发起回收。
lifeTime < 0的目的预创建对象池,在游戏场景Loading时可以用这个方法先把对象池创建起来,避免游戏中创建对象池造成掉帧。
ObjectPoolMgr用成员poolDic维护已分配的对象池实例
1 |
private
Dictionary< string ,
ObjectPool>
poolDic = new
Dictionary< string ,
ObjectPool >();
|
使用objectPoolList记录面板上的用户设置

ObjectPoolMgr初始化时会在Unity的层次(Hierarchy)面板中创建GameObject并添加自身脚本。开发者可以在Inspector面板中直接创建新的对象池,如下图。Pre Alloc Size是对象池创建时预申请的对象数量。Auto Increase Size是池中的对象被申请完后进行一定数量的自增。prefab对象池关联的预制类型
比如,当游戏需要申请Cube对象时,会调用GameObject cube = ObjectPoolMGR.alloc("Cube");方法。ObjectPoolMGR.alloc代码如下1 2 3 4 5 6 7 |
public static GameObject
alloc( string type,
float lifetime
= 0){
//根据传入type取出或创建对应类型对象池
ObjectPool
subPool = Instance._getpool(type);
//从对象池中取一个对象返回
GameObject
returnObj = subPool.alloc(lifetime);
return returnObj;
}
|
ObjectPoolMgr会根据传入的类型type,调用_getpool(type)找到对应的Pool,再从其中取一个对象返回。
代码中_getpool(string type)是ObjectPoolMgr中的私有方法。前面说过,ObjectPoolMgr有一个成员poolDic用来记录已创建的对象池实例,_getpool方法先去poolDic中查找,找到直接返回。如果找不到说明还未创建,使用反射创建对象池,记录入poolDic,代码如下

使用_getpool获得了对象池实例后,会调用对象池的alloc方法分配一个对象。 2>对象池基类ObjectPool 其中记录了对象池的通用方法和成员,如下
1 2 3 4 5 6 7 8 9 10 |
protected
Queue queue = new
Queue (); //用来保存池中对象
[SerializeField]
protected
int
_freeObjCount = 0; //池中待分配对象数量
public
int
preAllocCount; //初始化时预分配对象数量
public
int
autoIncreaseCount; //池中可增加对象数量
protected
bool
_binit = false ; //是否初始化
[HideInInspector]
public
GameObject prefab; //prefab引用
[HideInInspector]
public
string
objTypeString; //池中对象描述字符串
|
ObjectPool中的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public
virtual
GameObject alloc( float
lifetime){
//如果没有进行过初始化,先初始化创建池中的对象
if (!_binit){
_init();
|