You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
8.0 KiB
224 lines
8.0 KiB
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<string, unknown>) => void;
|
|
/** @internal 同一轮宝箱打开是否已上报过成功/失败,避免 init_error 与 panel_not_visible 重复上报 */
|
|
_skillSelectResultReported?: boolean;
|
|
} |