天天看點

遊戲設計模式——Unity對象池

對象池這個名字聽起來好像不明覺厲,其實就是将一系列需要反複建立和銷毀的對象存儲在一個看不到的地方,下次用同樣的東西時往這裡取,類似于一個存放備用物質的倉庫。

它的好處就是避免了反複執行個體化個體的運算,能減少大量記憶體碎片,當然你需要更多的空間來存這些備用對象,相信使用這些空間是非常值得的。

最常見的應用就是子彈的建立和銷毀。

一般對象池都是一個全局性的通用腳本,可以采用單例模式來設計。

https://www.cnblogs.com/koshio0219/p/11203631.html

對象池至少包含以下兩個基本功能:

1.從池中取出指定類型的對象

2.回收各式各樣的對象到池中

先定義對象池和池子的容量:

1     private const int maxCount = 128;
2     private Dictionary<string, List<GameObject>> pool = new Dictionary<string, List<GameObject>>();           

複制

容量是一個常量,最好取二的幂值,這樣的話可以剛好占用所有記憶體位的資源,避免浪費。

這裡池子用字典辨別,key為對象的名字,這樣比較好記,你用InstanceID也沒問題。

每個同樣的對象一般在池子中可以有很多,用一個List來存。

下面先定義回收對象的方法:

1     public void RecycleObj(GameObject obj)
 2     {
 3         var par = Camera.main;
 4         var localPos = obj.transform.localPosition;
 5         obj.transform.SetParent(par.transform);
 6         obj.transform.localPosition = localPos;
 7         obj.SetActive(false);
 8 
 9         if (pool.ContainsKey(obj.name))
10         {
11             if (pool[obj.name].Count < maxCount)
12             {
13                 pool[obj.name].Add(obj);
14             }
15         }
16         else
17         {
18             pool.Add(obj.name, new List<GameObject>() { obj });
19         }
20     }           

複制

這裡将回收的對象統一放在了場景主錄影機下,你也可以選擇放在自己喜歡的位置。

回收對象就是先把對象隐藏,然後看池子中有沒有這一類對象,有的話沒有超過容量上限就直接扔進去。

如果沒有這類對象,那就建立這一類型對象的Key值(名字:比如說螃蟹),順便添加第一隻螃蟹。

經常會遇到要批量回收進池子的情況:

1     public void RecycleAllChildren(GameObject parent)
2     {
3         for (; parent.transform.childCount > 0;)
4         {
5             var tar = parent.transform.GetChild(0).gameObject;
6             RecycleObj(tar);
7         }
8     }           

複制

對象可以回收了,那怎麼取呢,自然也是能從池子裡取就從池子裡取,實在不行才去執行個體化:

1     public GameObject GetObj(GameObject perfab)
 2     {
 3         //池子中有
 4         GameObject result = null;
 5         if (pool.ContainsKey(perfab.name))
 6         {
 7             if (pool[perfab.name].Count > 0)
 8             {
 9                 result = pool[perfab.name][0];
10                 result.SetActive(true);
11                 pool[perfab.name].Remove(result);
12                 return result;
13             }
14         }
15         //池子中缺少
16         result = Object.Instantiate(perfab);
17         result.name = perfab.name;
18         RecycleObj(result);
19         GetObj(result);
20         return result;
21     }           

複制

如果池子中有對象,取出來之後記得要把這個對象從該類對象的清單中移除,不然下次可能又會取到這家夥,而這家夥已經要派去做别的了。

如果池子中缺少對象,那就隻能執行個體化了,要注意把執行個體化後的對應改為大家都一樣的名字,這樣友善下一次取能找到它。

沒有對象的情況下,我這裡又重新回收了一下再取一次,你也可以直接傳回該對象,相當于在取的時候不存在這類對象的話我提前做了标記。

和Instantiate方法一樣,加一個可以設定父對象的重載方法:

1     public GameObject GetObj(GameObject perfab, Transform parent)
2     {
3         var result = GetObj(perfab);
4         var localPos = result.transform.localPosition;
5         result.transform.SetParent(parent);
6         result.transform.localPosition = localPos;
7         return result;
8     }           

複制

下面是完整腳本:

1 using System.Collections.Generic;
 2 using UnityEngine;
 3 
 4 public class ObjectPool:Singleton<ObjectPool>
 5 {
 6     private const int maxCount = 128;
 7     private Dictionary<string, List<GameObject>> pool = new Dictionary<string, List<GameObject>>();
 8 
 9     public GameObject GetObj(GameObject perfab)
10     {
11         //池子中有
12         GameObject result = null;
13         if (pool.ContainsKey(perfab.name))
14         {
15             if (pool[perfab.name].Count > 0)
16             {
17                 result = pool[perfab.name][0];
18                 result.SetActive(true);
19                 pool[perfab.name].Remove(result);
20                 return result;
21             }
22         }
23         //池子中缺少
24         result = Object.Instantiate(perfab);
25         result.name = perfab.name;
26         RecycleObj(result);
27         GetObj(result);
28         return result;
29     }
30 
31     public GameObject GetObj(GameObject perfab, Transform parent)
32     {
33         var result = GetObj(perfab);
34         var localPos = result.transform.localPosition;
35         result.transform.SetParent(parent);
36         result.transform.localPosition = localPos;
37         return result;
38     }
39 
40     public void RecycleObj(GameObject obj)
41     {
42         var par = Camera.main;
43         var localPos = obj.transform.localPosition;
44         obj.transform.SetParent(par.transform);
45         obj.transform.localPosition = localPos;
46         obj.SetActive(false);
47 
48         if (pool.ContainsKey(obj.name))
49         {
50             if (pool[obj.name].Count < maxCount)
51             {
52                 pool[obj.name].Add(obj);
53             }
54         }
55         else
56         {
57             pool.Add(obj.name, new List<GameObject>() { obj });
58         }
59     }
60 
61     public void RecycleAllChildren(GameObject parent)
62     {
63         for (; parent.transform.childCount > 0;)
64         {
65             var tar = parent.transform.GetChild(0).gameObject;
66             RecycleObj(tar);
67         }
68     }
69 
70     public void Clear()
71     {
72         pool.Clear();
73     }
74 }           

複制

因為是用名字作為存儲的Key值,是以不同類的物體命名不能相同,不然可能會取錯對象。

另外由于上面的腳本有更改父物體的情況,可能會出現物體的縮放發生變化,在取出物體之後也可以對transform進行歸位:

1     public static void ResetLocal(this Transform transform)
2     {
3         transform.localPosition = Vector3.zero;
4         transform.localRotation = Quaternion.identity;
5         transform.localScale = Vector3.one;
6     }           

複制

上面是對Transform類的一個擴充方法,例如:

1  var ins = ObjectPool.Instance.GetObj(bulletPrefab, parent.transform);
2  ins.transform.ResetLocal();
           

複制