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

cocosCreator 之 3.x使用NodePool对象池和封装

版本: cocosCreator 3.4.0

语言: TypeScript

环境: Mac


NodePool


在项目中频繁的使用instantiatenode.destory对性能有很大的耗费,比如飞机射击中的子弹使用和销毁。

因此官方提供了NodePool,它被作为管理节点对象的缓存池使用。定义如下:

export class NodePool {// 缓冲池处理组件,用于节点的回收和复用逻辑,这个属性可以是组件类名或组件的构造函数poolHandlerComp?: Constructor<IPoolHandlerComponent> | string;// 构造函数,可传递组件或名称,用于处理节点的复用和回收constructor(poolHandlerComp?: Constructor<IPoolHandlerComponent> | string);// 获取当前缓冲池的可用对象数量size(): number;// 销毁对象池中缓存的所有节点clear(): void;/*@func: 向缓冲池中存入一个不再需要的节点对象@param: 回收的目标节点注意:1. 该函数会自动将目标节点从父节点上移除,但是不会进行 cleanup 操作2. 如果存在poolHandlerComp组件和函数,会自动调用 unuse函数*/put(obj: Node): void;/*@func: 获取对象池中的对象,如果对象池没有可用对象,则返回空@param: 如果组件和函数存在,会向 poolHandlerComp 中的 'reuse' 函数传递的参数*/get(...args: any[]): Node | null;
}

接口汇总:

接口说明
new()创建对象池
put()将节点放到对象池中
get()从对象池中获取节点
size()获取对象池中对象的数目
clear()销毁对象池中的所有对象

使用NodePool的大概思路:

  • 通过NodePool创建对象池
  • 获取节点时,可先检测对象池的数目;如果 =0 则克隆节点并放到对象池中,如果 >0 则从对象池中获取
  • 节点不使用的时候,如果没有对象池,则调用node.destory,否则将节点放到对象池中

在对象池中,有个get(...args: any[])的方法,方法参数的使用主要针对于:对象池创建时添加了可选参数。

以飞机射击中子弹的构建为目的,看下关于对象池的使用示例相关:

// GameManager.ts 游戏管理类
import { BulletItem } from '../bullet/BulletItem';@ccclass('GameManager')
export class GameManager extends Component {@property(Prefab) bullet: Prefab = null;		// 子弹预制体private _bulletPool: NodePool = null;				// 子弹对象池onLoad() {// 创建子弹对象池this._bulletPool = new NodePool();}// 创建玩家子弹private createPlayerBullet() {// 获取子弹节点const bulletNode = this.getBulletNode();const bulletItem = bulletNode.getComponent(BulletItem);// 此处将子弹对象池传入子弹对象脚本bulletItem.init(this._bulletPool);}// 获取子弹节点private getBulletNode(): Node {const size = this._bulletPool.size();if (size <= 0) {// 克隆子弹节点const bulletNode = instantiate(this.bullet);// 将子弹节点添加到对象池中this._bulletPool.put(bulletNode);}// 从对象池中获取节点return this._bulletPool.get();}onDestroy() {// 销毁对象池this._bulletPool.clear();}
}// BulletItem.ts 子弹对象组件脚本
export class BulletItem extends Component {private _bulletPool: NodePool = null;public init(bulletPool: NodePool) {this._bulletPool = bulletPool;}private destroyBullet() {// 检测是否存在对象池,如果存在,则将对象放到对象池中,否则销毁if (this._bulletPool) {this._bulletPool.put(this.node);}else {this.node.destory();}}
}

如上例子,简单演示了下NodePool对象池的使用,但需要注意:

  • 最好存储同类型的节点,方便管理
  • 注意检测对象池内的对象数目或通过get获取对象后,进行安全判定,避免null
  • 注意对象池对象的释放

构造函数的可选参数

在上面的定义文件中,针对于对象池的构建,有着可选参数的支持,代码如下:

// 缓冲池处理组件,用于节点的回收和复用逻辑,这个属性可以是组件类名或组件的构造函数
poolHandlerComp?: Constructor<IPoolHandlerComponent> | string;
// 构造函数,可传递组件或名称,用于处理节点的复用和回收事件逻辑
constructor(poolHandlerComp?: Constructor<IPoolHandlerComponent> | string);

可选参数的支持主要有两种形式:

