Unity创建Java脚本教程:生成GameObject

对象池是游戏开发中常用的优化方法。

解决问题:在某些类型的游戏,相同的对象会多次创建和销毁,这些对象的创建十分耗时,因而,我们会以一部分内存为代价,将这部分对象缓存起来,并不去销毁它,在需要创建时,从缓存中将先前创建好的对象取出来使用。

在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应用中,也许你还会碰到各种池,比如常见的线程池,总体的思路都是如此,具体实现会略有不同,你还可以创建一个统一的接口,添加各有特色的池,让你的池系统更加完善

   

免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空