一、簡介
先說說為什么要使用物件池
在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不斷生成,可以自行設定計時器來限制生成的速度

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/469724.html
標籤:其他
