前言:

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

对象池就是一组可回收的节点对象,我们通过创建cc.NodePool的实例来初始化一种节点的对象池。通常当我们有多个 prefab 需要实例化时,应该为每个 prefab 创建一个 cc.NodePool 实例。

当我们需要创建节点时,向对象池申请一个节点,如果对象池里有空闲的可用节点,就会把节点返回给用户,用户通过node.addChild将这个新节点加入到场景节点树中。
当我们需要销毁节点时,调用对象池实例的put(node) 方法,传入需要销毁的节点实例,对象池会自动完成把节点从场景节点树中移除的操作,然后返回给对象池。

这样就实现了少数节点的循环利用。

具体操作

第一步:准备好 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 接口判断对象池中是否有空闲的对象
        // get()获取对象
        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
}

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

第五步:使用组件来处理回收和复用的事件

使用构造函数创建对象池时,可以指定一个组件类型或名称,作为挂载在节点上用于处理节点回收和复用事件的组件。

在创建对象池时可以用:

 let menuItemPool = new cc.NodePool('MenuItem');  // 指定一个组件类型

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

cc.Class({
    extends: cc.Component,
    onLoad: function () {
        this.node.selected = false;
        this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);
    },
    // put() 收回对象池时会调用
    unuse: function () {
        this.node.off(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);
    },
    // get()获取对象池内对象时会调用
    reuse: function () {
        this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);
    }
});

另外 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 方法来清空缓存节点。

以上就是如何使用CocosCreator对象池的详细内容,更多关于CocosCreator对象池的资料请关注程序员的世界其它相关文章!

