当前位置: 首页 > news >正文

Cocos3.x 对象池NodePool使用介绍和注意事项

文章目录

  • 前言
  • 一、适用场景
  • 二、基本原理
  • 三、创建和方法
  • 四、使用方案
    • 4.1 方案一:预先创建
      • 流程图
      • 代码
    • 4.2 方案二:动态创建
      • 流程图
      • 代码
  • 五、注意事项
    • 5.1 节点池NodePool生命周期
    • 5.2 还原节点状态
  • 六、总结


前言

现有Cocos对象池技术贴基本是2.x的,3.x的资料很少,如果是直接从3.x上手的建议看看这里。


一、适用场景

稍微复杂的游戏开发均需考虑到性能优化,基本会涉及对象池使用,对象池使用场景针对那种需要相同单位频繁创建、销毁的情况,例如动作游戏中的子弹、敌人等;因为(cc.instantiate)和销毁(node.destroy)操作是非常耗费性能的;

二、基本原理

针对相同单位的频繁创建和销毁,为了提高性能,我们可以创建后复用,场景切换时再销毁;
例如游戏中的枪循环发射5颗子弹,我们可以预先创建5颗子弹,然后把子弹放到缓存池子(NodePool)里,需要的时候从池子里取出,用完放回去,如此循环,那么整个游戏过程中只会涉及5次创建和销毁操作。

当然,这是最简单的情况,实际会更灵活,例如池子动态扩容等。

三、创建和方法

import { NodePool } from "cc";

// 创建,创建后pool是一个节点数组
const pool = new NodePool()

// 获取当前缓冲池的可用对象数量
pool.size() 

// 获取对象池中的对象,如果对象池没有可用对象,则返回空。这个函数会调用 poolHandlerComp 的 reuse 函数,如果组件和函数都存在的话。
pool.get() 

// 向缓冲池中存入一个不再需要的节点对象。这个函数会自动将目标节点从父节点上移除,但是不会进行 cleanup 操作。这个函数会调用 poolHandlerComp 的 unuse 函数,如果组件和函数都存在的话。
pool.put() 

// 销毁对象池中缓存的所有节点
pool.clear() 

四、使用方案

这里用“循环发射1颗子弹”为例子

4.1 方案一:预先创建

流程图

Created with Raphaël 2.3.0 开始 创建对象池 实例化子弹并放进对象池 开始使用:从对象池取出子弹 结束使用:把子弹放进对象池 游戏结束 销毁对象池里子弹 结束 yes no

代码

import { _decorator, Component, Prefab, NodePool, instantiate } from 'cc'
const { ccclass, property } = _decorator

@ccclass('Start')
export class Start extends Component {
  public pool!: NodePool // 定义节点池
  @property({ type: Prefab, tooltip: '子弹' }) readonly bulletPfb!: Prefab // 子弹预制体

  start() {
    // 1. 创建节点池
    this.pool = new NodePool()

    // 2. 通过预制体创建节点,并放到节点池
    const node = instantiate(this.bulletPfb)
    this.pool.put(node)

    // 3. 每5秒从节点池中取出节点,使用后放回节点池
    this.schedule(() => {
      // 从节点池中取出节点
      const node = this.pool.get()

      // 这里开始使用节点
      // ..............

      // 使用结束后放回节点池
      this.pool.put(node)
    }, 5)
  }

  // 控件销毁时,清空节点池
  onDestroy() {
    this.pool.clear()
  }
}

4.2 方案二:动态创建

动态创建核心是用到才创建对象池,这里用“循环发射1颗子弹”为例子

流程图

Created with Raphaël 2.3.0 开始 开始使用 存在对象池 对象池有可用子弹 从对象池取出子弹 使用结束:把子弹放进对象池 游戏结束 销毁对象池里子弹 结束 实例化子弹 创建对象池 yes no yes no yes no

代码

1、在实际应用中创建全局对象池,每个Prefab创建一个对象池,例如这里的:
data.pools = {}

2、封装getPoolNode方法,用于根据对象池是否有节点来动态创建节点

3、子弹节点会绑定Bullet自定义组件,里面封装了destroyPool方法来处理节点的销毁,以及pool.put()回收节点时触发onDisable来处理回收逻辑

  • 场景使用
import { _decorator, Component,  Prefab, } from 'cc'
import data from '../global/Data'
import { getPoolNode } from '../utils/GameCommon'
const { ccclass, property } = _decorator

@ccclass('Start')
export class Start extends Component {

  @property({ type: Prefab, tooltip: '子弹' }) readonly bulletPfb!: Prefab // 子弹预制体

  start() {
    // 1. 每5秒从节点池中取出节点,使用后放回节点池
    this.schedule(() => {
      // 从节点池中取出节点
      const node = getPoolNode(this.bulletPfb)

      // 这里开始使用节点
      // ..............

      // 使用结束后放回节点池(destroyPool为自定义方法)
      node.getComponent(Bullet).destroyPool()
    }, 5)
  }

