import { _decorator, Asset, assetManager, AssetManager, Component, easing, instantiate, Node, NodePool, Prefab, sp, SpriteFrame, Tween, tween, UIOpacity, v3, Vec3, VideoPlayer } from 'cc'; import { IUIConfig, UIInfo } from './UIManager'; import { ResPakage } from '../res/ResourcesManager'; const { ccclass, property } = _decorator; @ccclass('UIBase') export class UIBase extends Component { getResPkg(): ResPakage { return null; } onLoad(): void { } uiInfo: UIInfo = null; prema: any; private _onOpenAnimEnd: Function; private _onClose: Function; private _showLoading: number = 0; /** 底部波次宝箱打开 SkillSelect 时启用追踪日志 */ protected _skillSelectTreasureTrace = false; /** * 通过UI管理器打开面板时才会触发onAdded * @param args */ onAdded(args: OpenParam) { this.prema = args?.data; this._onOpenAnimEnd = args?.onOpenAnimEnd; this._onClose = args?.onClose; this._skillSelectTreasureTrace = !!(args?.onSkillSelectOpenSuccess || args?.onSkillSelectOpenFail); if (args?.showLoading !== undefined && args?.showLoading !== null) this._showLoading = args?.showLoading; let resPkg = this.getResPkg(); if (resPkg) { if (this._showLoading > 0) { gg.game.hideLoading(); } gg.res.loadResPakg(resPkg, (c, t) => { }, () => { // 分包/资源异步返回时用户可能已切走并销毁本面板,再调 onInit 会空引用(如 UIpageUserLevelup.refreshUI) if (!this.isValid || !this.node?.isValid) { if (this._showLoading > 0) { gg.game.hideLoading(); } return; } this.onInit(); if (this._showLoading > 0) { gg.game.hideLoading(); } }); } else { this.onInit(); } } onInit() { } /** * 预制的一个打开效果,子类需要时可直接调用 * @param callback */ showOpenCenterEff(callback?: Function) { let center = this.node.getChildByName('center') || this.node.getChildByName('ui_center'); if (center) { Tween.stopAllByTarget(center); tween(center) .set({ scale: Vec3.ZERO }) .to(0.3, { scale: Vec3.ONE }, { easing: easing.backOut }) .call(() => { if (callback) callback(); if (this._onOpenAnimEnd) this._onOpenAnimEnd(); }).start(); } } showCloseCenterEff(type = 1, callback?: Function) { let center = this.node.getChildByName('center') || this.node.getChildByName('ui_center'); if (center) { if (type == 1) { tween(center).to(0.2, { scale: Vec3.ZERO }).set({ scale: Vec3.ONE }).call(() => { if (callback) callback(); }).start(); } else if (type == 2) { tween(center).to(0.2, { opacity: 0 }).set({ opacity: 255 }).call(() => { if (callback) callback(); }).start(); } else { if (callback) callback(); } } return center != null; } /** * 显示打开效果 * @param type 1 整个页面渐显 2 只渐显黑色半透明背景遮罩 */ showOpenMaskEff(type = 1) { if (type == 1) { let o = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity) tween(o).set({ opacity: 0 }).to(0.3, { opacity: 255 }).start(); } else { let m = this.node.getChildByName('bgMask') || this.node.getChildByName('ui_bgMask') || this.node.getChildByName('ui_mask'); if (m) { let o = m.getComponent(UIOpacity) || m.addComponent(UIOpacity) tween(o).set({ opacity: 0 }).to(0.3, { opacity: 180 }).start(); } } } closeMaskEff(callback?: Function) { let m = this.node.getChildByName('bgMask') || this.node.getChildByName('ui_bgMask') || this.node.getChildByName('ui_mask'); if (m) { let o = m.getComponent(UIOpacity) || m.addComponent(UIOpacity) tween(o).to(0.2, { opacity: 0 }).call(() => { if (callback) callback(); }).start(); } else { if (callback) callback(); } return m != null; } /** * 关闭面板 * @param tweenType 1: 直接关闭 2: 弹窗缩回,同时背景渐变成透明 3: 不缩放,直接渐变到透明 * @returns */ async close(tweenType = 1) { await gg.ui.closePanel(this.uiInfo.id, tweenType); } /** 销毁前停止子树 tween、Spine,减轻与渲染/合批并发时的异常(快速切页、SpriteFrame/getGFXTexture、spine wasm) */ private prepareDestroyForRender(): void { if (!this.node?.isValid) return; const stopTweenDeep = (n: Node) => { if (!n?.isValid) return; Tween.stopAllByTarget(n); for (const c of n.children) { stopTweenDeep(c); } }; stopTweenDeep(this.node); const sks = this.node.getComponentsInChildren(sp.Skeleton); for (const sk of sks) { if (sk?.isValid) { try { // 缓存模式(SHARED_CACHE / PRIVATE_CACHE)下引擎禁止 clearTracks,会打 debug 警告 const skAny = sk as sp.Skeleton & { isAnimationCached?: () => boolean }; if (typeof skAny.isAnimationCached !== 'function' || !skAny.isAnimationCached()) { sk.clearTracks(); } sk.setCompleteListener(null); } catch (_) { /* wasm 已释放等 */ } } } } destroyPanel(tweenType = 1) { return new Promise((resolve, reject) => { let fun = () => { this.prepareDestroyForRender(); if (this.node.isValid) { this.node.destroy(); } // ResManager.Ins.releaseRes(this.uiconf.bundle, this.uiconf.path, this.uiconf.name); resolve(true); if (this._onClose) this._onClose(); } let isClose = false; if (tweenType == 2) { this.closeMaskEff(); isClose = this.showCloseCenterEff(1, fun.bind(this)); if (isClose) return; } if (tweenType == 3) { isClose = this.showCloseCenterEff(2, fun.bind(this)); if (isClose) return; else { tween(this.node).to(0.2, { opacity: 0 }).call(fun.bind(this)).start(); return } } fun(); }) } protected onDestroy(): void { let resPkg = this.getResPkg(); if (resPkg) { gg.res.clearResPkg(resPkg.name); } // Break references early to help GC and avoid accidental retention when panels are pooled/cached. this.prema = null; this._onOpenAnimEnd = null; this._onClose = null; this.uiInfo = null; } } /** * 打开面板参数 */ export interface OpenParam { onOpenAnimEnd?: Function; onClose?: Function; data?: any; showLoading?: number; /** 仅底部波次宝箱 SkillSelect:load + onInit 成功后回调 */ onSkillSelectOpenSuccess?: () => void; /** 仅底部波次宝箱 SkillSelect:打开失败回调(detail 为错误摘要;context 含三选一 id 与本局武器/技能) */ onSkillSelectOpenFail?: (reason: string, detail?: string, context?: Record) => void; /** @internal 同一轮宝箱打开是否已上报过成功/失败,避免 init_error 与 panel_not_visible 重复上报 */ _skillSelectResultReported?: boolean; }