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

2020-03-19 15:52:27 浏览数 (1)

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

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

最常见的应用就是子弹的创建和销毁。

一般对象池都是一个全局性的通用脚本,可以采用单例模式来设计。

https://cloud.tencent.com/developer/article/1601263

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

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

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

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

代码语言:javascript复制
1     private const int maxCount = 128;
2     private Dictionary<string, List<GameObject>> pool = new Dictionary<string, List<GameObject>>();

容量是一个常量,最好取二的幂值,这样的话可以刚好占用所有内存位的资源,避免浪费。

这里池子用字典标识,key为对象的名字,这样比较好记,你用InstanceID也没问题。

每个同样的对象一般在池子中可以有很多,用一个List来存。

下面先定义回收对象的方法:

代码语言:javascript复制
 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值(名字:比如说螃蟹),顺便添加第一只螃蟹。

经常会遇到要批量回收进池子的情况:

代码语言:javascript复制
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     }

对象可以回收了,那怎么取呢,自然也是能从池子里取就从池子里取,实在不行才去实例化:

代码语言:javascript复制
 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方法一样,加一个可以设置父对象的重载方法:

代码语言:javascript复制
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     }

下面是完整脚本:

代码语言:javascript复制
 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进行归位:

代码语言:javascript复制
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类的一个扩展方法,例如:

代码语言:javascript复制
1  var ins = ObjectPool.Instance.GetObj(bulletPrefab, parent.transform);
2  ins.transform.ResetLocal();

0 人点赞