对于unity之中的对象池技术,想必大家都有多耳闻,但是觉得没有多大的必要去使用,我刚开始学unity时候也是这么想的,但是后来我才意识到,随着你所做的项目的规模的扩大,你可能需要频繁的对某的游戏对象(GameObject)进行实例化,在销毁。假设你在开发一个FPS游戏,玩家,乃至大量的AI机器人都需要频繁的通过实例化“子弹”然后在子弹发生碰撞后销毁。
下面是我写的实例化小兵(协程)以及销毁小兵程序(无对象池)
IEnumerator Test01(int number)
{
numNO = 0;
while (numNO < number)
{
numNO++;
//每个怪产生的时间差
if (boNO > 5)
yield return new WaitForSeconds(numwait / 2);
else
yield return new WaitForSeconds(numwait);
string a = ChooseEnemy(boNO,numNO);
//动态实例化,Load(“路径自定,但必须在Resources文件夹里”)
GameObject enemy = (GameObject)Resources.Load("Prefabs_Enemy/"+a);
enemy.GetComponent<EnemyContrl>().Pos = GameObject.FindGameObjectWithTag("WayPos");
//print(enemy.GetComponent<EnemyContrl>().Pos.name);
GameObject o;
//如果在飞行怪,则拔高5
if (enemy.tag == "FlyEnemy")
{
o = Instantiate(enemy, transform.position + Vector3.up * 5, transform.rotation);
}
else
o = Instantiate(enemy,transform.position,transform.rotation);
o.transform.parent = transform;
}
}
/// <summary>
/// 选择怪物种类,储存动态实例化需要的文件名
/// </summary>
/// <param name="boNO"></param>波次
/// <param name="numNO"></param>怪次
/// <returns></returns>
string ChooseEnemy(int boNO, int numNO)
{
switch(boNO % 3)
{
case 1:
{
if (numNO < 8)
{
str = "AmurTiger_01";
}
else if (numNO < 10 && numNO >= 8)
{
str = "AmurTiger_02";
}
else
{
str = "AmurTiger_03";
}
break;
}
case 2:
{
if (numNO < 8)
{
str = "GiantBat";
}
else if (numNO < 10 && numNO >= 8)
{
str = "GiantBat_2";
}
else
{
str = "GiantBat_3";
}
break;
}
case 3:
{
if (numNO < 8)
{
print("3-1");
str = "Manticore_01";
}
else if (numNO < 10 && numNO >= 8)
{
print("3-2");
str = "Manticore_02";
}
else
{
str = "Manticore_03";
}
break;
}
case 0:
{
if (numNO < 4)
{
str = "GiantBat_2";
}
else if (numNO < 7 && numNO >= 4)
{
str = "AmurTiger_02";
}
else if (numNO <= 10 && numNO >= 7)
{
str = "Manticore_02";
}
break;
}
}
return str;
}
public void EnemyDead(float Blood)
{
if (Blood <= 0)
{
this.gameObject.GetComponent<Collider>().enabled = false;
Destroy(this.gameObject.GetComponent<Rigidbody>());
//杀怪物获得金币
GameData.Instance.money += 10;
Ani.SetBool("Dead", true);
Ani.SetBool("Attack", false);
//agt.SetDestination(this.transform.position);
this.gameObject.GetComponent<NavMeshAgent>().enabled = false;
Timer += Time.deltaTime;
if (Timer >= DestryTime)
{
Destroy(this.gameObject);
}
}
}
当然也有可能是使用触发器形式等等就不一一举例子了。这么操作虽然方便快捷,信手拈来但是这样做会占用很多内存资源,如果频繁这样操作可能会导致游戏卡顿甚至闪退。
那么对象池技术就在这种情况下被引入了进来,所谓的对象池技术就是我们把用到的对象在初始化的时候都放到这个对象池中,需要用到时可以从激活拿来用,不需要用时可以继续禁用放回对象池中,这样大大减少因为大量的实例化和销毁带来的性能消耗。
1.新建Unity项目,首先创建ObjectPoolsManager.cs管理类,利用单例模式封装对象池技术所用的方法,主要是为了方便调用;代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 对象池控制管理脚本
/// </summary>
public class ObjectPoolsManager
{
/// <summary>
/// 1.对象池-列表存储单个对象
/// </summary>
List<GameObject> _poolList;
/// <summary>
/// 2.对象池-字典存储对象
/// </summary>
Dictionary<int,List<GameObject>> _poolDictionary;
private static ObjectPoolsManager _singleton;
public static ObjectPoolsManager GetInstance()
{
if (_singleton == null) {
_singleton = new ObjectPoolsManager ();
}
return _singleton;
}
public ObjectPoolsManager()
{
_poolList=new List<GameObject> ();
_poolDictionary =new Dictionary<int, List<GameObject>>();
}
/// <summary>
/// 实例化并存储游戏对象池中(单个对象)
/// </summary>
/// <param name="gameObject">被创建的游戏对象</param>
/// <param name="pos">游戏对象位置</param>
/// <param name="rotation">游戏对象旋转</param>
public GameObject SetActiveObject(GameObject gameObject,Vector3 pos,Quaternion rotation)
{
if (_poolList.Count>0) {
GameObject result = _poolList [0];
_poolList.Remove (result);
result.SetActive (true);
result.transform.position = pos;
result.transform.rotation = rotation;
return result;
}
return GameObject.Instantiate (gameObject,pos,rotation) as GameObject;;
}
/// <summary>
/// 实例化并存储游戏对象池中(多个对象)
/// </summary>
/// <param name="gameObject">被创建的游戏对象</param>
/// <param name="pos">游戏对象位置</param>
/// <param name="rotation">游戏对象旋转</param>
public GameObject SetActiveMutilObject(GameObject gameObject,Vector3 pos,Quaternion rotation)
{
int key = gameObject.GetInstanceID ();
if (_poolDictionary.ContainsKey (key)) {
if (_poolDictionary [key].Count > 0) {
GameObject result = _poolDictionary [key] [0];
result.SetActive (true);
_poolDictionary [key].Remove (result);
result.transform.position = pos;
result.transform.rotation = rotation;
return result;
}
}
GameObject curObj = GameObject.Instantiate (gameObject,pos,rotation) as GameObject;
curObj.name = gameObject.GetInstanceID ().ToString();
return curObj;
}
/// <summary>
/// 隐藏当前对象,并且添加到列表
/// </summary>
/// <param name="gameObject">被创建的游戏对象</param>
public void SetDisableObject(GameObject gameObject)
{
gameObject.SetActive (false);
_poolList.Add (gameObject);
}
/// <summary>
/// 隐藏当前对象,并且添加到字典中
/// </summary>
/// <param name="gameObject">被创建的游戏对象</param>
public void SetDisableMutilObject(GameObject gameObject)
{
gameObject.SetActive (false);
int key = int.Parse (gameObject.name);
//不包含键名则添加进字典一条新的储存单个对象的List
if (!_poolDictionary.ContainsKey (key)) {
_poolDictionary.Add (key,new List<GameObject>());
}
//将gameObject添加进他所在的List
_poolDictionary[key].Add(gameObject);
}
/// <summary>
/// 延迟隐藏游戏对象,并添加到列表中
/// </summary>
/// <returns>The disable object.</returns>
/// <param name="gameObject">被创建的游戏对象</param>
/// <param name="time">延迟时间</param>
public IEnumerator IEDisableObject(GameObject gameObject,float time)
{
//协程:延迟time秒
yield return new WaitForSeconds (time);
GetInstance ().SetDisableObject (gameObject);
}
/// <summary>
/// 延迟隐藏游戏对象,并添加到字典中
/// </summary>
/// <returns>The disable object.</returns>
/// <param name="gameObject">被创建的游戏对象.</param>
/// <param name="time">延迟时间</param>
public IEnumerator IEDisableMutilObject(GameObject gameObject,float time)
{
//协程:延迟time秒
yield return new WaitForSeconds (time);
GetInstance ().SetDisableMutilObject (gameObject);
}
}
其中:
字典允许值对象为null,并且没有个数限制,所以当get()方法的返回值为null时,可能有两种情况,一种是在集合中没有该键对象,另一种是该键对象没有映射任何值对象,即值对象为null。因此,在_poolDictionary字典中不应该利用get()方法来判断是否存在某个键,而应该利用containsKey()方法来判断
2.接着简单创建一个测试对象池类TestObjectPoolsBehaviour.cs,组件直接挂载到场景中即可;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 测试对象池脚本
/// </summary>
public class TestObjectPoolsBehaviour : MonoBehaviour {
public GameObject TestObj;
public GameObject[] TestObjSet;
public float DelayTime = 3;
void Update ()
{
if (Input.GetKeyDown(KeyCode.A)) {
//单个游戏对象
GameObject obj = ObjectPoolsManager.GetInstance ().SetActiveObject (TestObj,Vector3.zero,TestObj.transform.rotation) as GameObject;
StartCoroutine (ObjectPoolsManager.GetInstance().IEDisableObject(obj,DelayTime));
}
if (Input.GetKeyDown(KeyCode.B)) {
//多个游戏对象
//实例化
GameObject obj = ObjectPoolsManager.GetInstance ().SetActiveMutilObject (TestObjSet[0],Vector3.zero,TestObjSet[0].transform.rotation) as GameObject;
//延迟隐藏
StartCoroutine(ObjectPoolsManager.GetInstance().IEDisableMutilObject(obj,DelayTime));
}
if (Input.GetKeyDown(KeyCode.C)) {
//多个游戏对象
//实例化
GameObject obj = ObjectPoolsManager.GetInstance ().SetActiveMutilObject (TestObjSet[1],Vector3.zero,TestObjSet[1].transform.rotation) as GameObject;
//延迟隐藏
StartCoroutine(ObjectPoolsManager.GetInstance().IEDisableMutilObject(obj,DelayTime));
}
}
}
这样的话,在我们大量,频繁使用某游戏对象时,就可以不必消耗大量内存来频繁的实例化再销毁,完全可以,用对象池,使用SetActive的true,false来“实现实例化与销毁”。
不单再FPS游戏中,在塔防游戏里,也会用到对象池技术。下面是我做的一个塔防游戏Demo,虽然看起来很low,但是重点是学习不是吗。