最近都在忙別的事了,今天忙里偷閑了解了一下對象池是啥玩意,簡單記錄一下。
還是個正在學習的萌新,如果寫的不好請見諒。
對象池是啥
在了解對象池之后,我才意識到以前寫的代碼有多么蠢,當場景中有一些重復的需要生成和銷毀的物體時(比如地上可拾取的金幣),我們常常調用Instantiate和Destroy函數去實現,這造成了大量的性能開銷,尤其是當游戲物體掛載腳本時,腳本中的Awake()、OnEnable()、OnDestroy()等方法不斷被調用,對性能造成了很大的負擔,所以在復用性強的游戲中緩存池是十分重要的。
對象池的原理就是我們把需要復用的物體存在池子里,需要“銷毀物體”時,通過SetActive方法暫時讓物體隱藏而不是真的銷毀,需要“生成物體”時,先看看池子里有沒有之前隱藏的物體,如果有就直接將其激活,沒有的話再去生成物體,這樣我們真正需要去生成物體的次數永遠是場景中同時存在該物體的最大值,而不是每次使用都去生成和銷毀。
對象池實現
我在unity中簡單實現了一個對象池,用字典的形式存儲每種物體對應的池子(也就是List),取出物體時使用一個字符串代表該物體,然后將該物體的預制體放在Resources目錄下,注意預制體的名字要和傳入的參數相同。
需要注意的是,我們只是隱藏了物體,而物體的位置、旋轉等信息並沒有改變,所以當我們下次取出它時,它的數據還和原來一樣,這種數據通常稱為“臟數據”,所以我們可以讓復用的物體都繼承一個父類,提供兩個抽象方法,讓子物體去重寫,當把物體取出和放回池子時調用這兩個方法去處理數據。
public abstract class RecyclableObject:MonoBehaviour { public abstract void OnSpawn(); public abstract void OnUnspawn(); }
取出和放回物體時,可以使用SendMessage來調用子類身上的方法處理臟數據,比如調整位置之類的。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ObjectPool { //單例 private static ObjectPool _instance; public static ObjectPool Instance { get { if (_instance == null) { _instance = new ObjectPool(); } return _instance; } } public Dictionary<string, List<GameObject>> poolsDict = new Dictionary<string, List<GameObject>>(); //取出物體 public void Spawn(string name,Transform parent) { //如果沒有這個類型的池子就創建一個 if(!poolsDict.ContainsKey(name)) { poolsDict.Add(name, new List<GameObject>()); } //得到池子 List<GameObject> ObjList; poolsDict.TryGetValue(name, out ObjList); //在池子里尋找被隱藏的游戲物體 GameObject go = null; foreach(var obj in ObjList) { if(!obj.activeSelf) { go = obj; } } if(go==null)//不存在隱藏的游戲物體 { go = Object.Instantiate(Resources.Load<GameObject>(name)); ObjList.Add(go); } else//存在隱藏的游戲物體 { go.SetActive(true); } go.transform.parent = parent; go.SendMessage("OnSpawn", SendMessageOptions.DontRequireReceiver); } //回收物體 public void Unspawn(GameObject go) { foreach(List<GameObject> list in poolsDict.Values) { if(list.Contains(go)&&go.activeSelf) { go.SendMessage("OnUnspawn", SendMessageOptions.DontRequireReceiver); go.SetActive(false); } } } //銷毀池子 public void ClearPool(string name) { if(poolsDict.ContainsKey(name)) { foreach(GameObject go in poolsDict[name]) { Object.Destroy(go); } poolsDict.Remove(name); } } }
馬上要參加計算機設計大賽的省賽和藍橋杯的國賽,所以暫時沒時間學游戲開發了,希望比賽能有個好結果,加油吧。