一、简介

先说说为什么要使用对象池

在Unity游戏运行时,经常需要生成一些物体,例如子弹、敌人等。虽然Unity中有Instantiate()方法可以使用,但是在某些情况下并不高效。特别是对于那些需要大量生成又需要大量销毁的物体来说,多次重复调用Instantiate()方法和Destory()方法会造成大量的性能消耗。

这时使用对象池是一个更好的选择。

那么什么是对象池呢?

简单来说,就是在一开始创建一些物体(或对象),将它们隐藏(休眠)起来,对象池就是这些物体的集合,当需要使用的时候,就将需要的对象激活然后使用,而不是实例化生成。如果对象池中的对象消耗完了可以扩大对象池或者重新再次使用对象池中的对象。

一般情况下,一个对象池中存放的都是一类物体,我们一般希望创建多个对象池来存储不同类型的物体。

例如我们需要两个对象池来分别存储球体和立方体。

那么可以选择使用Dictionary来创建对象池,这样不仅可以创建对象池,还能指定每个对象池存储对象的类型。这样就能通过Tag来访问对象池。

至于对象池中可以使用Queue(队列)来存储具体的对象,队列不仅可以快速获取到第一个对象,能够按顺序获取对象。如果出队的对象在使用完成之后再次入队,那么这样就可以一直循环来重用对象。

二、Unity中的具体实现

新建一个Unity项目,在场景中添加一个空物体,命名为ObjectPool

同时制作一个黑色的地面便于显示和观察



新建脚本ObjectPooler添加到ObjectPool上

public class ObjectPooler : MonoBehaviour
{
[System.Serializable]
public class Pool //对象池类
{
public string tag; //对象池的Tag(名称)
public GameObject prefab; //对象池所保存的物体类型
public int size; //对象池的大小
}
public List<Pool> pools; Dictionary<string, Queue<GameObject>> poolDictionary; //声明字典 void Start()
{
//实例化字典 对象池的Tag 对象池保存的物体
poolDictionary = new Dictionary<string, Queue<GameObject>>();
}
}

在Inspector中添加对应的数据,这里简单创建了立方体和球体并设为了预制体

然后继续修改ObjectPooler

public class ObjectPooler : MonoBehaviour
{
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
public List<Pool> pools;
Dictionary<string, Queue<GameObject>> poolDictionary; public static ObjectPooler Instance; //单例模式,便于访问对象池
private void Awake()
{
Instance = this;
}
void Start()
{
poolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (Pool pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>(); //为每个对象池创建队列
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
obj.SetActive(false); //隐藏对象池中的对象
objectPool.Enqueue(obj);//将对象入队
}
poolDictionary.Add(pool.tag, objectPool); //添加到字典后可以通过tag来快速访问对象池
}
} public GameObject SpawnFromPool(string tag, Vector3 positon, Quaternion rotation) //从对象池中获取对象的方法
{
if (!poolDictionary.ContainsKey(tag)) //如果对象池字典中不包含所需的对象池
{
Debug.Log("Pool: " + tag + " does not exist");
return null;
} GameObject objectToSpawn = poolDictionary[tag].Dequeue(); //出队,从对象池中获取所需的对象
objectToSpawn.transform.position = positon; //设置获取到的对象的位置
objectToSpawn.transform.rotation = rotation; //设置对象的旋转
objectToSpawn.SetActive(true); //将对象从隐藏设为激活 poolDictionary[tag].Enqueue(objectToSpawn); //再次入队,可以重复使用,如果需要的对象数量超过对象池内对象的数量,在考虑扩大对象池
//这样重复使用就不必一直生成和消耗对象,节约了大量性能
return objectToSpawn; //返回对象
}
}

新建脚本CubeSpanwer,来使用对象池生成物体

public class CubeSpanwer : MonoBehaviour
{
ObjectPooler objectPooler;
private void Start()
{
objectPooler = ObjectPooler.Instance;
}
private void FixedUpdate()
{
//这样会高效一点,比ObjectPooler.Instance
objectPooler.SpawnFromPool("Cube", transform.position, Quaternion.identity);
}
}

新建脚本Cube,添加到Cube预制体上,让其在生成时添加一个力便于观察

注意:为了方便观察这里移除了Cube上的BoxCollider

public class Cube : MonoBehaviour
{
void Start()
{
GetComponent<Rigidbody>().AddForce(new Vector3(Random.Range(0f, 0.2f), 1f, Random.Range(0f, 0.2f)));
}
}

我们发现Cube并没有向上飞起而是堆叠在一起



这时因为Cube只在生成时在Start中添加了力,只调用了一次,但马上就被隐藏放入对象池了,等到再次取出时,并没有任何方法的调用,只是单纯设置位置

我们需要让cube对象知道自己被重用了,再次调用添加力的方法

新建接口 IPooledObject

public interface IPooledObject
{
void OnObjectSpawn();
}

然后让Cube继承该接口

public class Cube : MonoBehaviour, IPooledObject
{
private Rigidbody rig;
public void OnObjectSpawn()
{
rig = gameObject.GetComponent<Rigidbody>();
rig.velocity = Vector3.zero; //将速度重置为0,物体在被隐藏时仍然具有速度,不然重用时仍然具有向下的速度
rig.AddForce(new Vector3(Random.Range(0, 0.2f), 10, Random.Range(0, 0.2f)), ForceMode.Impulse);
}
}

然后修改ObjectPooler,让Cube在被重用时调用重用的方法

