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

对象池的概念

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

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

关于 cc.NodePool 的详细 API 说明,请参考 cc.NodePool API 文档。

流程介绍

下面是使用对象池的一般工作流程

准备好 Prefab

把你想要创建的节点事先设置好并做成 Prefab 资源,方法请查看 预制资源工作流程。

初始化对象池

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

//…
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 接口放入对象池
}
}
对象池里需要的初始节点数量可以根据游戏的需要来控制,即使我们对初始节点数量的预估不准确也不要紧,后面我们会进行处理。

从对象池请求对象

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

// …

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,在这一步进行判断也可以。

将对象返回对象池

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

// …

onEnemyKilled: function (enemy) {
// enemy 应该是一个 cc.Node
this.enemyPool.put(enemy); // 和初始化时的方法一样,将节点放进对象池,这个方法会同时调用节点的 removeFromParent
}
这样我们就完成了一个完整的循环,主角需要刷多少怪都不成问题了!将节点放入和从对象池取出的操作不会带来额外的内存管理开销,因此只要是可能,应该尽量去利用。

使用组件来处理回收和复用的事件

使用构造函数创建对象池时,可以指定一个组件类型或名称,作为挂载在节点上用于处理节点回收和复用事件的组件。假如我们有一组可点击的菜单项需要做成对象池,每个菜单项上有一个 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); // 通过之前传入的管理类实例回收子弹
}
清除对象池

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

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

使用 cc.NodePool 的优势

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

而之前的 cc.pool 接口是一个单例,无法正确处理节点回收和复用时的事件注册。不再推荐使用。

对象池的基本功能其实非常简单,就是使用数组来保存已经创建的节点实例列表。如果有其他更复杂的需求,你也可以参考 暗黑斩 Demo 中的 PoolMng 脚本 来实现自己的对象池。

cocoscreator如何使用对象池-对象池使用详解相关推荐

  1. java 重启线程_java 可重启线程及线程池类的设计(详解)

    了解JAVA多线程编程的人都知道,要产生一个线程有两种方法,一是类直接继承Thread类并实现其run()方法:二是类实现Runnable接口并实现其run()方法,然后新建一个以该类为构造方法参数的 ...

  2. 一文搞懂线程池原理——Executor框架详解

    文章目录 1 使用线程池的好处 2 Executor 框架 2.1 Executor 框架结构 2.2 Executor 框架使用示意图 2.3 Executor 框架成员 2.3.1 Executo ...

  3. Tomcat 8熵池阻塞变慢详解

    Tomcat 8熵池阻塞变慢详解 Tomcat 8启动很慢,且日志上无任何错误,在日志中查看到如下信息: Log4j:[2015-10-29 15:47:11] INFO ReadProperty:1 ...

  4. future java 原理_Java线程池FutureTask实现原理详解

    前言 线程池可以并发执行多个任务,有些时候,我们可能想要跟踪任务的执行结果,甚至在一定时间内,如果任务没有执行完成,我们可能还想要取消任务的执行,为了支持这一特性,ThreadPoolExecutor ...

  5. 关于数据库连接池满了的问题详解

    关于数据库连接池满了的问题详解 代码级问题 实例问题 问题根源 问题扩展 代码级问题 问题重现: 某某系统在生产环境使用一定时间后表现出用户不能登录,后台tomcat日志报如下错: 2008-08-1 ...

  6. Java线程池七个参数详解

    java多线程开发时,常常用到线程池技术,这篇文章是对创建java线程池时的七个参数的详细解释. 从源码中可以看出,线程池的构造函数有7个参数,分别是corePoolSize.maximumPoolS ...

  7. 【C++】C++对象模型:对象内存布局详解(C#实例)

    C++对象模型:对象内存布局详解 0.前言 C++对象的内存布局.虚表指针.虚基类指针解的探讨,参考. 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个概念可 ...

  8. php 打印对象详细信息,php打印显示数组与对象的函数详解

    php打印显示数组与对象的函数详解 发布于 2014-11-17 18:55:49 | 699 次阅读 | 评论: 0 | 来源: 网友投递 PHP开源脚本语言PHP(外文名: Hypertext P ...

  9. python 元类 type_Python 使用元类type创建类对象常见应用详解

    本文实例讲述了Python 使用元类type创建类对象.分享给大家供大家参考,具体如下: type("123") 可以查看变量的类型;同时 type("类名", ...

最新文章

  1. js的defer属性
  2. 面试官问我:spring、springboot、springcloud的区别,我笑了
  3. 【Paper-Attack Defense】Adversarial Label-Flipping Attack and Defense for Graph Neural Networks
  4. python基础题目大全,测试你的水平,巩固知识(含答案)
  5. php编码怎么变西欧了403,你知道一个简单的PHP脚本在ip检查后抛出403吗?
  6. Excel任务该如何在FineReader 12中设置
  7. 修改hosts文件,解决端口占用方法
  8. 零基础如何用平面设计排版软件PS进行布局构图
  9. java怎么用蓝牙传_[技巧]蓝牙传输JAVA简易教程(图文及小常识)
  10. 《JAVA中的集合框架》
  11. 关于应用服务器和数据库服务器的区别浅谈
  12. dango-orm单表操作知识点
  13. 第三章 变量和数据类型_C语言中的小数(float,double)
  14. CSDN积分不够了怎么办?快速获得积分看这里
  15. Dremel学习总结2
  16. 第三章:Solr4.7以DIH的方式从数据库导数据
  17. [数值计算-16]:最小二乘法的求解1 - 一元二次方程解析法求解
  18. [数据结构][Python][经典题目]明星问题
  19. 要想提高工作效率,请拒绝做这 7 种事
  20. ThinkPHP框架下载

热门文章

  1. 金蝶k3 cloud 7.x 学习 授权
  2. 英语口语练习系列-C19-喜欢某人
  3. pycharm运行python程序环境配置
  4. DvaJS中的Subscription
  5. python itertools_python - itertools 模块
  6. 到底什么样的人可以做外贸?
  7. Elasticsearch 中的“生产模式”和“开发模式”是什么
  8. java getmapping(_java之@Controller和@RestController以及@GetMapping和@PostMapping接收参数的格式使用...
  9. NOIP 2008 传球游戏
  10. uboot移植rtc