cocos creator资源管理器,资源动态加载和释放

article/2025/8/2 20:11:43

cocos 2.4.11版本

cocos 动态加载的资源需要自己增加引用和减少引用计数 cc.Asset.addRef 和 cc.Asset.decRef

注意:

1.使用当前代码管理资源,要区分项目中的静态资源和动态资源,静态资源就是预制体或者场景中的资源,代码中动态加载的资源是动态资源,cocos无法管理动态资源的引用计数,需要自己调用addRef,如果静态资源和动态资源有重复,那可能会导致,动态资源释放的时候,静态资源中展示的资源也没有了,因为静态资源是自动记录引用计数的,会自动释放

比如A场景中引用了spriteFrame1,但是B界面动态加载了spriteFrame1,在B界面销毁的时候检测动态的引用计数,把spriteFrame1释放了,如果A还在显示 ,就出问题了

2.所以最好是预制体都动态加载,场景中不挂在任何资源,然后使用以下资源管理,这样会出现界面打开缓慢的问题,这时候可以使用预加载,对于一些比较复杂的界面,采用定制化的预先加载,比如进入A界面的时候,在帧率允许的情况下,偷偷加载B界面的一些资源,在打开B界面的时候,会从缓存中拿,此时如果A界面关了,会释放预加载的B界面资源,我们有延迟5秒释放,如果是打开的B界面,就会停止释放,如果没有打开B界面,那就真正释放资源

3.如果场景中确实要挂资源,那就要修改下面的代码,把自定义的计数,改成cocos的addRef和decRef,这样就能让cocos自己管理包含静态和动态的引用,自动释放,就不需要代码中的release方法了

4.使用下面代码需要,在基类UIBase中添加方法

 protected loadRes(paths: string[], type: { prototype: cc.Asset }, cb: Function, cbObj: Object, bundleName: string = this.selfBundleName) {

  // 根据paths和type还有bundleName从ResMgr中获取唯一key

        const key = ResMgr.instance._getAssetKey()

        ResMgr.instance.loadRes()

        if(! this.resCounts[key]){

                this.resCounts[key] = 1;

        }

      else{

          this.resCounts[key]++;

      }

}

protect onDestroy(){

        for(let k in this.resCounts){

            const num = this.resCounts[k];

            for(let i=0;i<num;i++){

                ResMgr.instance.releaseResByKey(k);

            }

        }

}

这个方法中调用ResMgr中的加载资源,同时要在基类中存储资源的加载数量

resCounts:{[key:string]:number},ResMgr中会有生成资源唯一key的方法,记录响应资源的在本类中的引用数量,在本类onDestroy中遍历resCounts调用ResMgr中的释放资源,releaseResByKey下面代码没有写,可以自己看下代码,添加就可以

在使用应用中调用基类中的this.loadRes即可加载资源,无需关心释放问题

上代码

