一、对象池的简介
Unity 对象池是一种优化技术,用于管理和重复使用游戏中的对象,以减少资源的创建和销毁开销,从而提高性能。在游戏开发中,经常需要创建和销毁大量的对象,比如子弹、敌人、特效等。使用对象池可以避免频繁地创建和销毁这些对象,而是将它们事先创建好并存放在一个池中,在需要的时候直接从池中取出并重复使用。
对象池的实现通常包括以下几个步骤:
- 创建对象池:在游戏启动时,创建一个对象池,并初始化一定数量的对象放入池中。
- 从对象池中获取对象:当游戏需要使用某个对象时,可以从对象池中获取一个可用的对象。
- 使用对象:使用获取到的对象进行相应的操作,比如设置位置、旋转、播放动画等。
- 对象回收:当不再需要使用该对象时,将其重新放回对象池中以供后续使用。
通过使用对象池,可以减少频繁的内存分配和垃圾回收,提高游戏的性能和流畅度。同时,对象池也可以控制对象的数量,避免过多的对象同时存在导致内存占用过高。在实际开发中,可以根据游戏的需求和性能优化的要求合理地使用对象池技术 。
二、对象池的基本构造
首先我们先用一种简单的写法实现一个对象池功能:
using UnityEngine;
using System.Collections.Generic;
public class ObjectPool : MonoBehaviour
{
public GameObject prefab;
public int poolSize;
private List<GameObject> objectPool;
private void Start()
{
// 创建对象池
objectPool = new List<GameObject>();
for (int i = 0; i < poolSize; i++)
{
GameObject obj = Instantiate(prefab);
obj.SetActive(false);
objectPool.Add(obj);
}
}
public GameObject GetObject()
{
// 从对象池中获取可用对象
foreach (GameObject obj in objectPool)
{
if (!obj.activeInHierarchy)
{
obj.SetActive(true);
return obj;
}
}
// 如果池中没有可用对象,则动态创建一个新对象
GameObject newObj = Instantiate(prefab);
objectPool.Add(newObj);
return newObj;
}
public void ReleaseObject(GameObject obj)
{
// 重置对象状态并返回到对象池
obj.SetActive(false);
obj.transform.position = Vector3.zero;
}
}
上述我们用了List实现了对象池的操作,但是我们可以发现在我们使用对象池中的元素的时候我们需要进行一个遍历,判断对象池中的元素是否在被使用,当发现有没有被使用的元素时候就进行启用并返回。
但如果当我们的对象池存储的数据量比较大的时候,我们又需要频繁遍历这时候我们的效率非常的底,比如:发射子弹,生成敌人等等...
那么我们接下来再看这段代码:
using UnityEngine;
using System.Collections.Generic;
public class ObjectPool : MonoBehaviour
{
public GameObject prefab;
public int poolSize;
private Queue<GameObject> objectPool;
private void Start()
{
// 创建对象池
objectPool = new Queue<GameObject>();
for (int i = 0; i < poolSize; i++)
{
GameObject obj = Instantiate(prefab);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
}
public GameObject GetObject()
{
// 从对象池中查询是否存在未引用对象
if(objectPool.Count == 0)
{
// 如果池中没有可用对象,则动态创建一个新对象
GameObject newObj = Instantiate(prefab);
objectPool.Enqueue(newObj);
return newObj;
}
// 有直接出队进行返回
return objectPool.Dequeue();
}
public void ReleaseObject(GameObject obj)
{
// 重置对象状态并返回到对象池
obj.SetActive(false);
obj.transform.position = Vector3.zero;
objectPool.Enqueue(obj);
}
}
这段代码是将List换成了Queue,我们可以不再需要每次进行调用元素的时候都需要遍历查找是否存在未引用对象了。
不光是Queue,Stack也是一样的,只是将上面代码中的出队和入队的函数换成我们的入栈和出栈即可,道理都是一样的。
三、运用对象池实现的简单示例
接下来我们实现一个简单的示例:
我们实现一下飙血的效果,而飙血用到的Text我们就会从对象池中进行存取
首先我们先实现点击屏幕让Cube进行位置的变换
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ControlCubePos : MonoBehaviour
{
private GameObject cube_Prefab;//Cube
private Camera camera_Main;//相机
private Vector3 cur_PointPos;//点击的位置
void Start()
{
cube_Prefab = GameObject.Find("Cube");
camera_Main = Camera.main;
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
//点击刷新位置
cur_PointPos = camera_Main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10));
cube_Prefab.transform.position = cur_PointPos;
}
}
}
现在我们实现了鼠标点击跟换Cube的位置
鼠标点击跟换Cube的位置
这个功能只是一个小插曲,接下来要用对象池实现飘血效果,接下来使用的对象池是将对象池的基本构造中所用的示例进行了一点点的改动,首先将它继承了一个单例,之后将我们的队列改成了栈的形式,又将GameObject的类型改成了Hp的类型(Hp是自定义的脚本)。
//飘血功能
if (Input.GetKeyDown(KeyCode.Space))
{
Hp text_Hp = ObjectPool.Instance.GetObject();
text_Hp.gameObject.SetActive(true);
text_Hp.InitPos(camera_Main.WorldToScreenPoint(cube_Prefab.transform.position));
}
这是一段在我们ControlCubePos 类中的Update函数新填的一段功能,它先从对象池中拿取一个未被引用的元素并进行启用,之后调用了Hp自定义类中的InitPos的函数,它是进行位置数据初始化的。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Hp : MonoBehaviour
{
private Vector2 m_StartPos;//初始位置
private Vector2 m_TargetPos;//目标位置
private bool action_IsOn;//开始运动 ---- 飘血
public RectTransform rect;
/// <summary>
/// 位置初始化
/// </summary>
/// <param name="_pos">Cube的位置</param>
public void InitPos(Vector2 _pos)
{
rect = transform as RectTransform;
//设置锚点位置
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.zero;
//赋值初始化位置
rect.anchoredPosition = _pos;
m_TargetPos = _pos + new Vector2(50, 100);
action_IsOn = true;
}
// Update is called once per frame
void Update()
{
if (action_IsOn)
{
//text移动
rect.anchoredPosition = Vector2.Lerp(rect.anchoredPosition, m_TargetPos, Time.deltaTime * 3);
if (Vector2.Distance(rect.anchoredPosition, m_TargetPos) <= 10)
{
ObjectPool.Instance.ReleaseObject(this);
action_IsOn = false;
}
}
}
}
在InitPos函数中改变了它的当前状态,使他可以进行往上飘的动作,当到达位置之后,调用ReleaseObject方法再放回我们的对象池当中。
接下来我们可以看一下整体的效果
飘血整体效果
这样一个非常简单的对象池小示例就完成了!!!
对于对象池的时候,在平时开发的过程当中运用还是非常广泛的,在这里我们只是简单的展示了一下对象池的基本原理,如果还想更加了解对象池的运用,可以在网上下载一些具体的案例,以便更加具体的了解对象池。