  1. string字符串形式
  2. IPoolHandlerComponent 缓存池处理组件形式

对于这两种形式,其本质就是增加了对对象池中对象的自定义逻辑处理,以组件为参数,看下它的定义:

export interface IPoolHandlerComponent extends Component {// 在对象被放入对象池的时候进行调用unuse(): void;// 从对象池中获取对象的时候被调用reuse(args: any): void;
}

这两个方法的调用,看下源码的实现:

// 来源于 node-pool.ts
export class NodePool {// 向对象缓存池存入不需要的对象public put (obj: Node) {if (obj && this._pool.indexOf(obj) === -1) {// 从父节点移除,但并不cleanupobj.removeFromParent();// 获取组件poolHandlerComp,并检测是否存在 unuse方法,如果存在则调用const handler = this.poolHandlerComp?obj.getComponent(this.poolHandlerComp):null;if (handler && handler.unuse) {handler.unuse();}this._pool.push(obj);}}// 获取对象池中的对象public get (...args: any[]): Node | null {// 检测对象池中是否有对象const last = this._pool.length - 1;if (last < 0) {return null;} else {// 将对象从缓存池中取出const obj = this._pool[last];this._pool.length = last;// 获取组件poolHandlerComp,并检测是否存在reuse方法,如果存在则调用const handler=this.poolHandlerComp?obj.getComponent(this.poolHandlerComp):null;if (handler && handler.reuse) {handler.reuse(arguments);}return obj;}}
}

上面的代码有助于对两个方法的调用时机增加一些了解。

下面我们依然以飞机的子弹构建为例,代码增加一些拓展,用于支持对象池的自定义逻辑处理。

// GameManager.ts 游戏管理类
import { BulletItem } from '../bullet/BulletItem';@ccclass('GameManager')
export class GameManager extends Component {  onLoad() {// 创建子弹对象池, 参数设定为子弹类的名字this._bulletPool = new NodePool("BulletItem");}private getBulletNodePool() {const size = this._bulletPool.size();if (size <= 0) {const bulletNode = instantiate(this.bullet_1);this._bulletPool.put(bulletNode);}// 获取子弹节点时,可以设置自定义的参数相关return this._bulletPool.get();}
}// BulletItem.ts 子弹对象组件脚本,增加
export class BulletItem extends Component implements IPoolHandlerComponent {unuse(): void {console.log("------ 调用了组件的 unuse 方法");}reuse(args: any): void {console.log("------ 调用了组件的 reuse 方法");}
}

增加对对象的自定义逻辑处理,其要点就是:

  • 构建对象池时,需要添加可选参数,参数的名字或组件一定要是对象的脚本组件相关
  • 对象的组件脚本类,需要增加implements IPoolHandlerComponent 的实现,也就是unusereuse方法
  • 根据情况,自定义设定NodePool.get的参数相关

到这里,关于NodePool的基本使用介绍完毕。


NodePool管理器


在上面的例子中,关于对象池的使用存在着几个问题:

  1. 从对象池获取对象和将对象放入对象池的调用在不同的脚本文件中,可能会出现维护比较困难的问题