public GameObject SpawnFromPool(string tag, Vector3 positon, Quaternion rotation)     //从对象池中获取对象的方法
{
......
IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();
if (pooledObj != null) //判断,并不是所有对象都继承了该接口,例如Cube我想让它向上飞,Sphere则让它直接生成,Sphere就不必继承IPoolObject接口
{
pooledObj.OnObjectSpawn(); //调用重用时的方法
}
poolDictionary[tag].Enqueue(objectToSpawn);
return objectToSpawn;
}

运行结果:

Cube从CubeSpawner不断生成,可以自行设置计时器来限制生成的速度

Unity实现简单的对象池的更多相关文章

  1. 在C#中实现简单的对象池

    当我们频繁创建删除大量对象的时候,对象的创建删除所造成的开销就不容小觑了.为了提高性能,我们往往需要实现一个对象池作为Cache:使用对象时,它从池中提取.用完对象时,它放回池中.从而减少创建对象的开 ...

  2. Java网络与多线程系列之1:实现一个简单的对象池

    前言 为什么要从对象池开始呢,先从一个网络IO操作的demo说起 比如下面这段代码,显而易见已经在代码中使用了一个固定大小的线程池,所以现在的重点在实现Runnble接口的匿名对象上,这个对象每次创建 ...

  3. Unity中的万能对象池

    本文为博主原创文章,欢迎转载.请保留博主链接http://blog.csdn.net/andrewfan Unity编程标准导引-3.4 Unity中的万能对象池 本节通过一个简单的射击子弹的示例来介 ...

  4. unity游戏开发_对象池

    现在假设游戏中我们需要实现一个这样功能,按下鼠标左键,发射一颗子弹,3秒之后消失.在这个功能中,我们发射了上百上千发子弹,就需要实例化生成上百上千次.这时候,我们就需要使用对象池这个概念,每次实例化生 ...

  5. Unity3D|-使用ScriptableObject脚本化对象来制作一个简单的对象池

    ScriptableObject是一个用于生成单独Asset的结构.同时,它也能被称为是Unity中用于处理序列化的结构. 可以作为我们存储资源数据的有效方案.同时此资源可以作为我们AB包的有效资源! ...

  6. Unity 对象池的使用

    在游戏开发过程中,我们经常会遇到游戏发布后,测试时玩着玩着明显的感觉到有卡顿现象.出现这种现象的有两个原因:一是游戏优化的不够好或者游戏逻辑本身设计的就有问题,二是手机硬件不行.好吧,对于作为程序员的 ...

  7. [译]Unity3D内存管理——对象池(Object Pool)

    原文地址:C# Memory Management for Unity Developers (part 3 of 3), 其实从原文标题可以看出,这是一系列文章中的第三篇,前两篇讲解了从C#语言本身 ...

  8. common-pool2对象池(连接池)的介绍及使用

    我们在服务器开发的过程中,往往会有一些对象,它的创建和初始化需要的时间比较长,比如数据库连接,网络IO,大数据对象等.在大量使用这些对象时,如果不采用一些技术优化,就会造成一些不可忽略的性能影响.一种 ...

  9. 【h5-egret】深入浅出对象池

    最近看到对象池这一块的东西,是频繁创建和删除类型游戏优化性能的一个解决方案. 简单来讲对象池就是个数组,把不用的对象放进去,因为数组还保存了对象的引用,所以对象不会被回收,等需要用的时候再从数组中取出 ...

随机推荐

  1. SpringSecurity集成启动报 In the composition of all global method configuration, no annotation support was actually activated 异常

    异常内容: Caused by: java.lang.IllegalStateException: In the composition of all global method configurat ...

  2. vue使用svg,animate事件绑定无效问题及解决方法

    由于使用svg制作圆形进度条,但是进度展示的太生硬,没有过渡圆滑的效果,所以使用 animate(在svg元素里可以查到) 元素标签,但 这样使用了,还是没有效果,我前端使用的 vue ,所以通过 @ ...

  3. CKEditor禁用浏览服务器的功能

    在CKeditor的config.js文件中,添加以下内容,重启服务器,图片.flash.video中的浏览服务器按钮就会消失掉 /*按下" 浏览服务器"按钮时应启动的外部文件管理 ...

  4. Redis ZSet Type

    Redis有序集合的操作命令和对应的api如下: zadd [zset] sco 'value' JedisAPI:public Long zadd(final String key, final d ...

  5. Spring Mvc 源代码之我见 一

    spring mvc 是一个web框架,包括controller.model.view 三大块.其中,核心在于model这个模块,用于处理请求的request. 和之前的博客一样,关键的代码,我会标注 ...

  6. Java设置方法模板

  7. Azure DevOps (八) 通过流水线编译Docker镜像

    上一篇文章我们完成了最简单的传统部署:上传应用到服务器上使用守护进程进行应用的部署. 本篇文章我们开始研究容器化和流水线的协作. 在开始操作之前,我们首先需要准备一下我们的dockerfile,这里我 ...

  8. 如何做好移动端的响应式设计:Viewport控制

    新人翻译,欢迎转载~ 英文原文地址:http://bitsofco.de/2015/respove-design-viewport/ 原文例程地址:https://github.com/ireade/ ...

  9. java的原子类到底是啥?ABA,CAS又是些什么?

    1)解决并发不是用锁就能解决吗,那SDK干嘛还要搞个原子类出来? 锁虽然能解决,但是加锁解锁始终还是对性能是有影响的,并且使用不当可能会造成死锁之类的问题. 2)原子类是怎样使用的,比如说我要实现一个 ...

  10. ubantu系统之 Ubuntu14.04安装Samba实现文件共享

    1 安装    sudo apt-get install samba  2 配置    打开Samba配置文件:    vim /etc/samba/smb.conf      在其最后添加:    ...