0%

Unity-对象池

前言

对象池是在游戏开发中十分重要的设计模式。解决游戏中大量重复物体的创建而使得性能低的问题。
本章将会简单介绍对象池的设计思想,以及实现案例。

对象池的提出

在 FPS 射击游戏中,虽然一些游戏会根据准星与目标对象在屏幕坐标中的相对位置来实现子弹击中效果,但在主流游戏设计中,一颗子弹就是一个对象物体。又或者如一些弹幕游戏,比如《雷霆战机》很明显子弹就是一个实体对象。
大量的对象创建不但消耗空间资源,也会降低游戏运行的性能。而为了能解决这些大量创建同时会降低性能的对象问题,“池”的概念就被提出了。

对象池的思想

我们使用使用对象池,来将会大量产生的对象存储在池中
当需要创建一个对象时,我们可以从对象池中找是否有空闲对象

  • 有空闲对象时,只需要简单的进行初始化,就能取出来用。
  • 没有对象时,就会自行创建一个新的对象
  • 对象生命周期结束后,再将其存储到池中

除了对象池,其实还有数据库池等,都是同样的设计思想。

Unity3d 对象池的设计思想

设计要素

为了实现对象池,需要几个要素来设计:

  • 单例模式
  • 字典 Dictionary
  • 队列/集合框架

单例模式是使得对象池是一个唯一的对象,不会被重复创建,来保证对象池的结构不被破坏。
字典(Dictionary)以键值方式存储数据,方便我们快速获取到我们需要的对象池。
队列/集合框架主要是为了作为池动态的存取数据对象。队列的特点是先入先出,在存取过程中性能极佳,但不利于定位特定对象元素。集合框架动态的存取对象,且拥有极其丰富的功能,但是在同时存取中可能会产生冲突。

设计结构内容

设计对象池主要有以下结构:

  • 单例的对象池
  • 单例对象池的获取方法
  • 取对象方法
  • 回收对象方法

实现案例

  1. 首先创建一个 Pool 类,作为我们的对象池类。私有的静态 Dictionary 类型的 pool 就是我们的对象池单例。键-值,值我们使用队列(Queue)来充当对象池来存储对象。将构造函数私有化能保证 Pool 不会被实例化。最后定义 GetInstance() 方法来获取单例对象。这就一个单例模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Pool{
//使用私有化,能确保pool不被修改
private static Dictionary<string,Queue<GameObject>> pool = new Dictionary<string, Queue<GameObject>>();

// 不被实例化
private Pool()
{
}

// 对象池的获取
public static Dictionary<string,Queue<GameObject>> GetInstance()
{
return pool;
}
}
  1. 接下来设计对象池的取对象方法。但首先需要定义一个常量 maxCount,这表示我们对象池的最大存储数量。
1
2
// 最大存储数量字迹根据自身需求来设定
private const int maxCount = 100;
  1. 定义取对象,首先要考虑参数有 预制体名(表示一类对象池的键)、对象坐标、对象转向。当然后面的两个参数主要用于定位对象的初始位置状态,也可以传入 transform。

内部的逻辑则是首先判断对象池是否为空且存在对应的键,如果对应池中有对象则出队返回出来,如果没有则根据预制体创建一个新对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static GameObject GetGameObject(string prefabName, Vector3 position,Quaternion quaternion)
{

GameObject obj;
string temp = prefabName + "(Clone)"; // 创建出来的对象是加(clone)

if (pool.Count >0 && pool.ContainsKey(temp) )//判断对象池单例是否为空,是否存在我们要创建的对象的对象池
{
if (pool[temp].Count > 0)
{
// 有则取出来,并且出队
obj = pool[temp].Dequeue();
obj.SetActive(true);
}
else
{
obj = (GameObject) Instantiate(Resources.Load(prefabName), position,rotation);
obj.transform.SetPositionAndRotation(position,rotation);
obj.SetActive(true);
}

}else
{
obj = (GameObject) Instantiate(Resources.Load(prefabName), position,rotation);
obj.transform.SetPositionAndRotation(position,rotation);
obj.SetActive(true);

}

return obj;
}

更加详细的需求可以自行设计。

  1. 最后是回收对象。回收对象的思想是,判断是否有此对象的对象池,有则直接入队;没有则创建一个队列键值,并将其存储其中。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public static void RecycleObj(GameObject gameObject){
    var par = Camera.main;
    gameObject.transform.SetParent(par.transform);
    gameObject.transform.SetPositionAndRotation(par.transform.position,par.transform.rotation);
    gameObject.SetActive(false);

    string prefname = gameObject.name;
    // 入队
    if (pool.ContainsKey(prefname))
    {
    if (pool[prefname].Count < maxCount)
    {
    pool[prefname].Enqueue(gameObject);
    }
    }
    else
    {
    Queue<GameObject> queue = new Queue<GameObject>();
    queue.Enqueue(gameObject);
    pool.Add(prefname,queue);
    }
    }

结语

对象池的实现方法有多种,并不一定是本章提供的设计方案,但是对象池的基本思想是一致的,根据自己的需求实现合适的对象池这才是最重要的。