2.16 使用Pool Boss创建缓存池
创建游戏实例每次都要重新分配内存,当销毁的时候需要进行垃圾回收。有时游戏中每帧会有很多零碎的内存申请,当积累较多进行垃圾回收可能会有丢帧,Unity官方建议的一种优化方案是每间隔一定帧数手动清理一次,不过这并不一定适用于所有游戏。
if(Time.frameCount % 30==0)// 每30帧强制清理一次 { System.GC.Collect(); }
对于比较大的内存占用,通常可以在切换关卡或暂停游戏时手动进行一次垃圾回收。
System.GC.Collect();
在Unity中创建一个游戏体实例是使用Instantiate函数,因此,应当尽可能地避免频繁使用这个函数。在这个太空射击游戏实例中,我们使用这个函数来创建子弹,因为子弹的创建频率很高而且经常用到,一个比较好的优化办法是在游戏开始之前一次创建多个子弹,先将它们隐藏起来,当需要时再放到需要的位置显示出来。
Unity的Asset Store中提供了很多缓存的插件,比如Pool Manager或Pool Boss,这些插件都非常容易使用,且已经具备了非常完善的功能。下面将通过一个简单的例子,将太空射击游戏的子弹使用Pool Boss缓存起来,可以避免运行时间的内存开销。
步骤 01 导入插件Core GameKit到当前工程中,如图2-61所示。这个插件包括了Pool Boss(单独下载Pool Boss也可以)。
图2-61 运行发布的游戏
步骤 02 找到LevelWaveSettings.prefab,注意不要直接拖动LevelWaveSettings.prefab创建实例,选择【Create LevelWaveSettings Prefab】,如图2-62所示,在场景中创建一个LevelWaveSettings实例。
图2-62 LevelWaveSettings.prefab
步骤 03 在Hierarchy窗口中选择LevelWaveSettings,选择【Configure Pooling】进入Pool Boss设置界面,这一步操作实际上就是选择了PoolBoss实例,如图2-63所示。
图2-63 LevelWaveSettings.prefab
步骤 04 拖动主角子弹Rocket.prefab到Drag Prefabs的黄色框内,设置Preload Qty决定缓存数量,选中Allow Instantiate More允许生成更多缓存实例,如图2-64所示。
图2-64 添加Prefab
步骤 05 修改脚本Player.cs,使用Pool Boss提供的Spawn函数替换掉Instantiate函数,这样我们将从缓存中创建实例,避免了运行时的内存开销。
//Instantiate(m_rocket, m_transform.position, m_transform.rotation); // 此处代码删除 DarkTonic.CoreGameKit.PoolBoss.Spawn("Rocket", m_transform.position, m_transform.rotation, null);
步骤 06 修改脚本Rocket.cs,去掉所有的Destroy函数,对于缓存实例,不能使用Destroy删除,而是要使用Despawn将实例交还给缓存池。这里再创建一个回调函数OnBecameInvisible,当子弹模型飞到屏幕外时进行回收。
void OnBecameInvisible() // 当模型停止渲染 { DarkTonic.CoreGameKit.PoolBoss.Despawn(this.transform); }
除了主角的子弹,敌人的子弹也需要重复这些步骤进行修改,这里不再赘述。最后运行游戏,所有的子弹将从缓存池中调用,避免了创建和删除操作产生的运行时内存开销。在运行游戏的时候,可以通过Pool Boss实时查看创建了多少实例,回收了多少,如图2-65所示。
图2-65 查看实例创建和回收状态
除了可以在Unity编辑器中手动将Prefab进行缓存,也可以通过代码实现。对于使用配置文件读取资源的游戏来说,使用代码动态创建缓存池会更加方便。在下面的代码中,首先从Resources路径下动态读取子弹的Prefab,注意,动态读取必须将资源放到名为Resources的目录中,因为是IO操作,对性能有一定影响,不适合在游戏运行中频繁调用,建议在关卡初始化时执行,然后使用PoolBoss.CreateNewPoolItem函数动态创建缓存池。
var prefab=Resources.Load<GameObject>("Rocket"); // 从Resources目录读入Prefab // 创建20个缓存,最高限制30个,放到一个名为Game的组中 PoolBoss.CreateNewPoolItem(prefab.transform, 20, true, 30, false, "Game");
本节最后的示例文件保存在资源目录c02_ShootingGame_PoolBoss中,其中不包括Core GameKit插件,读者需要自行安装才能运行。