對象池是一種Unity經常用到的內存管理服務,它的作用在於可以減少創建每個對象的系統開銷。
在Unity游戲開發的過程中經常會創建一些新的對象,如果數量較少還可以接受,如果創建的新對象數量龐大,那么對內存而言是一個極大的隱患。例如射擊游戲當中,每發射一顆子彈,都要創建一個新的子彈對象,那么子彈是數量龐大,可想而知一場游戲當中會創建多少這樣的新對象,那么如果這些子彈創建之后都對游戲起着關鍵且持續性的作用也無可厚非,問題是子彈發射完成之后,幾秒之后就不再擁有任何的意義,一般會將它自動的隱藏,也就是我們所說的SetActive(false),因此大量的非活躍對象出現在游戲場景當中。
為了解決大量創建重復對象造成的內存損耗,我們采用對象池的方式來解決。
下面用一個例子來演示 。
用代碼在plane上自動生成一堵方塊牆,通過點擊屏幕來發射子彈,擊塌這堵牆。
生成牆以及發射子彈的代碼 (掛在攝像機上):
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameController : MonoBehaviour { public int row = 6; public Vector2 offset = new Vector2(); public GameObject cubPrefab; public GameObject bulletPrefab; private RaycastHit hit; public float speed=3; void Start () {
//生成牆 for (int i = 0; i < row; i++) { for (int j= 0; j < row; j++) { Instantiate(cubPrefab, new Vector3(i, j, 0)+new Vector3(offset.x,offset.y,0), Quaternion.identity); } } } // Update is called once per frame void Update () { if (Input.GetMouseButtonDown(0)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray,out hit)) { Vector3 dir = hit.point - Camera.main.transform.position; //從對象池中獲取對象 GameObject bullet = ObjectPool.GetInstance().GetObj("Bullet"); bullet.transform.position = Camera.main.transform.position; bullet.GetComponent<Rigidbody>().velocity = dir.normalized * speed; } } } }
對象池代碼:
(不需要掛載)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ObjectPool { #region 單例 private static ObjectPool instance; private ObjectPool() { pool = new Dictionary<string, List<GameObject>>(); prefabs = new Dictionary<string, GameObject>(); } public static ObjectPool GetInstance() { if (instance==null) { instance = new ObjectPool(); } return instance; } #endregion /// <summary> /// 對象池 /// </summary> private Dictionary<string,List<GameObject>> pool; /// <summary> /// 預設體 /// </summary> private Dictionary<string, GameObject> prefabs; /// <summary> /// 從對象池中獲取對象 /// </summary> /// <param name="objName"></param> /// <returns></returns> public GameObject GetObj(string objName) { //結果對象 GameObject result=null; //判斷是否有該名字的對象池 if (pool.ContainsKey(objName)) { //對象池里有對象 if (pool[objName].Count>0) { //獲取結果 result = pool[objName][0]; //激活對象 result.SetActive(true); //從池中移除該對象 pool[objName].Remove(result); //返回結果 return result; } } //如果沒有該名字的對象池或者該名字對象池沒有對象 GameObject prefab = null; //如果已經加載過該預設體 if (prefabs.ContainsKey(objName)) { prefab = prefabs[objName]; } else //如果沒有加載過該預設體 { //加載預設體 prefab = Resources.Load<GameObject>("Prefabs/"+objName); //更新字典 prefabs.Add(objName, prefab); } //生成 result = UnityEngine.Object.Instantiate(prefab); //改名(去除 Clone) result.name = objName; //返回 return result; } /// <summary> /// 回收對象到對象池 /// </summary> /// <param name="objName"></param> public void RecycleObj(GameObject obj) { //設置為非激活 obj.SetActive(false); //判斷是否有該對象的對象池 if (pool.ContainsKey(obj.name)) { //放置到該對象池 pool[obj.name].Add(obj); } else { //創建該類型的池子,並將對象放入 pool.Add(obj.name, new List<GameObject>() { obj }); } } void Start () { } // Update is called once per frame void Update () { } }
掛載在子彈上,自動回收到對象池的代碼:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Bullet : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } /// <summary> /// 3秒后自動回收到對象池 /// </summary> /// <returns></returns> IEnumerator AutoRecycle() { yield return new WaitForSeconds(3f); ObjectPool.GetInstance().RecycleObj(gameObject); } private void OnEnable() { StartCoroutine(AutoRecycle()); } }
這樣就減少了重復的生成和銷毀子彈產生的內存消耗,優化了性能。