​对于游戏开发人员来说,性能优化是一个永远绕不过的话题,极致的性能是我们毕生的追求,今天就来带大家学习一下性能优化方法之一——「对象池」。

为什么要使用对象池?
在开始之前要先弄明白为什么要使用对象池?在运行时进行节点的创建(cc.instantiate)和销毁(node.destroy)操作是非常耗费性能的,因此我们在比较复杂的场景中,通常只有在场景初始化逻辑(onLoad)中才会进行节点的创建,在切换场景时才会进行节点的销毁。如果制作有大量敌人或子弹需要反复生成和被消灭的动作类游戏,我们要如何在游戏进行过程中随时创建和销毁节点并且不会大幅度的消耗性能呢?这里就需要对象池的帮助了。

通过下面这组数据可以看出使用对象池对游戏的性能是有质的提升的:

对象池的概念(这里我们直接引用官方文档)
对象池就是一组可回收的节点对象,我们通过创建 cc.NodePool 的实例来初始化一种节点的对象池。通常当我们有多个 prefab 需要实例化时,应该为每个 prefab 创建一个 cc.NodePool 实例。当我们需要创建节点时,向对象池申请一个节点,如果对象池里有空闲的可用节点,就会把节点返回给用户,用户通过 node.addChild 将这个新节点加入到场景节点树中。

当我们需要销毁节点时,调用对象池实例的 put(node) 方法,传入需要销毁的节点实例,对象池会自动完成把节点从场景节点树中移除的操作,然后返回给对象池。这样就实现了少数节点的循环利用。假如玩家在一关中要杀死 100 个敌人,但同时出现的敌人不超过 5 个,那我们就只需要生成 5 个节点大小的对象池,然后循环使用就可以了。

使用对象池的一般工作流程
下面正式开始介绍对象池的使用流程。

1.准备 Prefab
如果你对如何创建 Prefab 和动态添加子节点流程还不熟悉的话,可以参考我之前写的「一文带你彻底明白如何实现动态添加子节点及修改子节点属性」,这里不再具体展开说明。

2.初始化对象池
在场景加载的初始化脚本中,我们可以将需要数量的节点创建出来,并放进对象池:

//...
properties: {enemyPrefab: cc.Prefab
},
onLoad: function () {this.enemyPool = new cc.NodePool();let initCount = 5;for (let i = 0; i < initCount; ++i) {let enemy = cc.instantiate(this.enemyPrefab); // 创建节点this.enemyPool.put(enemy); // 通过 put 接口放入对象池}
}

对象池里需要的初始节点数量可以根据游戏的需要来控制,即使我们对初始节点数量的预估不准确也不要紧,后面我们会进行处理。

3.从对象池请求对象
接下来在我们的运行时代码中就可以用下面的方式来获得对象池中储存的对象了:

// ...
createEnemy: function (parentNode) {let enemy = null;if (this.enemyPool.size() > 0) { // 通过 size 接口判断对象池中是否有空闲的对象enemy = this.enemyPool.get();} else { // 如果没有空闲对象,也就是对象池中备用对象不够时,我们就用 cc.instantiate 重新创建enemy = cc.instantiate(this.enemyPrefab);}enemy.parent = parentNode; // 将生成的敌人加入节点树enemy.getComponent('Enemy').init(); //接下来就可以调用 enemy 身上的脚本进行初始化
}

安全使用对象池的要点就是在 get 获取对象之前,永远都要先用 size 来判断是否有可用的对象,如果没有就使用正常创建节点的方法,虽然会消耗一些运行时性能,但总比游戏崩溃要好!另一个选择是直接调用 get,如果对象池里没有可用的节点,会返回 null,在这一步进行判断也可以。

4.将对象返回对象池
当我们杀死敌人时,需要将敌人节点退还给对象池,以备之后继续循环利用,我们用这样的方法:

// ...
onEnemyKilled: function (enemy) {// enemy 应该是一个 cc.Nodethis.enemyPool.put(enemy); // 和初始化时的方法一样,将节点放进对象池,这个方法会同时调用节点的 removeFromParent
}

这样我们就完成了一个完整的循环,主角需要刷多少怪都不成问题了!将节点放入和从对象池取出的操作不会带来额外的内存管理开销,因此只要是可能,应该尽量去利用。

5.使用组件来处理回收和复用的事件
使用构造函数创建对象池时,可以指定一个组件类型或名称,作为挂载在节点上用于处理节点回收和复用事件的组件。假如我们有一组可点击的菜单项需要做成对象池,每个菜单项上有一个 MenuItem.js 组件:

// MenuItem.js
cc.Class({extends: cc.Component,
​onLoad: function () {this.node.selected = false;this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);},
​unuse: function () {this.node.off(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);},
​reuse: function () {this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);}
});