/*** 资源管理器* 功能:* 1. 资源加载与释放* 2. Bundle 管理* 3. 引用计数* 4. 连续加载处理(pending队列)*/
export class ResMgr {private static _instance: ResMgr;private _bundles: Map<string, cc.AssetManager.Bundle> = new Map();/**资源引用计数 */private _refCount: Map<string, number> = new Map();/**加载中缓存回调 */private _pendingQueue: Map<string, Array<{ resolve: Function, reject: Function }>> = new Map();/**已加载资源 */private _loadedAssets: Map<string, cc.Asset> = new Map();/**释放调度器 */private _releaseTimers: Map<string, number> = new Map();/**默认延迟5秒释放 */private _defaultDelayTime: number = 5000;/**路径分隔符 */private _pathSeparator: string = "!^!";public static get instance(): ResMgr {if (!this._instance) {this._instance = new ResMgr();}return this._instance;}private constructor() { }/*** 加载Bundle* @param bundleName bundle名称* @returns Promise<AssetManager.Bundle>*/public loadBundle(bundleName: string): Promise<cc.AssetManager.Bundle> {return new Promise((resolve, reject) => {// 如果已经加载过,直接返回if (this._bundles.has(bundleName)) {resolve(this._bundles.get(bundleName));return;}// 加载bundlecc.assetManager.loadBundle(bundleName, (err: Error, bundle: cc.AssetManager.Bundle) => {if (err) {console.error(`load bundle ${bundleName} failed`, err);reject(err);return;}this._bundles.set(bundleName, bundle);resolve(bundle);});});}/*** 释放Bundle* @param bundleName bundle名称*/public releaseBundle(bundleName: string): void {if (!this._bundles.has(bundleName)) {return;}const bundle = this._bundles.get(bundleName);cc.assetManager.removeBundle(bundle);this._bundles.delete(bundleName);}/*** 加载资源* @param path 资源路径* @param type 资源类型(可选)* @param bundleName bundle名称(可选,默认为"resources")* @returns Promise<Asset>*/public load(path: string,type:{prototype:cc.Asset}, bundleName: string = "resources"): Promise<cc.Asset> {const assetKey = this._getAssetKey(path,type,bundleName);return new Promise((resolve, reject) => {// 如果资源正在延迟释放,取消释放if (this._releaseTimers.has(assetKey)) {clearTimeout(this._releaseTimers.get(assetKey));this._releaseTimers.delete(assetKey);this._increaseRef(assetKey);resolve(this._loadedAssets.get(assetKey));return;}// 如果资源已经加载,增加引用计数并返回if (this._loadedAssets.has(assetKey)) {this._increaseRef(assetKey);resolve(this._loadedAssets.get(assetKey));return;}// 如果有正在加载的相同资源,加入pending队列if (this._pendingQueue.has(assetKey)) {this._pendingQueue.get(assetKey).push({ resolve, reject });return;}// 创建新的pending队列this._pendingQueue.set(assetKey, [{ resolve, reject }]);// 加载bundle(如果尚未加载)this.loadBundle(bundleName).then((bundle) => {// 加载资源bundle.load(path,type, (err: Error, asset: cc.Asset) => {if (err) {console.error(`load asset ${path} failed`, err);this._rejectPending(assetKey, err);return;}this._loadedAssets.set(assetKey, asset);// 触发所有pending的resolvethis._resolvePending(assetKey, asset);});}).catch((err) => {this._rejectPending(assetKey, err);});});}/*** 预加载资源* @param path 资源路径* @param type 资源类型(可选)* @param bundleName bundle名称(可选,默认为"resources")* @returns Promise<void>*/public preload(path: string, type?: typeof cc.Asset, bundleName: string = "resources"): Promise<void> {return new Promise((resolve, reject) => {this.loadBundle(bundleName).then((bundle) => {bundle.preload(path, type, (err: Error) => {if (err) {console.error(`preload asset ${path} failed`, err);reject(err);return;}resolve();});}).catch(reject);});}/*** 释放资源* @param path 资源路径* @param type 资源类型* @param bundleName bundle名称(可选,默认为"resources")* @param delayTime 延迟释放时间(可选,默认为5秒)*/public release(path: string, type: typeof cc.Asset, bundleName: string = "resources", delayTime?: number): void {const assetKey = this._getAssetKey(path,type,bundleName);if(this._pendingQueue.has(assetKey)) {// 如果资源正在加载,取消加载this._pendingQueue.delete(assetKey);}if (!this._loadedAssets.has(assetKey)) {return;}// 减少引用计数this._decreaseRef(assetKey);// 如果已经有释放定时器,先清除旧的if (this._releaseTimers.has(assetKey)) {clearTimeout(this._releaseTimers.get(assetKey));this._releaseTimers.delete(assetKey);}// 如果引用计数为0,释放资源if (this._getRefCount(assetKey) <= 0) {const delay = delayTime !== undefined ? delayTime : this._defaultDelayTime;const timer = setTimeout(() => {this._doRelease(assetKey, bundleName);}, delay);this._releaseTimers.set(assetKey, timer as unknown as number);}}/*** 直接释放资源对象* @param asset 资源对象*/public releaseAsset(asset: cc.Asset,type:typeof cc.Asset, delayTime?: number): void {if (!asset) {console.warn("Asset is null or undefined.");return;}// 查找资源对应的 keylet assetKey: string | null = null;for (const key in this._loadedAssets) {if (this._loadedAssets.get(key) === asset) {assetKey = key;break;}}if (!assetKey) {console.warn("Asset not found in loaded assets.");return;}// 减少引用计数this._decreaseRef(assetKey);// 如果已经有释放定时器,先清除旧的if (this._releaseTimers.has(assetKey)) {clearTimeout(this._releaseTimers.get(assetKey));this._releaseTimers.delete(assetKey);}// 如果引用计数为0,释放资源if (this._getRefCount(assetKey) <= 0) {const [bundleName, path] = assetKey.split(this._pathSeparator, 2);const delay = delayTime !== undefined ? delayTime : this._defaultDelayTime;const timer = setTimeout(() => {this._doRelease(assetKey, bundleName);}, delay);this._releaseTimers.set(assetKey, timer as unknown as number);}}/*** 获取资源引用计数* @param path 资源路径* @param bundleName bundle名称(可选,默认为"resources")* @returns number*/public getRefCount(path: string,type:typeof cc.Asset, bundleName: string = "resources"): number {const assetKey = this._getAssetKey(path,type,bundleName);return this._getRefCount(assetKey);}// 私有方法/*** 实际执行资源释放* @param assetKey 资源键名* @param bundleName bundle名称*/private _doRelease(assetKey: string, bundleName: string): void {// 再次检查引用计数,确保可以释放if (!this._loadedAssets.has(assetKey) || this._getRefCount(assetKey) > 0) {return;}const path = assetKey.split(this._pathSeparator)[1];const bundle = this._bundles.get(bundleName);if (bundle) {bundle.release(path);}this._loadedAssets.delete(assetKey);this._refCount.delete(assetKey);this._releaseTimers.delete(assetKey);}private _increaseRef(assetKey: string): void {const count = this._getRefCount(assetKey);this._refCount.set(assetKey, count + 1);}private _decreaseRef(assetKey: string): void {const count = this._getRefCount(assetKey);this._refCount.set(assetKey, Math.max(0, count - 1));}private _getRefCount(assetKey: string): number {return this._refCount.get(assetKey) || 0;}private _resolvePending(assetKey: string, asset: cc.Asset): void {if (!this._pendingQueue.has(assetKey)) {return;}const pendingList = this._pendingQueue.get(assetKey);pendingList.forEach(({ resolve }) => {this._increaseRef(assetKey);  // 增加引用计数resolve(asset);});this._pendingQueue.delete(assetKey);}private _rejectPending(assetKey: string, err: Error): void {if (!this._pendingQueue.has(assetKey)) {return;}const pendingList = this._pendingQueue.get(assetKey);pendingList.forEach(({ reject }) => {reject(err);});this._pendingQueue.delete(assetKey);}/*** 生成资源唯一键* @param path 原始路径(可带或不带后缀)* @param type 资源类型* @param bundleName bundle名称*/public _getAssetKey(path: string, type:{prototype:cc.Asset}, bundleName: string): string {// 标准化路径:移除后缀,转为小写避免大小写问题const normalizedPath = path.replace(/\.[^/.]+$/, "").toLowerCase();return `${bundleName}${this._pathSeparator}${normalizedPath}|${type["name"]}`;}
}


http://www.hkcw.cn/article/wTlPgkRWQm.shtml

相关文章

认识scratch,scratch是什么,如何使用

scratch是图形编程&#xff0c;将编程简化为积木的堆叠和嵌套&#xff0c;无需手写代码&#xff0c;只需清晰的逻辑即可完成自己的代码设计。通过它可以制作简单的小游戏等。 如图所示&#xff0c;这个就是scratch打开的界面&#xff0c;整个界面分为左中右三个部分&#xff0c…

HarmonyOS实战:腾讯IM之聊天详情页面搭建(二)

前言 鸿蒙版本腾讯 IM 的聊天功能十分复杂&#xff0c;需要开发者手动实现整个聊天对话的业务代码&#xff0c;这对开发者来说是个不小的挑战。本篇文章先从最基础的聊天对话列表开始教你一步一步实现完整的聊天功能&#xff0c;建议点赞收藏&#xff01; 实现效果 先看本文…

IM系统的负载均衡

1.IM场景的负载均衡 2.方案总览 SDK层想要连接一个TCP网关或者WebSocket网关的方案 SDK单地址:在SDK中写死某个网关的IP或者域名,缺点是更换地址需要重新打包SDK SDK多地址:防止某一个地址嗝屁了写上多个地址用足保持高可用 暴露接口给客户端:SDK层访问接口动态获得地址 注…

动态规划之网格图模型(一)

文章目录 动态规划之网格图模型&#xff08;一&#xff09;LeetCode 64. 最小路径和思路Golang 代码 LeetCode 62. 不同路径思路Golang 代码 LeetCode 63. 不同路径 II思路Golang 代码 LeetCode 120. 三角形最小路径和思路Golang 代码 LeetCode 3393. 统计异或值为给定值的路径…

血糖监测仪解决方案推荐芯片-NRF52832/HS6621/OM6626

随着糖尿病患者数量的增加和人们健康意识的提升&#xff0c;血糖监测仪成为了日常健康管理的重要设备。市场对便携、智能且易于使用的血糖监测仪需求持续增长&#xff0c;而无线通信技术&#xff0c;尤其是蓝牙技术&#xff0c;已成为现代血糖监测仪的核心组件&#xff0c;提供…

基于Vite的前端自动化部署方案

&#x1f468; 作者简介&#xff1a;大家好&#xff0c;我是Taro&#xff0c;全栈领域创作者 ✒️ 个人主页&#xff1a;唐璜Taro &#x1f680; 支持我&#xff1a;点赞&#x1f44d;&#x1f4dd; 评论 ⭐️收藏 文章目录 前言一、主流解决方案二、了解SCP概念三、自动化部署…

PlankAssembly 笔记 DeepWiki 正交视图三维重建

manycore-research/PlankAssembly | DeepWiki PlankAssembly项目原理 这个项目是一个基于深度学习的3D重建系统&#xff0c;其核心原理是从三个正交视图的工程图纸中重建出3D形状的结构化程序表示。 核心技术原理 1. 问题定义 PlankAssembly旨在从三个正交视图的工程图纸中…

MQTT协议,EMQX部署,MQTTX安装学习

一、MQTT概述 1.什么是MQTT MQTT是一种基于“发布订阅“”模式的消息传输协议。 消息&#xff1a;设备和设备之间传输的数据&#xff0c;或者服务和服务之间要传输的数据。 协议&#xff1a;传输数据时所遵循的规范。 2.常见的通讯模式 &#xff08;1&#xff09;客户端-服…

多模态大语言模型arxiv论文略读(101)

ML-Mamba: Efficient Multi-Modal Large Language Model Utilizing Mamba-2 ➡️ 论文标题&#xff1a;ML-Mamba: Efficient Multi-Modal Large Language Model Utilizing Mamba-2 ➡️ 论文作者&#xff1a;Wenjun Huang, Jiakai Pan, Jiahao Tang, Yanyu Ding, Yifei Xing, …

论文阅读:ADVWEB : CONTROLLABLE BLACK-BOX ATTACKS ON VLM-POWERED WEB AGENTS

原文&#xff1a;2410.17401 源码&#xff1a;https://ai-secure.github.io/AdvWeb/ 摘要&#xff1a; 本文设计了一种专门针对web agent的黑盒攻击框架&#xff0c;通过训练一个对抗性提示生成模型&#xff0c;在网页中自动生成并注入“隐形”对抗性字符串&#xff0c;引导网…

Wireshark 在 macOS 上使用及问题解决

wireshark概述 Wireshark 是被广泛使用的免费开源网络协议分析软件&#xff08;network protocol analyzer&#xff09;或网络数据包分析工具&#xff0c;它可以让你在微观层面上查看网络上发生的事情。它的主要功能是截取网络数据包&#xff0c;并尽可能详细地展示网络数据包…

企业级安全实践:SSL/TLS 加密与权限管理(一)

引言 ** 在数字化转型的浪潮中&#xff0c;企业对网络的依赖程度与日俱增&#xff0c;从日常办公到核心业务的开展&#xff0c;都离不开网络的支持。与此同时&#xff0c;网络安全问题也日益严峻&#xff0c;成为企业发展过程中不可忽视的重要挑战。 一旦企业遭遇网络安全事…

#Js篇:BlobFile对象URL.createObjectURL()fetchlocationnavigatornew URl

Blob 在 JavaScript 中&#xff0c;Blob 是一个非常重要的对象&#xff0c;用于表示不可变的、原始的二进制数据块&#xff08;Binary Large Object&#xff09; arrayBuffer()&#xff1a;获取 Blob 的二进制数据作为 ArrayBuffer。 stream()&#xff1a;创建一个可读流&…

HAProxy 可观测性最佳实践

HAProxy 简介 HAProxy&#xff08;High Availability Proxy&#xff09;是一款广泛使用的高性能负载均衡器&#xff0c;支持 TCP 和 HTTP 协议&#xff0c;提供高可用性、负载均衡和代理服务。它特别适用于负载较大的 Web 站点&#xff0c;能够支持数以万计的并发连接&#xf…

软件测试|FIT故障注入测试工具——ISO 26262合规下的智能汽车安全验证引擎

FIT&#xff08;Fault Injection Tester&#xff09;是SURESOFT专为汽车电子与工业控制设计的自动化故障注入测试工具​&#xff0c;基于ISO 26262等国际安全标准开发&#xff0c;旨在解决传统测试中效率低、成本高、安全隐患难以复现的问题&#xff0c;其核心功能包括&#xf…

【计算机网络】应用层协议Http——构建Http服务服务器

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a; 【Linux笔记】——进程间关系与守护进程 &#x1f516;流水不争&#xff0c;争的是滔滔不息 一、Http协…

[ctfshow web入门] web80

信息收集 过滤了php和data if(isset($_GET[file])){$file $_GET[file];$file str_replace("php", "???", $file);$file str_replace("data", "???", $file);include($file); }else{highlight_file(__FILE__); }解题 大小写…

移动安全Android——客户端数据安全

本地文件权限配置 测试流程 &#xff08;1&#xff09;手机运行待测APP应用&#xff0c;adb执行命令找到APP包名 adb shell dumpsys activity top|findstr ACTIVITY &#xff08;2&#xff09;adb shell 进入设备&#xff0c;以Root权限进入/data/data/package包名目录下 c…

AI生态警报:MCP协议风险与应对指南(下)——MCP Host安全

AI生态警报&#xff1a;MCP协议风险与应对指南&#xff08;上&#xff09;——架构与供应链风险https://blog.csdn.net/WangsuSecurity/article/details/148335401?sharetypeblogdetail&sharerId148335401&sharereferPC&sharesourceWangsuSecurity&spm1011.24…

机房网络设备操作安全管理制度

该制度围绕机房网络设备操作安全,规定账号实行系统管理员、操作管理员、一般用户三级分级管理,遵循最小授权和权限分割原则,账号需实名制、禁止共享及转借,密码设置需至少 8 位、3 种字符组合且每 3 个月修改一次;高危指令执行需上级审批、双人核查,远程登录需限制权限、…