如何使用CocosCreator对象池的更多相关文章

  1. 整理CocosCreator常用知识点

    一、场景加载cc.director.loadScene(‘场景名称');//场景跳转cc.director.preloadScene(‘场景名称');//预加载场景cc.director.getScene();//获取当前场景二、查找节点1,节点查找node = cc.find(“Canvas/bg......

  2. 详解JavaScript中哪一种循环最快呢

    了解哪一种 for 循环或迭代器适合我们的需求,防止我们犯下一些影响应用性能的低级错误。JavaScript 是 Web 开发领域的“常青树”。无论是 JavaScript 框架(如 Node.js、React、Angular、Vue 等),还是原生 JavaScript,都拥有非常庞大的粉丝基础。......

  3. 基于JS简单实现手持弹幕功能+文字抖动特效代码

    前段时间抖音上有个抖动弹幕挺火的,于是决定仿写一个,话不多说,先看效果…效果展示GIF图看着有点模糊,但实际效果还是不错的。第一眼看上去也不知道该咋做,那就先把要实现的功能拆解一下吧。生成一个铺满全屏的黑色背景,写上文字,然后内容居中实现无缝滚动实现文字抖动特效旋转90度(默认横屏展示)代码如下.h......

  4. 深入理解Js中的this

    深入理解Js中的thisJavaScript作用域为静态作用域static scope,但是在Js中的this却是一个例外,this的指向问题就类似于动态作用域,其并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能......

  5. 在JavaScript中查找字符串中最长单词的三种方法(推荐)

    本文基于Free Code Camp基本算法脚本“查找字符串中最长的单词”。在此算法中,我们要查看每个单词并计算每个单词中有多少个字母。然后,比较计数以确定哪个单词的字符最多,并返回最长单词的长度。在本文中,我将解释三种方法。首先使用FOR循环,其次使用sort()方法,第三次使用reduce()方......

  6. react实现Radio组件的示例代码

    本文旨在用最清楚的结构去实现一些组件的基本功能。希望和大家一起学习,共同进步效果展示:测试组件:class Test extends Component {constructor(props) {super(props)this.state = {active:1}}onGroupChange(va......

  7. JS数组处理汇总

    join()方法:将数组中所有元素通过指定分隔符连接成一个字符串举例:myArr.join('-') // 用'-'符号拼接concat()方法:将两个数组或多个数组合并成一个数组举例:myArr.concat(arr1, arr2, ..., arrN)注意:该方法不会改变现有的数组,所以可以和空......

  8. JavaScript实现颜色查看器

    本文实例为大家分享了JavaScript实现颜色查看器的具体代码,供大家参考,具体内容如下实现效果方框中初始为白色输入框中输入颜色代码,点击查看颜色,在上方即可出现对应颜色点击复原,复原到初始的白色,同时清空输入框的内容实现代码<!DOCTYPE html><html lang=&......

  9. CocosCreator ScrollView优化系列之分帧加载

    一、 前言JS是单线程的,也就意味着所有任务需要排队,只有当前一个任务结束了,后一个任务才会执行。如果前一个任务耗时很长,后一个任务就不得不一直等着。Cocos Creator 是采用 Java Script/Type Script语言开发,本质上是JS,同样会拥有以上特征。特别地,如果使用不当,极......

  10. ES6在工作中会用到的核心知识点讲解

    一、var, let, const谈到ES6,估计大家首先肯定会想到var,let,const咱就先谈谈这三者的区别var a = 3;{var a = 4;}console.log(a);//4let b = 5;{let b = 6;}console.log(b);//5const c = 7;......

随机推荐

  1. 全面了解Vue3的 reactive 和相关函数

    Vue3的 reactive 怎么用,原理是什么,官网上和reactive相关的那些函数又都是做什么用处的?这里会一一解答。ES6的ProxyProxy 是 ES6 提供的一个可以拦截对象基础操作的代理。因为 reactive 采用 Proxy 代理的方式,实现引用类型的响应性,所以我们先看看 Pr......

  2. go语言实现简易比特币系统之交易签名及校验功能

    介绍签名的输入:待签名的交易数据,包括输入和输出引用的UTXO信息私钥签名的输出:数字数字签名公钥签名的目的证明交易所引用的UTXO的确属于付款人证明交易的所有数据的确是付款人提供的,且未被修改过签名中需要的数据UTXO中的PubKeyHash,这描述了付款人新生成UTXO中的PubKeyHash,......

  3. php中数组最简单的使用方法

    我们在说映射的时候,有些小伙伴就会联想到数组,没错,因为数组就是用了映射的思想。这里很多刚学php的小伙伴对概念不是很了解,不过小编把数组的定义、语法整理出来,大家跟着进行实例学习就可以了,最后再看看使用数组的一些注意事项。下面我们进入今天的学习吧。1.定义数组是PHP中的重要数组类型之一,是复合类......

  4. mybatis-plus动态表名的实现示例

    背景在分表的背景下,有时候查询数据的时候需要跨表查询,那此时就需要MP在解析的时候,能够很好的自适应表格名称实现MP中是通过PaginationInterceptor(分页插件)完成动态表名解析的,配置如下:数据库中表依赖<dependency><groupId>com.ba......

  5. python之Django自动化资产扫描的实现

    1. Django项目搭建- django项目创建cmdb- 子应用的创建scanhost>python manage.py startapp scanhost- 设置本地化(语言,时区, 安装的应用)setting# LANGUAGE_CODE = 'en-us'LANGUAGE_CODE ......

  6. 基于R语言中的各种更新方法分享

    宏包的更新卸载packageremove.packages("mypackage")查看的versionR.version或者getRversion()查看package的versionpackageVersion("mypackage")更新包update.......

  7. 【应急响应】Windows应急响应入门手册

    0x01 应急响应概述首先我们来了解一下两个概念:应急响应和安全建设,这两者的区别就是应急响应是被动响应、安全建设是主动防御。所谓有因才有果,既然是被动的,那么我们在应急响应的时候就得先了解本次安全事件的起因,常见的有:安全设备告警、数据被勒索加密、数据泄露在网上贩卖、网页被篡改、服务器CPU爆满卡......

  8. Java注解与反射机制

    前言Java注解和反射是很基础的Java知识了,为何还要讲它呢?因为我在面试应聘者的过程中,发现不少面试者很少使用过注解和反射,甚至有人只能说出@Override这一个注解。我建议大家还是尽量能在开发中使用注解和反射,有时候使用它们能让你事半功倍,简化代码提高编码的效率。很多优秀的框架都基本使用了注......

  9. Unity 静态变量跨场景操作

    创建两个场景同时赋值StaticVarious 脚本 然后按键好,H ,J 进行不断切换场景,会发现unity 控制台输出数字不断增加,然后把静态去掉,这样结果都是10。using UnityEngine;using System.Collections;using UnityEngine.Scen......

  10. SQLServer连接字符串

    1、连接字符串中常用的声明有:服务器声明 Data Source、Server和Addr等,数据库声明 Initial Catalog和DataBase等,集成Windows账号的安全性声明 Integrated Security和Trusted_Connection等,使用数据库账号的安全性声明 ......