在创建对象池时可以用:

let menuItemPool = new cc.NodePool('MenuItem');

这样当使用 menuItemPool.get() 获取节点后,就会调用 MenuItem 里的 reuse 方法,完成点击事件的注册。当使用 menuItemPool.put(menuItemNode) 回收节点后,会调用 MenuItem 里的 unuse 方法,完成点击事件的反注册。

另外 cc.NodePool.get() 可以传入任意数量类型的参数,这些参数会被原样传递给 reuse 方法:

// BulletManager.js
let myBulletPool = new cc.NodePool('Bullet'); //创建子弹对象池
// ...
let newBullet = myBulletPool.get(this); // 传入 manager 的实例,用于之后在子弹脚本中回收子弹
​
// Bullet.js
reuse (bulletManager) {this.bulletManager = bulletManager; // get 中传入的管理类实例
}
​
hit () {// ...this.bulletManager.put(this.node); // 通过之前传入的管理类实例回收子弹
}

6.清除对象池
如果对象池中的节点不再被需要,我们可以手动清空对象池,销毁其中缓存的所有节点:

myPool.clear(); // 调用这个方法就可以清空对象池

当对象池实例不再被任何地方引用时,引擎的垃圾回收系统会自动对对象池中的节点进行销毁和回收。但这个过程的时间点不可控,另外如果其中的节点有被其他地方所引用,也可能会导致内存泄露,所以最好在切换场景或其他不再需要对象池的时候手动调用 clear 方法来清空缓存节点。

7.使用 cc.NodePool 的优势
cc.NodePool 除了可以创建多个对象池实例,同一个 prefab 也可以创建多个对象池,每个对象池中用不同参数进行初始化,大大增强了灵活性;此外 cc.NodePool 针对节点事件注册系统进行了优化,用户可以根据自己的需要自由的在节点回收和复用的生命周期里进行事件的注册和反注册。

心得:
在使用对象池的时候,有时对象的回收和对象池的创建会不在一个 js 文件里面,这时对「节点树」的理解和「对其他节点组件的访问」就会显得尤为重要,虽然这些东西在学的时候可能就是一个概念、一两行代码,但在整个游戏开发的过程中都是在这些基础之上进行的,切不可操之过急!共勉!

最后:
本来是想把这几天使用对象池的心得写一下的,但是发现官方文档对于初学者来说还是非常详细的,于是几乎原封不动的搬了过来,与其说分享到不如说是一个记录。


我是「Super于」,立志做一个每天都有正反馈的人!