  // 控件销毁时,清空节点池
  onDestroy() {
    Object.keys(data.pools).forEach((key) => {
      data.pools[key].clear()
    })
  }
}

  • getPoolNode 方法
/**
 * 通过节点池创建节点
 * @param prefab 预制体
 */
export function getPoolNode(prefab: Prefab) {
  let name = prefab.data.name
  let node: Node = null
  if (data.pools.hasOwnProperty(name)) {
    //已有对应的对象池
    let pool = data.pools[name]
    if (pool.size() > 0) {
      node = pool.get()
    } else {
      node = instantiate(prefab)
    }
  } else {
    // 没有对应对象池,创建他!
    let pool = new NodePool()
    data.pools[name] = pool

    node = instantiate(prefab)
  }
  return node
}
  • Bullet 组件
import { _decorator, Component } from 'cc'
import data from '../../global/Data'
const { ccclass } = _decorator

@ccclass('Bullet')
export class CommonUnit extends Component {
  public $hp = 1 // 生命

  // 禁用时还原状态(节点池pool.put()时触发)
  onDisable() {
    this.$hp = 1
    // 如果有用到tween,则这里要停止,否则节点还会执行
    // Tween.stopAllByTarget(this.node)
  }

  // 从对象池中销毁
  destroyPool() {
    const pool = data.pools['Bullet']
    if (pool) {
      pool.put(this.node)
    } else {
      this.destroy()
    }
  }
}

五、注意事项

5.1 节点池NodePool生命周期

当节点在 NodePool 中时

  • onLoad start 只会在节点第一次从 NodePool 中取出时触发
  • onEnable 当节点从 NodePool 中取出时触发
  • onDisable 当节点被放回 NodePool 时触发
  • onDestroy 当 NodePool 被 clear 时触发

5.2 还原节点状态

回收节点时,节点状态并不会还原节点初始状态,需要用户手动还原
这就是方案二中,onDisable需要做的事

六、总结

这就是Cocos3.x 对象池NodePool使用介绍和注意事项,我也是从坑中走出来的,希望对您有帮助。
点个赞再走!

相关文章:

  • 计算机二级WPS 选择题(模拟和解析二)
  • java计算机毕业设计基于安卓Android微信的儿童疫苗接种管理小程序uniApp
  • 什么是协程?
  • [配置] 安卓 | 将微信公众号文章保存到Notion
  • Docker启动mysql服务
  • 基于java安全管理系统计算机毕业设计源码+系统+lw文档+mysql数据库+调试部署
  • 为何基于树的模型在表格型数据中能优于深度学习?
  • 贪心+二分
  • Geoserver Windows 安装部署教程
  • haproxy,nginx,keepalived综合运用
  • 动态多目标优化算法:MOEA/D-FD求解FDA1、FDA2、FDA3、FDA4和FDA5(Matlab代码)
  • 【基于C的排序算法】插入排序之直接插入排序
  • Golang——从入门到放弃
  • 报告分享|数据变现,车企利润新增长点
  • 计算机网络基本概念
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • javascript 哈希表
  • select2 取值 遍历 设置默认值
  • Vue学习第二天
  • 初探 Vue 生命周期和钩子函数
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 少走弯路,给Java 1~5 年程序员的建议
  • 自动记录MySQL慢查询快照脚本
  • zabbix3.2监控linux磁盘IO
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (06)金属布线——为半导体注入生命的连接
  • (31)对象的克隆
  • (六)c52学习之旅-独立按键
  • (免费分享)基于springboot,vue疗养中心管理系统
  • (三)Pytorch快速搭建卷积神经网络模型实现手写数字识别(代码+详细注解)
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (一)SpringBoot3---尚硅谷总结
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (原創) 如何安裝Linux版本的Quartus II? (SOC) (Quartus II) (Linux) (RedHat) (VirtualBox)
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • .net core 依赖注入的基本用发
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .net 发送邮件
  • .NET6实现破解Modbus poll点表配置文件
  • .NET的数据绑定
  • @modelattribute注解用postman测试怎么传参_接口测试之问题挖掘
  • @RequestBody与@ResponseBody的使用
  • []sim300 GPRS数据收发程序
  • [AAuto]给百宝箱增加娱乐功能
  • [C#基础]说说lock到底锁谁?
  • [C++]打开新世界的大门之C++入门
  • [COI2007] Sabor
  • [C语言][PTA基础C基础题目集] strtok 函数的理解与应用
  • [JavaWeb]—前端篇
  • [LeetCode]--61. Rotate List
  • [Linux] PXE批量装机
  • [Linux] 进程间通信基础