游戲設計模式——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         obj.transform.SetParentSafe(par.transform);
 5         obj.SetActive(false);
 6 
 7         if (pool.ContainsKey(obj.name))
 8         {
 9             if (pool[obj.name].Count < maxCount)
10             {
11                 pool[obj.name].Add(obj);
12             }
13         }
14         else
15         {
16             pool.Add(obj.name, new List<GameObject>() { obj });
17         }
18     }

這里將回收的對象統一放在了場景主攝像機下,你也可以選擇放在自己喜歡的位置。

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

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

如果回收對象時改變了父物體,最好在設置父物體前后記錄下對象的本地位置,旋轉和縮放,可以寫了一個擴展方法用於安全設置父物體:

 1     public static void SetParentSafe(this Transform transform,Transform parent)
 2     {
 3         var lp = transform.localPosition;
 4         var lr = transform.localRotation;
 5         var ls = transform.localScale;
 6         transform.SetParent(parent);
 7         transform.localPosition = lp;
 8         transform.localRotation = lr;
 9         transform.localScale = ls;
10     }

 

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

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         result.transform.SetParentSafe(parent);
5         return result;
6     }

 

下面是完整腳本:

 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                 if (result != null)
19                 {
20                     result.SetActive(true);
21                     pool[perfab.name].Remove(result);
22                     return result;
23                 }
24                 else
25                 {
26                     pool.Remove(perfab.name);
27                 }
28             }
29         }
30         //池子中缺少
31         result = Object.Instantiate(perfab);
32         result.name = perfab.name;
33         RecycleObj(result);
34         GetObj(result);
35         return result;
36     }
37 
38     public GameObject GetObj(GameObject perfab, Transform parent)
39     {
40         var result = GetObj(perfab);
41         result.transform.SetParentSafe(parent);
42         return result;
43     }
44 
45     public void RecycleObj(GameObject obj)
46     {
47         var par = Camera.main;
48         obj.transform.SetParentSafe(par.transform);
49         obj.SetActive(false);
50 
51         if (pool.ContainsKey(obj.name))
52         {
53             if (pool[obj.name].Count < maxCount)
54             {
55                 pool[obj.name].Add(obj);
56             }
57         }
58         else
59         {
60             pool.Add(obj.name, new List<GameObject>() { obj });
61         }
62     }
63 
64     public void RecycleAllChildren(GameObject parent)
65     {
66         for (; parent.transform.childCount > 0;)
67         {
68             var tar = parent.transform.GetChild(0).gameObject;
69             RecycleObj(tar);
70         }
71     }
72 
73     public void Clear()
74     {
75         pool.Clear();
76     }
77 }
View Code

因為是用名字作為存儲的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();

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM