对象池是游戏开发中常用的优化方法。
解决问题:在某些类型的游戏,相同的对象会多次创建和销毁,这些对象的创建十分耗时,因而,我们会以一部分内存为代价,将这部分对象缓存起来,并不去销毁它,在需要创建时,从缓存中将先前创建好的对象取出来使用。
在Unity游戏开发中,创建GameObject是一个费时的过程,本文将针对GameObject类创建一个对象池。因为是一个非常常用的优化手段,因而,我们需要把其能够方便的移植到其他项目中,不妨放到一个单一的常用工具的文件夹中,然后可以很方便的导出为package,并在其他项目中导入引用。
登录后复制
public class PoolOfGameObjects
{
private List<GameObject> pool;//存放提前创建好的对象缓存区
private GameObject sample;//这个对象池缓存的对象样本
private int index;//未使用对象的位置坐标(比如10个对象,使用了3个,index就会变成3,即pool[3]是下一个可用得对象)
}
首先,我们需要去创建这个池,即将它的属性初始化:
登录后复制
public PoolOfGameObjects(GameObject sample)
{
this.sample = sample;//不管怎么说,你都先要给对象池一个对象做样本,不然池子里该放什么呢?
pool = new List<GameObject> ();//把池子初始化,不然你提前创建好的对象要放哪里呢?
index = -1;//现在池子里是空的,所以你读取pool[-1]只会得到错误。
}
接下来我们复制一些样本的副本到池子里:
登录后复制
private void Add(int count=1)
{
for (int i = 0; i < count; i++)
{
GameObject copy = GameObject.Instantiate (this.sample);
copy.SetActive (false);//还没使用的物体就让它安静躺着池子里,不然可能会被渲染出来。
pool.Add (copy);
}
}
这个方法是私有的,我们的池子很智能,不需要外部给我们加水,当池子的对象被耗尽,池子会自己往里添加更多能用的对象。
接下来,我们应该在什么时候往池里加对象呢?也许你的游戏有一个漂亮的加载界面,你也许想在这时候给池子里加上差不多够用的对象备用,也许你没有那么多内存去存放多余的对象,你甚至不清楚你会用到几个,或者你并没有足够的加载时间,只想吝啬的需要一个时添加一个(在编程时,希望你能做一个吝啬鬼)。
不管你是前者还是后者,既然想让更多的项目能够复用,我们得能同时满足两者的需要,为了前者,我们需要另一个构建函数:
登录后复制
public PoolOfGameObjects(GameObject sample,int capacity)
{
this.sample = sample;
pool = new List<GameObject> ();
Add(capacity);
index = 0;
}
在初始化池子的时候,我们就往里面创建了用户预计会使用到的对象副本个数。
现在,你有了一个池子,也许里面还有一些对象供你使用,现在来规定一下你能用这个池子来做什么,首先,这个池子里的对象是供我们拿出来使用的,并且如果你有良好的管理习惯,你应该认为,用完再放回原处是理所当然的,我们一定要吝啬,如果你随意丢弃,那么当你把池子里的对象用尽时,不得不再请求cpu再为你创建一个,每创建一个副本,池子的体积就会越来越大,你可能认为,我已经把GameObject清理掉了,但池子并不会知道你以及把它的管理对象清理了,它还是会为对象保留一个位置。所以我们需要两个基本方法,借用和归还。
首先是借用:
登录后复制
public GameObject Borrow()
{
if (index >= 0 && index < pool.Count)//index属性保存着可用对象的下标,我希望能连续的取对象,而不是每次都去循环一遍List,假如100个中用掉99个,得需要循环多少次哦
{
pool[index].SetActive(true);
return pool[index++];//借出一个后,浮标移动到仅靠着得下一位
}
else
{
Add();//不够得时候,得为池子增加一个对象
if (index < 0)
{
index = 0;//这里用index++也可以
}
return Borrow(); //刚刚没有借到,现在增加了一个对象,总可以借给我了
}
}
有时候你需要一次性借多个,你总不希望要来好多趟吧:
登录后复制
public GameObject[] Borrow(int count)
{
GameObject[] order = new GameObject[count];
for (int i = 0; i < count; i++)//不要介意这里使用的循环,假如你把这个循环放到里层,创建GameObject的时间复杂度是一样的,但会多调用几次上面的方法,这里我不想为这一点性能,多写一部分代码。
{
order[i] = Borrow();
}
return order;
}
使用完之后,我们还要好好的把副本还回去:
登录后复制
public bool Lend(GameObject gameobject)
{
for (int i = 0; i < index; i++)//只需要在已经借出的列表前部分进行比对
{
if (pool[i].Equals(gameobject))//的确是这个池子里的借出去的对象
{
pool[i].SetActive(false);
pool.Insert(pool.Count, pool[i]);//将对象插入到最后面待之后继续使用
pool.Remove(pool[i]);//将原来的空出来的位置去掉
index--;//浮标向前一位
return true;//归还成功
}
}
return false;//归还不成功
}
同样的,归还你也不想重复好几次吧:
登录后复制
public GameObject[] Lend(GameObject[] gameobects)
{
List<GameObject> notMatch = new List<GameObject>();
for (int i = 0; i < gameobects.Length; i++)
{
if (!Lend(gameobects[i]))
{
notMatch.Add(gameobects[i]);
}
}
return notMatch.ToArray();
}
归还多个的变数多一些,我们将不匹配的对象拒退给用户。
最后,我们还需要有清理对象池的方法,有时候我们没有把握好初始化池子的大小,或者一开始我们用了很多副本,但是之后我们需要的很少,将未使用的副本清理出去:
登录后复制
public void Clear()
{
if (index >= 0)
{
for (int i = pool.Count-1; i >=index; i--)
{
if (!pool[i].activeSelf)//以防我们删掉正在使用的对象,这本是没有必要的。经过测试,有没有这个判断不会造成误删,但是多一层保险,有时候未必是坏事
{
GameObject.Destroy(pool[i]);
}
}
pool.RemoveRange(index, pool.Count - index);//把池子的容量恢复到刚好的尺寸
}
}
当你不想要这个池子的时候,需要销毁它来释放更多的内存:
public void Destory(bool force)//false时,仅仅销毁池子和未使用的对象,已经使用的对象不会被销毁,但也无法再归还回来;true时,已经使用的对象也会被强制销毁掉。
登录后复制
{
int start;
if (force)
{
start = 0;
}
else
{
start = index;
}
for (int i = pool.Count - 1; i >= start; i--)
{
if ((force) || (!pool[i].activeSelf))
{
GameObject.Destroy(pool[i]);
}
}
pool.Clear();
}
以上,就是一个GameObject对象池的基本实现,放心大胆的部署在你的各个Unity应用中,也许你还会碰到各种池,比如常见的线程池,总体的思路都是如此,具体实现会略有不同,你还可以创建一个统一的接口,添加各有特色的池,让你的池系统更加完善
免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删