diff --git a/assets/core/common/audio/AudioEffectPool.ts b/assets/core/common/audio/AudioEffectPool.ts index e4f3a712bc6febb970393fa69d18bd4be53f39c8..5c740e716df620a6965a84ed5070088181b6afe1 100644 --- a/assets/core/common/audio/AudioEffectPool.ts +++ b/assets/core/common/audio/AudioEffectPool.ts @@ -157,7 +157,7 @@ export class AudioEffectPool { // 释放音效资源 this.res.forEach((urls: string[], bundleName: string) => { - urls.forEach(url => resLoader.release(bundleName, url)); + urls.forEach(url => resLoader.release(url, bundleName)); }); // 释放池中播放器 diff --git a/assets/core/common/loader/ResLoader.ts b/assets/core/common/loader/ResLoader.ts index 8489f6e93c7d747166c8478d62318c63a8720773..50b856c610b6b961d5f5d3981f766d596920fb1d 100644 --- a/assets/core/common/loader/ResLoader.ts +++ b/assets/core/common/loader/ResLoader.ts @@ -1,4 +1,4 @@ -import { __private, Asset, AssetManager, assetManager, error, js, resources, warn } from "cc"; +import { __private, AnimationClip, Asset, AssetManager, assetManager, AudioClip, error, Font, ImageAsset, js, JsonAsset, Material, Mesh, Prefab, resources, sp, SpriteFrame, Texture2D, warn } from "cc"; export type AssetType = __private.__types_globals__Constructor | null; export type Paths = string | string[]; @@ -105,6 +105,15 @@ oops.res.loadRemote(this.url, opt, onComplete); //#endregion //#region 资源包管理 + + /** + * 获取资源包 + * @param name 资源包名 + */ + getBundle(name: string) { + return assetManager.bundles.get(name); + } + /** * 加载资源包 * @param name 资源地址 @@ -355,7 +364,7 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback); if (bundle) { const asset = bundle.get(path); if (asset) { - this.releasePrefabtDepsRecursively(asset); + this.releasePrefabtDepsRecursively(bundleName, asset); } } } @@ -371,7 +380,7 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback); var infos = bundle.getDirWithPath(path); if (infos) { infos.map((info) => { - this.releasePrefabtDepsRecursively(info.uuid); + this.releasePrefabtDepsRecursively(bundleName, info.uuid); }); } @@ -381,18 +390,77 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback); } } + /** + * 获取资源路径 + * @param bundleName 资源包名 + * @param uuid 资源唯一编号 + * @returns + */ + getAssetPath(bundleName: string, uuid: string): string { + let b = this.getBundle(bundleName)!; + let info = b.getAssetInfo(uuid)!; + //@ts-ignore + return info.path; + } + /** 释放预制依赖资源 */ - private releasePrefabtDepsRecursively(uuid: string | Asset) { + private releasePrefabtDepsRecursively(bundleName: string, uuid: string | Asset) { if (uuid instanceof Asset) { uuid.decRef(); // assetManager.releaseAsset(uuid); + // this.debugLogReleasedAsset(bundleName, uuid); } else { const asset = assetManager.assets.get(uuid); if (asset) { asset.decRef(); // assetManager.releaseAsset(asset); + // this.debugLogReleasedAsset(bundleName, asset); + } + } + } + + private debugLogReleasedAsset(bundleName: string, asset: Asset) { + if (asset.refCount == 0) { + let path = this.getAssetPath(bundleName, asset.uuid); + let content: string = ""; + if (asset instanceof JsonAsset) { + content = "【释放资源】Json【路径】" + path; + } + else if (asset instanceof Prefab) { + content = "【释放资源】Prefab【路径】" + path; + } + else if (asset instanceof SpriteFrame) { + content = "【释放资源】SpriteFrame【路径】" + path; + } + else if (asset instanceof Texture2D) { + content = "【释放资源】Texture2D【路径】" + path; + } + else if (asset instanceof ImageAsset) { + content = "【释放资源】ImageAsset【路径】" + path; + } + else if (asset instanceof AudioClip) { + content = "【释放资源】AudioClip【路径】" + path; + } + else if (asset instanceof AnimationClip) { + content = "【释放资源】AnimationClip【路径】" + path; + } + else if (asset instanceof Font) { + content = "【释放资源】Font【路径】" + path; + } + else if (asset instanceof Material) { + content = "【释放资源】Material【路径】" + path; + } + else if (asset instanceof Mesh) { + content = "【释放资源】Mesh【路径】" + path; + } + else if (asset instanceof sp.SkeletonData) { + content = "【释放资源】Spine【路径】" + path; + } + else { + content = "【释放资源】未知【路径】" + path; } + console.log(content); } } @@ -480,7 +548,7 @@ oops.res.loadDir("game", onProgressCallback, onCompleteCallback); /** 打印缓存中所有资源信息 */ dump() { assetManager.assets.forEach((value: Asset, key: string) => { - console.log(assetManager.assets.get(key)); + console.log(`引用数量:${value.refCount}`, assetManager.assets.get(key)); }) console.log(`当前资源总数:${assetManager.assets.count}`); } diff --git a/assets/core/gui/layer/DelegateComponent.ts b/assets/core/gui/layer/DelegateComponent.ts index 43b6c5eb8aea33f9b254765f629c2a2a6225891e..bd2042048bf1b485955c4c26e2cdd990a8d401a9 100644 --- a/assets/core/gui/layer/DelegateComponent.ts +++ b/assets/core/gui/layer/DelegateComponent.ts @@ -19,6 +19,8 @@ const EventOnRemoved: string = "onRemoved"; export class DelegateComponent extends Component { /** 视图参数 */ vp: ViewParams = null!; + /** 关闭窗口之前 */ + onCloseWindowBefore: Function = null!; /** 界面关闭回调 - 包括关闭动画播放完(辅助框架内存业务流程使用) */ onCloseWindow: Function = null!; @@ -69,6 +71,10 @@ export class DelegateComponent extends Component { /** 窗口关闭前动画处理完后的回调方法,主要用于释放资源 */ private onBeforeRemoveNext(isDestroy?: boolean) { + if (this.onCloseWindowBefore) { + this.onCloseWindowBefore(); + this.onCloseWindowBefore = null!; + } this.removed(this.vp, isDestroy); } diff --git a/assets/core/utils/PlatformUtil.ts b/assets/core/utils/PlatformUtil.ts index 56be533c9cd729788968b807bbd8251727827be7..56cef59b70c1e9f9398648a270bc4dbc28c982dc 100644 --- a/assets/core/utils/PlatformUtil.ts +++ b/assets/core/utils/PlatformUtil.ts @@ -8,7 +8,7 @@ import { __private, native, sys } from "cc"; /** 平台数据 */ export class PlatformUtil { /** 获取当前设备的网络类型, 如果网络类型无法获取,默认将返回 `sys.NetworkType.LAN` */ - getNetworkType(): __private._pal_system_info_enum_type_network_type__NetworkType { + static getNetworkType(): __private._pal_system_info_enum_type_network_type__NetworkType { return sys.getNetworkType(); } @@ -16,17 +16,23 @@ export class PlatformUtil { * 获取当前设备的电池电量,如果电量无法获取,默认将返回 1 * @return - 0.0 ~ 1.0 */ - getBatteryLevel(): number { + static getBatteryLevel(): number { return sys.getBatteryLevel(); } /** 尝试打开一个 web 页面,并非在所有平台都有效 */ - openURL(url: string) { + static openURL(url: string) { sys.openURL(url); } /** 拷贝字符串到剪切板 */ - copyText(text: string) { - native.copyTextToClipboard(text); + static async copyText(text: string) { + if (sys.isNative) { + native.copyTextToClipboard(text); + + } + else { + await navigator.clipboard.writeText(text) + } } } diff --git a/assets/module/common/GameComponent.ts b/assets/module/common/GameComponent.ts index 2bb99bee71595c80ccd23def6e14dae05470c3fa..68f02c545eda62fbf35af8e0b249a632dd10f410 100644 --- a/assets/module/common/GameComponent.ts +++ b/assets/module/common/GameComponent.ts @@ -26,10 +26,13 @@ interface ResRecord { bundle: string, /** 资源路径 */ path: string, + /** 引用计数 */ + refCount: number, /** 资源编号 */ resId?: number } + /** * 游戏显示对象组件模板 * 1、当前对象加载的资源,会在对象释放时,自动释放引用的资源 @@ -148,8 +151,12 @@ export class GameComponent extends Component { for (let index = 0; index < paths.length; index++) { let realPath = paths[index]; let key = this.getResKey(realBundle, realPath, resId); - if (!rps.has(key)) { - rps.set(key, { path: realPath, bundle: realBundle, resId: resId }); + let rp = rps.get(key); + if (rp) { + rp.refCount++; + } + else { + rps.set(key, { path: realPath, bundle: realBundle, refCount: 1, resId: resId }); } } } @@ -157,16 +164,24 @@ export class GameComponent extends Component { let realBundle = bundleName; let realPath = paths; let key = this.getResKey(realBundle, realPath, resId); - if (!rps.has(key)) { - rps.set(key, { path: realPath, bundle: realBundle, resId: resId }); + let rp = rps.get(key); + if (rp) { + rp.refCount++; + } + else { + rps.set(key, { path: realPath, bundle: realBundle, refCount: 1, resId: resId }); } } else { let realBundle = oops.res.defaultBundleName; let realPath = bundleName; let key = this.getResKey(realBundle, realPath, resId); - if (!rps.has(key)) { - rps.set(key, { path: realPath, bundle: realBundle, resId: resId }); + let rp = rps.get(key); + if (rp) { + rp.refCount++; + } + else { + rps.set(key, { path: realPath, bundle: realBundle, refCount: 1, resId: resId }); } } } @@ -265,7 +280,9 @@ export class GameComponent extends Component { const rps = this.resPaths.get(ResType.Load); if (rps) { rps.forEach((value: ResRecord) => { - oops.res.release(value.path, value.bundle); + for (let i = 0; i < value.refCount; i++) { + oops.res.release(value.path, value.bundle); + } }); rps.clear(); } @@ -290,7 +307,7 @@ export class GameComponent extends Component { const rps = this.resPaths.get(ResType.Audio); if (rps) { rps.forEach((value: ResRecord) => { - oops.audio.putEffect(value.resId!, value.path, value.bundle); + oops.audio.putEffect(value.resId!, value.path, value.bundle); // 回收音乐效到音效池中等下次使用 }); } } @@ -313,6 +330,7 @@ export class GameComponent extends Component { } return; } + spriteFrame.addRef(); target.spriteFrame = spriteFrame; } //#endregion @@ -346,16 +364,16 @@ export class GameComponent extends Component { */ async playEffect(url: string, bundleName?: string) { if (bundleName == null) bundleName = oops.res.defaultBundleName; - await oops.audio.playEffect(url, bundleName, () => { + let resId = await oops.audio.playEffect(url, bundleName, () => { if (!this.isValid) return; - + const rps = this.resPaths.get(ResType.Audio); if (rps) { const key = this.getResKey(bundleName, url); rps.delete(key); } }); - this.addPathToRecord(ResType.Audio, bundleName, url); + this.addPathToRecord(ResType.Audio, bundleName, url, resId); } //#endregion diff --git a/assets/module/common/ModuleUtil.ts b/assets/module/common/ModuleUtil.ts index 43b4d38a80942da543daa5e58c1fc3f615a1ba00..3ec7dae347577d9480d1547b1d2131992d4ccd9c 100644 --- a/assets/module/common/ModuleUtil.ts +++ b/assets/module/common/ModuleUtil.ts @@ -2,6 +2,7 @@ import { Node, __private } from "cc"; import { oops } from "../../core/Oops"; import { resLoader } from "../../core/common/loader/ResLoader"; import { UICallbacks } from "../../core/gui/layer/Defines"; +import { DelegateComponent } from "../../core/gui/layer/DelegateComponent"; import { ViewUtil } from "../../core/utils/ViewUtil"; import { ecs } from "../../libs/ecs/ECS"; import { CompType } from "../../libs/ecs/ECSModel"; @@ -81,13 +82,43 @@ export class ModuleUtil { /** * 业务实体上移除界面组件 - * @param ent 模块实体 - * @param ctor 界面逻辑组件 - * @param uiId 界面资源编号 - * @param isDestroy 是否释放界面缓存(默认为释放界面缓存) + * @param ent 模块实体 + * @param ctor 界面逻辑组件 + * @param uiId 界面资源编号 + * @param isDestroy 是否释放界面缓存(默认为释放界面缓存) + * @param onRemoved 窗口关闭完成事件 */ - static removeViewUi(ent: ecs.Entity, ctor: CompType, uiId: number, isDestroy: boolean = true) { - if (isDestroy) ent.remove(ctor, isDestroy); + static removeViewUi(ent: ecs.Entity, ctor: CompType, uiId: number, isDestroy: boolean = true, onRemoved?: Function) { + const node = oops.gui.get(uiId); + if (!node) { + if (onRemoved) onRemoved(); + return; + } + + const comp = node.getComponent(DelegateComponent); + if (comp) { + if (comp.vp.callbacks.onBeforeRemove) { + comp.onCloseWindowBefore = () => { + ent.remove(ctor, isDestroy); + if (onRemoved) onRemoved(); + }; + } + else if (comp.vp.callbacks.onRemoved) { + comp.onCloseWindow = () => { + ent.remove(ctor, isDestroy); + if (onRemoved) onRemoved(); + }; + } + else { + ent.remove(ctor, isDestroy); + if (onRemoved) onRemoved(); + } + } + else { + ent.remove(ctor, isDestroy); + if (onRemoved) onRemoved(); + } + oops.gui.remove(uiId, isDestroy); } } \ No newline at end of file