  2. 对象池的构建不仅针对于子弹,而且可能还有敌机,道具等,可能会出现多个对象池且代码重复的问题。

因此,我们可构建一个对象池的管理类,来统一管理多个不同的对象池,类似于cocos2d-x中的PoolManager

大致的属性和接口是:

属性或方法说明
_nodePoolMap保存所有对象池的容器,结构是Map<对象池名, NodePool池>
getNodePoolByName():NodePool通过名字从容器中获取对象池,如果没有则创建,如果存在则获取
getNodeFromPool():Node通过名字从对象池中获取节点,如果没有则克隆,如果存在则获取
putNodeToPool()将节点放入对象池中
clearNodePoolByName()通过名字将对象池从容器中移除
clearAll()移除容器中的所有对象池

该类使用的是单例模式,详细的代码如下:

// 对象池管理器
import { _decorator, Component, instantiate, NodePool, Prefab} from 'cc';
const { ccclass } = _decorator;export class NodePoolManager {private static _instance: NodePoolManager = null;private _nodePoolMap: Map<string, NodePool> = null;static get instance() {if (this._instance) {return this._instance;}this._instance = new NodePoolManager();return this._instance;}constructor() {this._nodePoolMap = new Map<string, NodePool>();}/*@func 通过对象池名字从容器中获取对象池@param name 对象池名字@return 对象池*/private getNodePoolByName(name: string): NodePool {if (!this._nodePoolMap.has(name)) {let nodePool = new NodePool(name);this._nodePoolMap.set(name, nodePool);}let nodePool = this._nodePoolMap.get(name);return nodePool;}/*@func 通过对象池名字从对象池中获取节点@param name 对象池名字@param prefab 可选参数,对象预制体@return 对象池中节点 */public getNodeFromPool(name: string, prefab?: Prefab): Node | null {let nodePool = this.getNodePoolByName(name);const poolSize = nodePool.size();if (poolSize <= 0) {let node = instantiate(prefab);nodePool.put(node);}return nodePool.get();}/*@func 将节点放入对象池中@param name 对象池名字@param node 节点*/public putNodeToPool(name: string, node: Node) {let nodePool = this.getNodePoolByName(name);nodePool.put(node);}// 通过名字将对象池从容器中移除public clearNodePoolByName(name: string) {// 销毁对象池中对象let nodePool = this.getNodePoolByName(name);nodePool.clear();// 删除容器元素this._nodePoolMap.delete(name);}// 移除所有对象池public clearAll() {this._nodePoolMap.forEach((value: NodePool, key: string) => {value.clear();});this._nodePoolMap.clear();}static destoryInstance() {this._instance = null;}
}

测试示例:

// GameManager.ts 
const BULLET_POOL_NAME = "BulletItem"       // 子弹内存池// 创建玩家子弹
private createPlayerBullet() {// 获取子弹节点,参数:节点名,子弹预制体const poolManager = NodePoolManager.instance;const bulletNode = poolManager.getNodeFromPool(BULLET_POOL_NAME, this.bulletPrefab);bulletNode.parent = this.bulletRoot;
}// BulletItem.ts
private destroyBullet() {// 检测是否存在对象池,如果存在,则将对象放到对象池中,否则销毁if (this._bulletPool) {//this._bulletPool.put(this.node);const poolManager = NodePoolManager.instance;poolManager.putNodeToPool(BULLET_POOL_NAME, this.node);}else {this.node.destory();}
}

管理类中有个接口叫做getNodeFromPool(name: string, prefab?: Prefab),第二个参数也可以为prefabName,然后通过resource.load进行动态加载,类似实现:

public getNodeFromPool(name: string, prefabName?: string): Node | null {let nodePool = this.getNodePoolByName(name);const poolSize = nodePool.size();if (poolSize <= 0) {const url = "prefab/" + prefabName;resources.load(url, (err, prefab) => {if (err) {return console.err("getNodeFromPool resourceload failed:" + err.message);}let node = instantiate(prefab);nodePool.put(node);});}return nodePool.get();
}

resouces.load属于异步操作,可能会出现代码未加载完成就获取的问题,因此可使用异步编程

public getNodeFromPool(name: string, prefabName?: string): Promise<Node | null> {return new Promise<Node | null>((resolve, reject) => {let nodePool = this.getNodePoolByName(name);const poolSize = nodePool.size();if (poolSize <= 0) {const url = "prefab/" + prefabName;resources.load(url, (err, prefab) => {if (err) {console.error("getNodeFromPool resourceload failed:" + err.message);reject(err);} else {let node = instantiate(prefab);nodePool.put(node);resolve(nodePool.get());}});} else {resolve(nodePool.get());}});
}

关于一些TypeScript的语法相关,可参考博客:

TypeScript 之 Map

TypeScript 之 异步编程

因工作的某些缘故,可能对NodePool的理解及编写示例有所不当,请不吝赐教,感激不尽!

最后祝大家学习生活愉快!

相关文章:

cocosCreator 之 3.x使用NodePool对象池和封装

版本&#xff1a; cocosCreator 3.4.0 语言&#xff1a; TypeScript 环境&#xff1a; Mac NodePool 在项目中频繁的使用instantiate和node.destory对性能有很大的耗费&#xff0c;比如飞机射击中的子弹使用和销毁。 因此官方提供了NodePool&#xff0c;它被作为管理节点对象…...

三、RestClient操作索引库与文档

文章目录 三、RestClient操作索引库与文档3.1 操作索引库3.2 操作文档结束语 三、RestClient操作索引库与文档 ES官方提供了各种不同语言的客户端&#xff0c;用来操作ES。这些客户端的本质就是组装DSL语句&#xff0c;通过http请求发送给ES。 官方文档地址: https://www.ela…...

Hadoop3教程(五):NameNode和SecondaryNameNode

文章目录 &#xff08;59&#xff09;NN和2NN的工作机制&#xff08;60&#xff09;FsImage镜像文件&#xff08;61&#xff09;Edits编辑日志&#xff08;62&#xff09;Checkpoint时间设置参考文献 &#xff08;59&#xff09;NN和2NN的工作机制 NameNode的数据是存储在磁盘…...

腾讯云我的世界mc服务器多少钱一年?

腾讯云我的世界mc服务器多少钱&#xff1f;95元一年2核2G3M轻量应用服务器、2核4G5M带宽优惠价218元一年、4核8G12M带宽轻量服务器446元一年&#xff0c;云服务器CVM标准型S5实例2核2G优惠价280元一年、2核4G配置服务器748元一年&#xff0c;腾讯云百科txybk.com分享腾讯云我的…...

modelsim实现二选一以及D触发器并仿真

#实验一 二选一 module two_1(in1,in2,cho,out); input[3:0] in1,in2; output[3:0] out; reg[3:0] out; input cho; always* begin if(cho0) outin1; else outin2; end endmodule module two1_test(); …...

直线导轨在喷涂行业中的应用场景

直线导轨因其具有精度高、速度快、刚性强、使用寿命长等特点被广泛应用在各行各种中&#xff0c;其中&#xff0c;在喷涂行业中的应用最为广泛&#xff0c;以下是直线导轨在喷涂行业中的应用场景&#xff1a; 1、平面喷涂&#xff1a;直线导轨可以应用在各种机械加工的平面&…...

纯css+js自制下拉框

前提 因为html的select标签&#xff0c;下拉框自定义程度非常的低&#xff0c;为了贴合而项目ui显示&#xff0c;所以打算自制下拉框 代码 html <div class"pos-rel"><div id"select" class"select get-select"><span class&…...

uniapp在App端如何动态修改原生导航栏?

uniapp在App端如何动态修改原生导航栏&#xff1f; 文章目录 uniapp在App端如何动态修改原生导航栏&#xff1f;page.json配置修改 buttons 文字修改按钮上的角标设置 searchInput的 focus设置 searchInput的 text 在App端可以通过得到 webview 对象&#xff0c;通过当前 webvi…...

Linux:CPUPower管理器 --- cpufreq解析

一、cpufreq是什么&#xff1f; cpufreq是Linux内核下的一种功率管理框架&#xff0c;它负责改变CPU的频率&#xff0c;以降低功耗并延长电池寿命。该框架的主要机制是动态调整CPU频率&#xff0c;该频率受限于CPU的负载和功耗。cpufreq能够动态地将频率降低到最低值或最高值&a…...

【嵌入式开发问答】不是普通的嵌入式八股

1. 进程、线程、堆栈、溢出 【问&#xff1a;】 进程的堆栈的物理内存是什么时候分配的&#xff1f; 堆栈的大小限制是多大&#xff1f;这个限制可以调整吗&#xff1f; 当堆栈发生溢出后应用程序会发生什么&#xff1f; 【答&#xff1a;】...

面试题-springboot篇-SpringBoot的注解

SpringBootApplication是SpringBoot的最核心的注解。 SpringBootApplication注解在SpringBoot的主类上&#xff0c;标识是SpringBoot应用&#xff0c;用来开启SpringBoot的各项能力。由SpringBootConfiguration、EnableAutoConfiguration、ComponentScan三个注解组成。这三个注…...

BaiChuan2保姆级微调范例

前方干货预警&#xff1a;这可能是你能够找到的&#xff0c;最容易理解&#xff0c;最容易跑通的&#xff0c;适用于各种开源LLM模型的&#xff0c;同时支持多轮和单轮对话数据集的大模型高效微调范例。 我们构造了一个修改大模型自我认知的3轮对话的玩具数据集&#xff0c;使用…...

postgresql参数优化

一 相关参数介绍 1.1 内存参数-shared_buffers shared_buffers&#xff1a;共享缓存区的大小&#xff0c;相当于oracle数据库中的SGA. 一般推荐为内存的四分之一&#xff0c;不超过总内存的二分之一。 该值默认是128M。 1.2 cpu并行参数-max_parallel_workers max_parall…...

【极速发表】2-4区SCI (含CCF),平均录用周期仅2个月,最快11天见刊!

一、计算机科学类SCI (11.30截稿) 【期刊概况】IF:4.0-5.0, JCR2区&#xff0c;中科院3区&#xff1b; 【检索情况】SCI在检&#xff0c;正刊&#xff1b; 【国人占比】10.58%&#xff1b; 【自引率】7.50%&#xff1b; 【年发文量】100篇以下&#xff1b; 【预警情况】无…...

Git 提交规范

遇到的问题 在项目中采用 git 管理代码版本时&#xff0c;突然不能进行提交&#xff08;git commit&#xff09;。 报错信息如下&#xff1a; ERROR invalid commit message format. Proper commit message format is required for automated changelog generation. Git 规范…...

[Python进阶] 操纵鼠标:PyAutoGUI

6.4 操纵鼠标&#xff1a;PyAutoGUI 6.4.1 说明 PyAutoGUI是一个Python的GUI自动化工具&#xff0c;它可以让程序自动控制鼠标和键盘的一系列操作。它能够模拟鼠标的移动、点击、拖拽等操作&#xff0c;以及键盘的按键按下和释放等操作。PyAutoGUI还提供了其他功能&#xff0…...

JavaScript querySelector

querySelector方法的语法&#xff1a; var element document.getElementById("id"); element.querySelector(selector)element是要执行选择操作的父元素&#xff0c;selector是CSS选择器&#xff0c;用于指定要选择的元素。 querySelector方法返回匹配选择器的第一…...

Selenium自动化测试

一、Selenium自动化测试&#xff08;基于python&#xff09; 1、Selenium简介&#xff1a; 1.1 Selenium是一款主要用于Web应用程序自动化测试的工具集合。Selenium测试直接运行在浏览器中&#xff0c;本质是通过驱动浏览器&#xff0c;模拟浏览器的操作&#xff0c;比如跳转…...

Lua调用C#类

先创建一个Main脚本作为主入口&#xff0c;挂载到摄像机上 public class Main : MonoBehaviour {// Start is called before the first frame updatevoid Start(){LuaMgr.GetInstance().Init();LuaMgr.GetInstance().DoLuaFile("Main");}// Update is called once p…...

“react“: “^16.14.0“,打开弹窗数据发生变化

“react”: “^16.14.0”, 弹窗 打开弹窗数据发生变化 // 这里对比changeHistoryVisible是否发生改变调用后端方法改变数据componentDidUpdate(prevProps) {if (prevProps.changeHistoryVisible ! this.props.changeHistoryVisible && this.props.changeHistoryVisi…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解

进来是需要留言的&#xff0c;先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码&#xff0c;输入的<>当成字符串处理回显到页面中&#xff0c;看来只是把用户输…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...

aardio 自动识别验证码输入

技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”&#xff0c;于是尝试整合图像识别与网页自动化技术&#xff0c;完成了这套模拟登录流程。核心思路是&#xff1a;截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...

【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法

使用 ROS1-Noetic 和 mavros v1.20.1&#xff0c; 携带经纬度海拔的话题主要有三个&#xff1a; /mavros/global_position/raw/fix/mavros/gpsstatus/gps1/raw/mavros/global_position/global 查看 mavros 源码&#xff0c;来分析他们的发布过程。发现前两个话题都对应了同一…...