游戏设计模式——Unity对象池

对象池这个名字听起来好像不明觉厉,其实就是将一系列须要反复建立和销毁的对象存储在一个看不到的地方,下次用一样的东西时往这里取,相似于一个存放备用物质的仓库。html

它的好处就是避免了反复实例化个体的运算,能减小大量内存碎片,固然你须要更多的空间来存这些备用对象,相信使用这些空间是很是值得的。ide

最多见的应用就是子弹的建立和销毁。this

 

通常对象池都是一个全局性的通用脚本,能够采用单例模式来设计。spa

http://www.javashuo.com/article/p-pseccouf-gs.html设计

 

对象池至少包含如下两个基本功能:code

1.从池中取出指定类型的对象orm

2.回收各式各样的对象到池中htm

 

先定义对象池和池子的容量:对象

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

容量是一个常量,最好取二的幂值,这样的话能够恰好占用全部内存位的资源,避免浪费。blog

这里池子用字典标识,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();