Cocos Creator 性能优化——对象池相关推荐

  1. Cocos Creator 性能优化:DrawCall

    Cocos Creator 性能优化:DrawCall(全面!) title: Cocos Creator 性能优化:DrawCall 前言 在游戏开发中,DrawCall 作为一个非常重要的性能指标 ...

  2. Cocos Creator性能优化---DrawCall

    前言 在游戏开发中,DrawCall 作为一个非常重要的性能指标,直接影响游戏的整体性能表现. 无论是 Cocos Creator.Unity.Unreal 还是其他游戏引擎,只要说到游戏性能优化,D ...

  3. Cocos Creator性能优化-2-包体优化

    对于Cocos Creator包体优化可分为 1.项目设置 1.模块设置 通过去除无需使用的模块来减少包体 (微信小游戏还支持引擎插件可在打包时勾选) 2.resources 总而言之不需要动态加载的 ...

  4. Cocos Creator 性能调优优化集锦

    01 为什么要做性能优化? 性能:是一种优秀的能力.唤醒快.运行持久.稳定. 这种能力在游戏上能让你的用户感觉很爽,表征表现为加载快.手机不发热.运行流畅.不卡顿. 所以,性能优化的终极目标是让你的用 ...

  5. Cocos Creator 性能调优:如何减少 2D/3D DrawCall?

    Cocos 中文社区第4期有奖征稿活动火热进行中,iWatch SE.坚果投影仪等丰厚奖品等你来拿,点击文末[阅读原文]进入社区专贴,把你的聪明才智向我们砸来吧! 点击查看活动详情 本文即为此次社区征 ...

  6. Android 内存优化-对象池★

    1.对象池 内存优化不仅要防止内存泄露,也要注意频繁GC卡顿.内存抖动以及不必要的内存开销造成的内存需求过大或者内存泄露. 比如,如果有大量临时对象的创建该如何处理呢? 首先要确定问题发生的原因,对象 ...

  7. 数据库性能优化—数据库连接池

    文章出自:阿里巴巴十亿级并发系统设计(2021版) 链接:https://pan.baidu.com/s/1lbqQhDWjdZe1CBU-6U4jhA 提取码:8888 目录 接下来,让我们正式进入 ...

  8. Cocos Creator | 游戏优化之内存优化-资源管理

    更多教程请关注微信公众号: 设备对每个程序都有最大的内存分配限制,如果超过了这个阈值,会被系统强制关闭,造成 crash 因此在开发的过程中,我们要在保证程序运行效率的前提下,尽量压缩程序运行时所占用 ...

  9. Cocos Creator实例-制作抽奖池

    出现空白,请点击下方[] 转载地址:https://blog.csdn.net/u011607490/article/details/82701325 [预览效果](https://saber2pr. ...

最新文章

  1. RabbitMQ是什么
  2. 旋转矩阵、欧拉角、四元数比较
  3. python中国大学排名爬虫写明详细步骤-Python爬虫——定向爬取“中国大学排名网”...
  4. wmsys.WM_CONCAT
  5. Jetpack:使用 ActivityResult 处理 Activity 之间的数据通信
  6. 少数民族青年作家要有更高的标准和目标
  7. 【实战 Ids4】║ 认证中心之内部加权
  8. python玩转android_如何用python玩跳一跳 ?(安卓版)
  9. LeetCode-计数质数
  10. 强连通图------(1)通过两次DFS或BFS判断是不是强连通图
  11. python怎么加锁_Python开发【笔记】:加锁的最佳方案
  12. Android中Bitmap,byte[],Drawable相互转化
  13. 错过了粽子不要紧,只是不要错过Ta!
  14. delphi 剪切板变量_Delphi操作剪贴板
  15. 电子档案管理系统java,电子档案管理系统单点登陆示例
  16. dell台式机进入安全模式_戴尔电脑如何进入安全模式?
  17. ubuntu Rhythmbox 乱码解决
  18. 大数据背后的神秘公式(上):贝叶斯公式
  19. delphi网络时间校对
  20. odb 使用指南(三)持久化对象的处理

热门文章

  1. python模块大全doc_Python pydoc模块详解:查看、生成帮助文档
  2. 机器人机器学习环境框架搭建—从ubuntu到mujoco仿真实现(第一篇):ubuntu18.04的安装
  3. MongoDB 6.0 (五)索引操作
  4. 【微信小程序】数组排序以及去重问题详解
  5. 基于JavaWeb的疫情期间社区出入管理系统设计与实现
  6. [深度学习论文笔记]Brain tumour segmentation using a triplanar ensemble of U-Nets 基于Unet三平面集成的脑肿瘤分割
  7. 商场促销 --- 策略模式
  8. 震精!房妹、其父、其母各有两个身份证,倒卖308套房
  9. Cook-Torrance光照模型附 shader代码
  10. RecycleView 系列(3)--利用 ItemDecoration 实现时光轴(物流时间)样式