/** @format */ import {Data, Mgr} from '../GameControl' import {Log} from '../utils/LogUtils' import {ccUtils} from '../utils/ccUtils' import {GAME_TYPE} from '../enums/Enum' import Bundle = cc.AssetManager.Bundle import {UI} from '../enums/UI' /** * BaseUI界面基础类 * * 1. 快速关闭与屏蔽点击的选项配置 * 2. 界面缓存设置(开启后界面关闭不会被释放,以便下次快速打开) * 3. 界面显示类型配置 * * 4. 加载资源接口(随界面释放自动释放),this.loadRes(xxx) * 5. 由UIManager释放 * * 5. 界面初始化回调(只调用一次) * 6. 界面打开回调(每次打开回调) * 7. 界面打开动画播放结束回调(动画播放完回调) * 8. 界面关闭回调 * 9. 界面置顶回调 * */ const {ccclass, property} = cc._decorator /** 界面展示类型 */ export enum UIShowTypes { UISingle, // 单界面显示 UIAddition = 500, // 叠加显示 UITop = 1000, // 最上层显示,消息弹窗,悬浮提示等UI } @ccclass export class BaseUI extends cc.Component { /** 快速关闭 */ @property() quickClose: boolean = false /** 屏蔽点击选项 在UIConf设置屏蔽点击*/ // @property // preventTouch: boolean = true; /** 缓存选项 */ @property() cache: boolean = false /** 界面显示类型 */ @property({type: cc.Enum(UIShowTypes)}) showType: UIShowTypes = UIShowTypes.UISingle /** 阻止点击事件穿透 */ @property() prevent: boolean = true /** 界面id */ public uiID: number = 0 /** 通过全局预制体加载的节点 */ public itemsNodes: cc.Node[] = [] private loadAsset: any[] = [] private nodeLoadingMap: Map = new Map() private nodeLoadInfoMap: Map = new Map() /********************** UI的回调 ***********************/ /** * 当界面被创建时回调,生命周期内只调用 * @param args 可变参数 */ public init(...args): void {} /** * 当界面被打开时回调,每次调用Open时回调 * @param args 可变参数 * @param fromUI 从哪个UI打开的 */ public onShow(args, fromUI: number): void {} /** * 每次界面Open动画播放完毕时回调 */ public onOpenAniOver(): void {} /** * 当界面被关闭时回调,每次调用Close时回调 * 返回值会传递给下一个界面 */ public onHide(): any {} /** * 当界面被置顶时回调,Open时并不会回调该函数 * @param preID 前一个ui * @param args 可变参数, */ public onTop(preID: number, ...args): void {} /** * 隐藏当前界面 */ public hide(): void { Mgr.ui.hideBaseUI(this) } /** * 动态加载加入引用计数 * @param bundle * @param url * @param node * @param childUrl */ public load( bundle: cc.AssetManager.Bundle, url: string, type: typeof cc.Asset, onComplete?: (error: Error, assets: T) => void, node?: cc.Node, childUrl?: string, addRef?: boolean, notRecordRef?: boolean, ) { if ((node && !cc.isValid(node)) || !bundle) { cc.warn( 'BaseUI.load node is not Valid:', (node && !cc.isValid(node)) == true, 'bundle is Valid:', cc.isValid(bundle), ) return } if (childUrl) { const child = cc.find(childUrl, node) if (child) { node = cc.find(childUrl, node) } } let sprite if (node) { sprite = node.getComponent(cc.Sprite) } if (sprite && sprite.spriteFrame && !notRecordRef) { let index = this.loadAsset.findIndex(value => value.asset == sprite.spriteFrame) if (index >= 0) { let data = this.loadAsset[index] if (data.sprite == sprite && data.url == bundle.name + url) { //相同的sprite重复加载了同一个资源,return return } if (sprite.addRefTag) { sprite.spriteFrame = null let same = this.loadAsset.splice(index, 1) same[0].asset.decRef() if (same[0].asset.refCount < 0) { console.error('refCount < 0', same[0].asset) } } //console.log(same[0].name, '--->uuid:', same[0]['_uuid'], '-->已在预制体加载计数-1') } } let onCompleteFuc = (err, asset: T) => { if (!err && this.node.activeInHierarchy) { let data: any = {asset: asset, url: bundle.name + url, addRef, decRef: false} if (type == cc.SpriteFrame) { if (!sprite) console.error('sprite is null', 'node.name-->', node.name, 'childUrl-->', childUrl) sprite.spriteFrame = asset data.sprite = sprite sprite.addRefTag = addRef } if (type == cc.AnimationClip) { data.animation = node.getComponent(cc.Animation) } if (!notRecordRef) { if (addRef) { asset.addRef() data.decRef = url.includes(UI[this.uiID]) // console.log( // asset.name, // '--->uuid:', // asset['_uuid'], // '-->计数+1', // '-->isValid:', // asset.isValid, // asset, // ) } else { if (type == cc.SpriteFrame) { let index = this.loadAsset.findIndex(value => value.asset == asset && !value.addRef) if (index == -1) { // console.log( // asset.name, // '--->uuid:', // asset['_uuid'], // '-->计数只为1', // '-->isValid:', // asset.isValid, // asset, // ) asset.addRef() data.decRef = url.includes(UI[this.uiID]) } } } this.loadAsset.push(data) } } else if (err) { //console.log(err) } onComplete && onComplete(err, asset) let arr = this.nodeLoadInfoMap.get(node) if (arr && arr.length > 0) { let info = arr.shift() bundle.load(info.url, info.type, info.onCompleteFuc) } else { this.nodeLoadingMap.set(node, false) } } if (node) { let isLoading = this.nodeLoadingMap.get(node) if (!isLoading) { this.nodeLoadingMap.set(node, true) // 等待当前资源释放帧结束,不然会有概率加载重复资源时完成时间小于资源释放,导致被提前释放 this.scheduleOnce(() => { bundle.load(url, type, onCompleteFuc) }) } else { let arr = this.nodeLoadInfoMap.get(node) if (arr) { arr.push({url, type, onCompleteFuc}) } else { arr = [{url, type, onCompleteFuc}] this.nodeLoadInfoMap.set(node, arr) } } } else { bundle.load(url, type, onCompleteFuc) } } public loadTexImg(url: string, node?: cc.Node, childUrl?: string, addRef?: boolean) { this.load(Data.main.texBundle, url, cc.SpriteFrame, null, node, childUrl, addRef) } public loadNotRefTexImg(url: string, node?: cc.Node, childUrl?: string) { this.load(Data.main.texBundle, url, cc.SpriteFrame, null, node, childUrl, false, true) } //同步加载,不计入引用计数 public loadSync(bundle: cc.AssetManager.Bundle, url: string, type: typeof cc.Asset) { return new Promise((resolve: (asset: T) => void, reject) => { bundle.load(url, type, (err, asset: T) => { if (err) { resolve(null) } else { resolve(asset) } }) }) } /** * 减去当前UI生命周期内动态加载的资源引用 */ public decAllRef(): void { for (let i = this.itemsNodes.length - 1; i > 0; i--) { let node = this.itemsNodes.splice(i, 1) if (node[0]['uiID'] == this.uiID) { Data.main.itemsPoolMap.get(node[0].name)?.put(node[0]) } } this.loadAsset.forEach(value => { if (value.decRef) { value.asset.decRef() } if (value.sprite) { value.sprite.spriteFrame = null } if (value.animation) { let clips = value.animation.getClips() if (clips) { clips.forEach(clip => { if (clip.name == value.asset.name) value.animation.removeClip(clip, true) }) } } // console.log( // value.asset.name, // '--->uuid:', // value.asset['_uuid'], // '--->refCount:', // value.asset.refCount, // '-->尝试释放', // value, // ) }) this.loadAsset = [] this.nodeLoadInfoMap.clear() this.nodeLoadingMap.clear() } creatAniNode(parent: cc.Node, bundle: Bundle, url: string, isDestroy?: boolean, finishFuc?: Function): cc.Node { let node = new cc.Node() node.name = url.substring(url.lastIndexOf('/') + 1) node.addComponent(cc.Sprite) node.parent = parent let ani = node.addComponent(cc.Animation) this.load( bundle, url, cc.AnimationClip, (err, animationClip: cc.AnimationClip) => { if (!err && this.node.activeInHierarchy) { ani.addClip(animationClip) ccUtils.playAni(animationClip.name, 0, node) ani.once('finished', () => { finishFuc && finishFuc() if (isDestroy) node.destroy() }) } }, node, ) return node } }