牛骨文教育服务平台(让学习变的简单)
博文笔记

Unity3d中对象池(ObjectPool)的实现思路

创建时间:2016-10-25 投稿人: 浏览次数:2404
概述 什么是对象池?   池(Pool),与集合在某种意义上有些相似。 水池,是一定数量的水的集合;内存池,是一定数量的已经分配好的内存的集合;线程池,是一定数量的已经创建好的线程的集合。那么,对象池,顾名思义就是一定数量的已经创建好的对象(Object)的集合[1]。   在C/C++的程序中,如果一种对象,你要经常用malloc/free(或new/delete)来创建、销毁,这样子一方面开销会比较大,另一方面会产生很多内存碎片,程序跑的时间一长,性能就会下降。这个时候,就产生了对象池。可以事先创建好一批对象,放在一个集合中,以后每当程序需要新的对象时候,都从对象池里获取,程序用完该对象后,再把该对象归还给对象池。这样,就会少了很多的malloc/free(new/delete)的调用,在一定程度上提高了系统的性能,尤其在动态内存分配比较频繁的程序中效果较为明显。
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();
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。