一、对象池概念
对象池模式并不是游戏开发独有的设计模式,它的设计思路与其他开发中的数据库连接池、线程池的思路等是一样的。
其核心思想是,使用完不直接删除,而是将其放回池子里,需要用的时候再取出来。 对象池模式的出现主要优化两点:
1、防止对象被频繁的创建和删除,从而内存抖动、频繁GC(垃圾回收)
2、对象初始化成本较高
但是因为传统软件开发的对象通常都是轻中里量级的, 分配/释放对象的开销可以忽略不计,所以所以在传统的软件开发中朴素的对象池应用还是比较少的。一般都是特定对象为了②上优化,例如数据库连接池、线程池,这样就很好解决了重用,同时还能解决连接数问题等。 但是在游戏开发的过程中,由于很多游戏的游戏对象创建和删除也很频繁,同时游戏对象包含了非常多的对象,所以即使是朴素的对象池技术也有了比较多的应用场景。
二、对象池操作
下面用文字的方式简介对象池的基本操作
借用: 通俗点讲就是从池中获取物体,如果是第一次获取物体要初始化池。 如果池中没有想要的物体了,则创建一个该对象
归还: 通俗点讲就是物品用完了原本是要删除的,但是应用了对象池之后则是把物体归还到池内,前提是池中数量是不大于预设的最大数量的(防止太多内存炸了),如果池中数量已经大于了预设的最大数量,则直接删除
预热: 就是预加载一定数量的对象,我个人认为这是对象池中比较精髓的部分之一。如果不做预热的,那么第一次创建对象的时候还是直接涉及初始化问题。一个很容易懂道理是玩家宁愿在加载界面多等1秒,也不会愿意在游戏中卡顿0.1秒,特别是竞技类的游戏,玩家会想砸电脑的(笑)。所以我觉得如果不做预热的对象池优化只做了一半。
缩小: 差不多就像是预热反着来,上面在归还的时候说如果大于了设定的数量阈值就不返回池中而是直接删除,但实际上删除也有可能会带来时间成本,所以我们可以先不删除,在每次游戏中途的过关之类的加载界面的时候再删除缩小内存池。如果怕在加载界面之前内存爆了的话可以多设置一个必须删除的阈值,其作用跟上面归还时写的一样。(该功能我在我的DEMO当中没有做)
重置: 每个新拿出来的物体应该和新创建的一样时“崭新”的,不能明显带有上次使用过的状态,因此再每次物体出池的时候要对可能存在后效性的地方重置。在unity中则是在物体的OnEnable()中写物体手动初始化的内容,包括清空刚体的力等等,OnEnable()和Start()的区别就是Start()只在物体第一次启用的第一帧运行,OnEnable会在每次物体重新启用的时候运行。
三、具体实验
下面是我自己做的一个小DEMO小实验 因为只是一个小demo实验,所以写的很不健壮我也在注释中表达了,所以如果是关于完整性、健全性的问题就不要过多吐槽了。但是如果我的代码里犯了原则性问题导致还能有大优化的地方请大家指出学习。
下面是ObjectPool的脚本,函数的设计是为了让他们看起来更像unity原生的Instantiate和Destroy。
using
然后是测试脚本GameManager
using
一个Canvas上弄了两个按钮,然后弄了个空的GameObject用来当GameManager,绑定了上了ObjectPool(对象池的脚本)和GameManager(测试脚本),让两个按钮的OnClick事件分别监听TestOfNotOP()和TestOfOP()
最后用资源商店里下载的一个粒子特效来测试
(详细测试视频看我B站)
可以看到,如果不使用对象池的话播放500帧(每帧生成一个粒子特效)大概是在9.8-9.9秒左右,而应用了对象池之后播放500帧大概是在9.4-9.5秒左右。还是有优化的结果的,如果是旧版本的unity可能优化的会更多,这点很久以前雨松大佬也吐槽过 http://www.xuanyusong.com/archives/2925 现在的unity粒子特效初始化应该是优化过了的。 总的来说就是,所有的优化技术,优化成本和结果一定要衡量。