Unity实现简单的对象池
一、简介
先说说为什么要使用对象池
在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实现简单的对象池的更多相关文章
- 在C#中实现简单的对象池
当我们频繁创建删除大量对象的时候,对象的创建删除所造成的开销就不容小觑了.为了提高性能,我们往往需要实现一个对象池作为Cache:使用对象时,它从池中提取.用完对象时,它放回池中.从而减少创建对象的开 ...
- Java网络与多线程系列之1:实现一个简单的对象池
前言 为什么要从对象池开始呢,先从一个网络IO操作的demo说起 比如下面这段代码,显而易见已经在代码中使用了一个固定大小的线程池,所以现在的重点在实现Runnble接口的匿名对象上,这个对象每次创建 ...
- Unity中的万能对象池
本文为博主原创文章,欢迎转载.请保留博主链接http://blog.csdn.net/andrewfan Unity编程标准导引-3.4 Unity中的万能对象池 本节通过一个简单的射击子弹的示例来介 ...
- unity游戏开发_对象池
现在假设游戏中我们需要实现一个这样功能,按下鼠标左键,发射一颗子弹,3秒之后消失.在这个功能中,我们发射了上百上千发子弹,就需要实例化生成上百上千次.这时候,我们就需要使用对象池这个概念,每次实例化生 ...
- Unity3D|-使用ScriptableObject脚本化对象来制作一个简单的对象池
ScriptableObject是一个用于生成单独Asset的结构.同时,它也能被称为是Unity中用于处理序列化的结构. 可以作为我们存储资源数据的有效方案.同时此资源可以作为我们AB包的有效资源! ...
- Unity 对象池的使用
在游戏开发过程中,我们经常会遇到游戏发布后,测试时玩着玩着明显的感觉到有卡顿现象.出现这种现象的有两个原因:一是游戏优化的不够好或者游戏逻辑本身设计的就有问题,二是手机硬件不行.好吧,对于作为程序员的 ...
- [译]Unity3D内存管理——对象池(Object Pool)
原文地址:C# Memory Management for Unity Developers (part 3 of 3), 其实从原文标题可以看出,这是一系列文章中的第三篇,前两篇讲解了从C#语言本身 ...
- common-pool2对象池(连接池)的介绍及使用
我们在服务器开发的过程中,往往会有一些对象,它的创建和初始化需要的时间比较长,比如数据库连接,网络IO,大数据对象等.在大量使用这些对象时,如果不采用一些技术优化,就会造成一些不可忽略的性能影响.一种 ...
- 【h5-egret】深入浅出对象池
最近看到对象池这一块的东西,是频繁创建和删除类型游戏优化性能的一个解决方案. 简单来讲对象池就是个数组,把不用的对象放进去,因为数组还保存了对象的引用,所以对象不会被回收,等需要用的时候再从数组中取出 ...
随机推荐
- Springmvc入门基础(三) ---与mybatis框架整合
1.创建数据库springmvc及表items,且插入一些数据 DROP TABLE IF EXISTS `items`; CREATE TABLE `items` ( `id` int(11) NO ...
- 什么是 bean 的自动装配?
Spring 容器能够自动装配相互合作的 bean,这意味着容器不需要和配置,能通 过 Bean 工厂自动处理 bean 之间的协作.
- remote debug 的详细配置
一.remote debug 的简单介绍 何为远程debug,项目写完后就需要进入到测试环节,将代码打包发布到测试环境(服务器)上.这时候测试人员测试出一个缺陷(bug).由于代码已经发布到测试环境, ...
- 寄存器、特殊功能寄存器和ram之间的区别联系
存储器在CPU外,一般指硬盘,U盘等可以在切断电源后保存资料的设备,容量一般比较大,缺点是读写速度都很慢,普通的机械硬盘读写速度一般是50MB/S左右. 内存和寄存器就是为了解决存储器读写速度慢而产生 ...
- 《深入理解ES6》笔记—— Promise与异步编程(11)
为什么要异步编程 我们在写前端代码时,经常会对dom做事件处理操作,比如点击.激活焦点.失去焦点等:再比如我们用ajax请求数据,使用回调函数获取返回值.这些都属于异步编程. 也许你已经大概知道Jav ...
- 论文阅读-Clustering temporal disease networks to assist clinical decision support systems in visual analytics of comorbidity progression
一.问题描述: 二.相关工作: 三.方法描述: 四.实验及结果
- PAT B1081 检查密码
题目描述: 本题要求你帮助某网站的用户注册模块写一个密码合法性检查的小功能.该网站要求用户设置的密码必须由不少于6个字符组成,并且只能有英文字母.数字和小数点 .,还必须既有字母也有数字. 输入格式: ...
- JS函数传递参数是是按值传递
JavaScript在传参的时候只有一种传递方法那就是按值传递(来自红宝书第四版本) 函数在传递参数的时候会把实参的值拷贝过来一份,而基础类型数据值是存在内存中,在拷贝的时候会复制出来一份,而引用类型 ...
- openlayer路线箭头
// 用于设置线串所在的矢量图层样式的函数 var styleFunction = function(feature,res){ //轨迹线图形 var trackLine= feature.getG ...
- C++---继承和派生
继承和派生 在C++中, 代码重用是通过继承机制来实现的 继承, 就是在一个已经存在的类的基础上, 再建议一个新类 从已经有的类派生出新的类, 派生类就继承了基类的特征, 包括成员和方法 继承可以完成 ...