Merge pull request !18 from dgflash/develop
This commit is contained in:
dgflash
2025-09-03 02:11:42 +00:00
committed by Gitee
21 changed files with 565 additions and 436 deletions

View File

@@ -4,12 +4,21 @@
* @LastEditors: dgflash
* @LastEditTime: 2022-09-02 10:22:36
*/
import { AudioSource, _decorator } from 'cc';
import { AudioClip, AudioSource, _decorator } from 'cc';
import { IAudioParams } from './IAudio';
const { ccclass } = _decorator;
/** 游戏音效 */
@ccclass('AudioEffect')
export class AudioEffect extends AudioSource {
/** 唯一编号 */
key: string = null!;
/** 音效编号 */
aeid: number = -1;
/** 音效果资源路径 */
path: string | AudioClip = null!
/** 音效参数 */
params: IAudioParams = null!
/** 背景音乐播放完成回调 */
onComplete: Function | null = null;
@@ -18,6 +27,13 @@ export class AudioEffect extends AudioSource {
}
private onAudioEnded() {
this.onComplete && this.onComplete();
this.onComplete && this.onComplete(this);
}
reset() {
this.stop();
this.clip = null;
this.path = null!;
this.params = null!;
}
}

View File

@@ -2,41 +2,24 @@ import { AudioClip, Node, NodePool } from "cc";
import { oops } from "../../Oops";
import { resLoader } from "../loader/ResLoader";
import { AudioEffect } from "./AudioEffect";
import { IAudioParams } from "./IAudio";
import { AudioEffectType } from "./AudioEnum";
import { IAudioData, IAudioParams } from "./IAudio";
/** 音乐效缓冲编号最大值 */
const AE_ID_MAX = 30000;
/** 音效池 */
export class AudioEffectPool {
private _switch: boolean = true;
/** 音效开关 */
get switch(): boolean {
return this._switch;
}
set switch(value: boolean) {
this._switch = value;
if (value) this.stop();
}
private _volume: number = 1;
/** 所有音效音量 */
get volume(): number {
return this._volume;
}
set volume(value: number) {
this._volume = value;
this.effects.forEach(ae => {
ae.volume = value;
});
}
/** 音效播放器对象池 */
/** 音效配置数据 */
private data: { [node: string]: IAudioData } = null!;
/** 音效播放器节点对象池 */
private pool: NodePool = new NodePool();
/** 对象池集合 */
private effects: Map<string, AudioEffect> = new Map();
/** 用过的音资源记录 */
private res: Map<string, string[]> = new Map();
/** 记录项目资源库中使用过的音资源 */
private res_project: Map<string, string[]> = new Map();
/** 外网远程资源记录(地址、音效对象) */
private res_remote: Map<string, AudioClip> = new Map();
private _aeId: number = 0;
/** 获取请求唯一编号 */
@@ -46,80 +29,149 @@ export class AudioEffectPool {
return this._aeId;
}
/**
* 注册音效类型
* @param type
*/
register(type: string) {
this.data[type] = { switch: true, volume: 1 };
}
/**
* 音效开关
* @param type 音效类型
* @returns 音效开关
*/
getSwitch(type: string = AudioEffectType.Effect) {
let iad = this.data[type];
if (iad == null) console.error(`类型为【${type}】的音效配置不存在`);
return iad.switch;
}
/**
* 音效音量设置
* @param type 音效类型
* @param value 音效开关
*/
setSwitch(value: boolean, type: string = AudioEffectType.Effect) {
let iad = this.data[type];
if (iad == null) console.error(`类型为【${type}】的音效配置不存在`);
iad.switch = value;
if (!value) this.stop();
}
/**
* 音效音量获取
* @param type 音效类型
* @returns 音效音量
*/
getVolume(type: string = AudioEffectType.Effect) {
let iad = this.data[type];
if (iad == null) console.error(`类型为【${type}】的音效配置不存在`);
return iad.volume;
}
/**
* 音效音量设置
* @param value 音效音量
* @param type 音效类型
*/
setVolume(value: number, type: string = AudioEffectType.Effect) {
let iad = this.data[type];
if (iad == null) console.error(`类型为【${type}】的音效配置不存在`);
iad.volume = value;
this.effects.forEach(ac => ac.volume = value);
}
/**
* 加载与播放音效
* @param url 音效资源地址与音效资源
* @param bundleName 资源包名
* @param onPlayComplete 播放完成回调
* @param path 音效资源地址与音效资源
* @param params 音效附加参数
* @returns
*/
async loadAndPlay(url: string | AudioClip, params?: IAudioParams): Promise<number> {
async loadAndPlay(path: string | AudioClip, params?: IAudioParams): Promise<AudioEffect> {
return new Promise(async (resolve, reject) => {
if (!this.switch) return resolve(-1);
let bundleName = resLoader.defaultBundleName;
let loop = false;
let volume = this.volume;
let onPlayComplete: Function = null!;
if (params) {
if (params.bundle != null) bundleName = params.bundle;
if (params.loop != null) loop = params.loop;
if (params.volume != null) volume = params.volume;
if (params.onPlayComplete != null) onPlayComplete = params.onPlayComplete;
}
// 创建音效资源
let clip: AudioClip;
if (url instanceof AudioClip) {
clip = url;
if (params == null) {
params = {
type: AudioEffectType.Effect,
bundle: resLoader.defaultBundleName,
loop: false,
destroy: false
}
}
else {
clip = resLoader.get(url, AudioClip, bundleName)!;
if (params.type == null) params.type = AudioEffectType.Effect;
if (params.bundle == null) params.bundle = resLoader.defaultBundleName;
if (params.loop == null) params.loop = false;
if (params.type == null) params.destroy = false;
}
// 加载音效资源
let iad = this.data[params.type!];
if (iad == null) console.error(`类型为【${params.type!}】的音效配置不存在`);
if (!iad.switch) {
resolve(null!);
return;
}
if (params.volume == null) params.volume = iad.volume;
let bundle = params.bundle!;
let key: string = null!;
let clip: AudioClip | undefined;
// 通过预制自动加载的音效资源(音效内存跟随预制体的内存一并释放)
if (path instanceof AudioClip) {
key = `${params.type}_${path.uuid}`;
clip = path;
}
// 非引擎管理的远程资源加载
else if (path.indexOf("http") == 0) {
key = `${params.type}_${path}`;
clip = this.res_remote.get(path);
if (clip == null) {
let urls = this.res.get(bundleName);
if (urls == null) {
urls = [];
this.res.set(bundleName, urls);
urls.push(url);
const extension = path.split('.').pop();
clip = await resLoader.loadRemote<AudioClip>(path, { ext: `.${extension}` });
this.res_remote.set(path, clip);
}
}
// 资源加载
else {
key = `${params.type}_${bundle}_${path}`;
clip = resLoader.get(path, AudioClip, bundle)!;
// 加载音效资源 - 如果一个预制上加载了了音乐同一个音乐资源,此处不会记录音乐资源路径数据,资源内存由预制释放时一起释放
if (clip == null) {
let paths = this.res_project.get(bundle);
if (paths == null) {
paths = [];
this.res_project.set(bundle, paths);
}
else if (urls.indexOf(url) == -1) {
urls.push(url);
}
clip = await resLoader.loadAsync(bundleName, url, AudioClip);
if (paths.indexOf(path) == -1) paths.push(path);
clip = await resLoader.loadAsync(bundle, path, AudioClip);
}
}
// 资源已被释放
if (!clip.isValid) {
resolve(-1);
console.warn(`音效资源【${key}】已被释放`);
resolve(null!);
return;
}
let aeid = this.getAeId();
let key: string;
if (url instanceof AudioClip) {
key = url.uuid;
}
else {
key = `${bundleName}_${url}`;
}
key += "_" + aeid;
// 获取音效果播放器播放音乐
let aeid: number = -1;
let ae: AudioEffect;
let node: Node = null!;
if (this.pool.size() == 0) {
node = new Node();
node.name = "AudioEffect";
node.parent = oops.audio.node;
ae = node.addComponent(AudioEffect)!;
ae.onComplete = () => {
this.put(aeid, url, bundleName); // 播放完回收对象
onPlayComplete && onPlayComplete(aeid, url, bundleName);
// console.log(`【音效】回收,池中剩余音效播放器【${this.pool.size()}】`);
};
aeid = this.getAeId();
key += "_" + aeid;
node = new Node("AudioEffect");
ae = node.addComponent(AudioEffect);
ae.key = key;
ae.aeid = aeid;
ae.onComplete = this.onAudioEffectPlayComplete.bind(this);
}
else {
node = this.pool.get()!;
@@ -127,39 +179,46 @@ export class AudioEffectPool {
}
// 记录正在播放的音效播放器
this.effects.set(key, ae);
this.effects.set(ae.key, ae);
ae.loop = loop;
node.parent = oops.audio.node;
ae.path = path;
ae.params = params;
ae.loop = params.loop!;
ae.volume = params.volume!;
ae.clip = clip;
ae.volume = volume;
ae.currentTime = 0;
ae.play();
resolve(aeid);
resolve(ae);
});
}
/** 音效播放完成 */
private onAudioEffectPlayComplete(ae: AudioEffect) {
if (ae.params.destroy) {
if (ae.path instanceof AudioClip) {
ae.path.decRef();
}
else {
resLoader.release(ae.path, ae.params!.bundle);
}
}
ae.params && ae.params.onPlayComplete && ae.params.onPlayComplete(ae);
this.put(ae);
// console.log(`【音效】回收,池中剩余音效播放器【${this.pool.size()}】`);
}
/**
* 回收音效播放器
* @param aeid 播放器编号
* @param url 音效路径
* @param bundleName 资源包名
* @param ae loadAndPlay 方法返回的音效播放器对象
*/
put(aeid: number, url: string | AudioClip, bundleName: string = resLoader.defaultBundleName) {
let key: string;
if (url instanceof AudioClip) {
key = url.uuid;
}
else {
key = `${bundleName}_${url}`;
}
key += "_" + aeid;
put(ae: AudioEffect) {
let effect = this.effects.get(ae.key);
if (effect && effect.clip) {
effect.reset();
let ae = this.effects.get(key);
if (ae && ae.clip) {
this.effects.delete(key);
ae.stop();
this.pool.put(ae.node);
this.effects.delete(ae.key);
this.pool.put(effect.node);
}
}
@@ -167,41 +226,58 @@ export class AudioEffectPool {
stop() {
this.effects.forEach(ae => {
ae.stop();
this.onAudioEffectPlayComplete(ae);
});
this.effects.clear();
}
/** 恢复所有音效 */
play() {
if (!this.switch) return;
this.effects.forEach(ae => {
ae.play();
});
this.effects.forEach(ae => ae.play());
}
/** 暂停所有音效 */
pause() {
if (!this.switch) return;
this.effects.forEach(ae => {
ae.pause();
this.onAudioEffectPlayComplete(ae);
});
this.effects.clear();
}
/** 释放所有音效资源与对象池中播放器 */
release() {
// 释放正在播放的音效
this.effects.forEach(ae => {
ae.node.destroy();
});
this.effects.clear();
// 释放池中音乐播放器
this.releasePool();
// 释放音效资源
this.res.forEach((urls: string[], bundleName: string) => {
urls.forEach(url => resLoader.release(url, bundleName));
});
// 释放各个资源包中的音效资源
this.releaseRes();
// 释放池中播放器
// 释放外网远程音效资源
this.releaseResRemote();
}
/** 释放池中音乐播放器 */
releasePool() {
this.pool.clear();
// 释放正在播放的音效对象
this.effects.forEach(ae => ae.node.destroy());
this.effects.clear();
}
/** 释放各个资源包中的音效资源 */
releaseRes() {
this.res_project.forEach((paths: string[], bundleName: string) => {
paths.forEach(path => resLoader.release(path, bundleName));
});
}
/** 释放外网远程音效资源 */
releaseResRemote() {
this.res_remote.forEach((clip: AudioClip, path: string) => {
clip.decRef();
});
this.res_remote.clear();
}
}

View File

@@ -0,0 +1,7 @@
/** 音乐音效默认类型 */
export enum AudioEffectType {
/** 背景音乐 */
Music = "music",
/** 音乐音效 */
Effect = "effect",
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "68e8e4a0-93f5-4606-9c42-06b1daff6d1e",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,8 +1,10 @@
import { AudioClip, Component } from "cc";
import { oops } from "../../Oops";
import { AudioEffect } from "./AudioEffect";
import { AudioEffectPool } from "./AudioEffectPool";
import { AudioEffectType } from "./AudioEnum";
import { AudioMusic } from "./AudioMusic";
import { IAudioParams } from "./IAudio";
import { IAudioData, IAudioParams } from "./IAudio";
const LOCAL_STORE_KEY = "game_audio";
@@ -20,31 +22,39 @@ export class AudioManager extends Component {
effect: AudioEffectPool = new AudioEffectPool();
/** 音乐管理状态数据 */
private localData: any = {};
private data: { [node: string]: IAudioData } = {};
/**
* 播放背景音乐
* @param path 资源路径
* @param params 音效参数
*/
playMusic(path: string, params?: IAudioParams) {
this.music.loadAndPlay(path, params);
}
/**
* 播放音效
* @param url 资源地址
* @param params 音效参数
* @param path 资源路径
* @param params 音效参数
*/
playEffect(url: string | AudioClip, params?: IAudioParams): Promise<number> {
return this.effect.loadAndPlay(url, params);
playEffect(path: string | AudioClip, params?: IAudioParams): Promise<AudioEffect> {
return this.effect.loadAndPlay(path, params);
}
/** 回收音效播放器 */
putEffect(aeid: number, url: string | AudioClip, bundleName?: string) {
this.effect.put(aeid, url, bundleName);
putEffect(ae: AudioEffect) {
this.effect.put(ae);
}
/** 恢复当前暂停的音乐与音效播放 */
resumeAll() {
if (!this.music.playing && this.music.progress > 0) this.music.play();
this.effect.play();
this.music.resume();
}
/** 暂停当前音乐与音效的播放 */
pauseAll() {
if (this.music.playing) this.music.pause();
this.music.pause();
this.effect.pause();
}
@@ -56,26 +66,17 @@ export class AudioManager extends Component {
/** 保存音乐音效的音量、开关配置数据到本地 */
save() {
this.localData.volume_music = this.music.volume;
this.localData.volume_effect = this.effect.volume;
this.localData.switch_music = this.music.switch;
this.localData.switch_effect = this.effect.switch;
oops.storage.set(LOCAL_STORE_KEY, this.localData);
oops.storage.set(LOCAL_STORE_KEY, this.data);
}
/** 本地加载音乐音效的音量、开关配置数据并设置到游戏中 */
load() {
this.music = this.getComponent(AudioMusic) || this.addComponent(AudioMusic)!;
this.music = new AudioMusic();
this.music.parent = this.node;
this.localData = oops.storage.getJson(LOCAL_STORE_KEY);
if (this.localData) {
try {
this.setState();
}
catch {
this.setStateDefault();
}
this.data = oops.storage.getJson(LOCAL_STORE_KEY);
if (this.data) {
this.setState();
}
else {
this.setStateDefault();
@@ -83,17 +84,36 @@ export class AudioManager extends Component {
}
private setState() {
this.music.volume = this.localData.volume_music;
this.effect.volume = this.localData.volume_effect;
this.music.switch = this.localData.switch_music;
this.effect.switch = this.localData.switch_effect;
//@ts-ignore
this.music.data = this.data;
//@ts-ignore
this.effect.data = this.data;
}
/** 默认音乐配置数据 */
private setStateDefault() {
this.localData = {};
this.music.volume = 1;
this.effect.volume = 1;
this.music.switch = true;
this.effect.switch = true;
this.data = {};
for (const key in AudioEffectType) {
//@ts-ignore
const value = AudioEffectType[key];
if (typeof value === 'string') {
this.data[value] = { switch: true, volume: 1 };
switch (value) {
case AudioEffectType.Music:
//@ts-ignore
this.music.data = this.data;
this.music.setSwitch(true);
this.music.setVolume(1);
break;
default:
//@ts-ignore
this.effect.data = this.data;
this.effect.setSwitch(true, value);
this.effect.setVolume(1, value);
break;
}
}
}
this.save();
}
}

View File

@@ -4,38 +4,64 @@
* @LastEditors: dgflash
* @LastEditTime: 2023-05-16 09:11:30
*/
import { AudioClip, AudioSource, _decorator } from 'cc';
import { AudioClip, Node } from 'cc';
import { resLoader } from '../loader/ResLoader';
import { IAudioParams } from './IAudio';
const { ccclass } = _decorator;
import { AudioEffect } from './AudioEffect';
import { AudioEffectType } from './AudioEnum';
import { IAudioData, IAudioParams } from './IAudio';
/**
* 背景音乐
* 1、播放一个新背景音乐时先加载音乐资源然后停止正在播放的背景资源同时放当前背景音乐资源,最后播放新的背景音乐
* 1、播放一个新背景音乐时先加载音乐资源然后停止正在播放的背景资源同时放当前背景音乐资源,最后播放新的背景音乐
* 2、背景音乐循环播放时不会触发播放完成事件
*/
@ccclass('AudioMusic')
export class AudioMusic extends AudioSource {
export class AudioMusic extends Node {
/** 音效配置数据 */
private data: { [node: string]: IAudioData } = null!;
private _progress: number = 0;
private _isLoading: boolean = false;
private _nextUrl: string = null!;
private _nextPath: string = null!;
private _nextParams: IAudioParams = null!;
private _params: IAudioParams = null!;
private _ae: AudioEffect = null!;
/** 背景音乐开关 */
private _switch: boolean = true;
get switch(): boolean {
return this._switch;
/**
* 音效开关
* @param type 音效类型
* @returns 音效开关
*/
getSwitch() {
return this.data[AudioEffectType.Music].switch;
}
set switch(value: boolean) {
this._switch = value;
/**
* 音效音量设置
* @param type 音效类型
* @param value 音效开关
*/
setSwitch(value: boolean) {
this.data[AudioEffectType.Music].switch = value;
if (!value) this.stop();
}
/**
* 音效音量获取
* @param type 音效类型
* @returns 音效音量
*/
getVolume(): number {
return this.data[AudioEffectType.Music].volume;
}
/**
* 音效音量设置
* @param value 音效音量
*/
setVolume(value: number) {
this.data[AudioEffectType.Music].volume = value;
}
/** 获取音乐播放进度 */
get progress(): number {
if (this.duration > 0)
this._progress = this.currentTime / this.duration;
if (this._ae.duration > 0) this._progress = this._ae.currentTime / this._ae.duration;
return this._progress;
}
/**
@@ -44,92 +70,112 @@ export class AudioMusic extends AudioSource {
*/
set progress(value: number) {
this._progress = value;
this.currentTime = value * this.duration;
this._ae.currentTime = value * this._ae.duration;
}
start() {
// this.node.on(AudioSource.EventType.STARTED, this.onAudioStarted, this);
this.node.on(AudioSource.EventType.ENDED, this.onAudioEnded, this);
constructor() {
super();
this.name = "AudioMusic";
this._ae = this.addComponent(AudioEffect);
this._ae.onComplete = this.onAudioEffectPlayComplete.bind(this);
}
// private onAudioStarted() { }
private onAudioEnded() {
if (this._params && this._params.onPlayComplete) {
this._params.onPlayComplete();
}
/** 音效播放完成 */
private onAudioEffectPlayComplete(ae: AudioEffect) {
ae.params && ae.params.onPlayComplete && ae.params.onPlayComplete(ae);
}
/**
* 加载音乐并播放
* @param url 音乐资源地址
* @param path 音乐资源地址
* @param params 背景音乐资源播放参数
*/
async loadAndPlay(url: string, params?: IAudioParams) {
if (!this.switch) return; // 禁止播放音乐
async loadAndPlay(path: string, params?: IAudioParams) {
if (!this.getSwitch()) return; // 禁止播放音乐
// 下一个加载的背景音乐资源
if (this._isLoading) {
this._nextUrl = url;
this._nextPath = path;
this._nextParams = params!;
return;
}
let bundleName = resLoader.defaultBundleName;
let loop = false;
let volume = this.volume;
let onPlayComplete: Function = null!;
if (params) {
this._params = params!
if (params.bundle != null) bundleName = params.bundle;
if (params.loop != null) loop = params.loop;
if (params.volume != null) volume = params.volume;
if (params.onPlayComplete != null) onPlayComplete = params.onPlayComplete;
};
if (params == null) {
params = {
type: AudioEffectType.Music,
bundle: resLoader.defaultBundleName,
loop: true,
volume: this.getVolume()
}
}
else {
if (params.type == null) params.type = AudioEffectType.Music;
if (params.bundle == null) params.bundle = resLoader.defaultBundleName;
if (params.loop == null) params.loop = true;
if (params.volume == null) params.volume = this.getVolume()
}
this._isLoading = true;
var clip: AudioClip = await resLoader.loadAsync(bundleName, url, AudioClip);
if (clip) {
this._isLoading = false;
// 处理等待加载的背景音乐
if (this._nextUrl != null) {
// 加载等待播放的背景音乐
this.loadAndPlay(this._nextUrl, this._nextParams);
this._nextUrl = null!;
this._nextParams = null!;
}
else {
// 正在播放的时候先关闭
if (this.playing) {
this.stop();
}
let clip: AudioClip = null!;
if (path.indexOf("http") == 0) {
const extension = path.split('.').pop();
clip = await resLoader.loadRemote<AudioClip>(path, { ext: `.${extension}` });
}
else {
clip = await resLoader.loadAsync(params.bundle!, path, AudioClip);
}
// 删除当前正在播放的音乐
this.release();
this._isLoading = false;
// 播放背景音乐
this.clip = clip;
this.loop = loop;
this.volume = volume;
this.currentTime = 0;
this.play();
}
// 处理等待加载的背景音乐
if (this._nextPath != null) {
// 加载等待播放的背景音乐
this.loadAndPlay(this._nextPath, this._nextParams);
this._nextPath = null!;
this._nextParams = null!;
}
else {
// 正在播放的时候先关闭
if (this._ae.playing) this.stop();
// 删除当前正在播放的音乐
this.release();
// 播放背景音乐
this._ae.params = params;
this._ae.path = path;
this._ae.clip = clip;
this._ae.loop = params.loop!;
this._ae.volume = params.volume!;
this._ae.currentTime = 0;
this._ae.play();
}
}
/** 恢复当前暂停的音乐与音效播放 */
resume() {
if (!this._ae.playing && this.progress > 0) this._ae.play();
}
/** 暂停当前音乐与音效的播放 */
pause() {
if (this._ae.playing) this._ae.pause();
}
/** 停止当前音乐与音效的播放 */
stop(): void {
if (this.switch && this.playing) {
super.stop();
if (this.getSwitch() && this._ae.playing) {
this._ae.stop();
}
}
/** 释放当前背景音乐资源 */
release() {
if (this.clip) {
if (this._ae.clip) {
this.stop();
this.clip.decRef();
this.clip = null;
this._ae.clip.decRef();
this._ae.clip = null;
}
}
}

View File

@@ -1,10 +1,21 @@
export interface IAudioParams {
/** 音乐分类 */
type?: string,
/** 资源包名 */
bundle?: string,
/** 是否循环播放 */
loop?: boolean;
/** 音效音量 */
volume?: number;
/** 是否在播放完后自动释放音乐资源(默认不释放) */
destroy?: boolean;
/** 播放完成事件 */
onPlayComplete?: Function;
}
export interface IAudioData {
/** 音乐开关 */
switch: boolean;
/** 音量 */
volume: number;
}

View File

@@ -73,34 +73,28 @@ export class ResLoader {
* 加载远程资源
* @param url 资源地址
* @param options 资源参数,例:{ ext: ".png" }
* @param onComplete 加载完成回调
* @example
var opt: IRemoteOptions = { ext: ".png" };
var onComplete = (err: Error | null, data: ImageAsset) => {
const texture = new Texture2D();
texture.image = data;
const spriteFrame = new SpriteFrame();
spriteFrame.texture = texture;
var sprite = this.sprite.addComponent(Sprite);
sprite.spriteFrame = spriteFrame;
}
oops.res.loadRemote<ImageAsset>(this.url, opt, onComplete);
var opt: IRemoteOptions = { ext: ".png" };
var data = await oops.res.loadRemote<ImageAsset>(this.url, opt);
const texture = new Texture2D();
texture.image = data;
const spriteFrame = new SpriteFrame();
spriteFrame.texture = texture;
var sprite = this.sprite.addComponent(Sprite);
sprite.spriteFrame = spriteFrame;
*/
loadRemote<T extends Asset>(url: string, options: IRemoteOptions | null, onComplete?: CompleteCallback): void;
loadRemote<T extends Asset>(url: string, onComplete?: CompleteCallback): void;
loadRemote<T extends Asset>(url: string, ...args: any): void {
let options: IRemoteOptions | null = null;
let onComplete: CompleteCallback = null;
if (args.length == 2) {
options = args[0];
onComplete = args[1];
}
else {
onComplete = args[0];
}
assetManager.loadRemote<T>(url, options, onComplete);
loadRemote<T extends Asset>(url: string, options: IRemoteOptions | null = null): Promise<T> {
return new Promise<T>((resolve, reject) => {
assetManager.loadRemote<T>(url, options, (err, data: T) => {
if (err) {
reject(null);
return;
}
resolve(data);
});
});
}
//#endregion
@@ -117,12 +111,13 @@ oops.res.loadRemote<ImageAsset>(this.url, opt, onComplete);
/**
* 加载资源包
* @param name 资源地址
* @param options 资源参数,例:{ version: "74fbe" }
* @example
await oops.res.loadBundle(name);
await oops.res.loadBundle(name, options);
*/
loadBundle(name: string): Promise<AssetManager.Bundle> {
loadBundle(name: string, options: { [k: string]: any; version?: string; } | null = null): Promise<AssetManager.Bundle> {
return new Promise<AssetManager.Bundle>((resolve, reject) => {
assetManager.loadBundle(name, (err, bundle: AssetManager.Bundle) => {
assetManager.loadBundle(name, options, (err, bundle: AssetManager.Bundle) => {
if (err) {
resolve(null!);
return;

View File

@@ -4,11 +4,12 @@
* @LastEditors: dgflash
* @LastEditTime: 2025-08-15 10:06:47
*/
import { Layers, Node, NodePool, Prefab, Vec3, warn, Widget } from "cc";
import { Node, NodePool, Prefab, Vec3, warn } from "cc";
import { resLoader } from "../../common/loader/ResLoader";
import { ViewUtil } from "../../utils/ViewUtil";
import { LayerCustomType } from "./LayerEnum";
import { GameElementParams, LayerGameElement } from "./LayerGameElement";
import { LayerHelper } from "./LayerHelper";
import { GameElementConfig } from "./UIConfig";
/* 二维游戏层 */
@@ -18,14 +19,7 @@ export class LayerGame extends Node {
constructor() {
super(LayerCustomType.Game);
const widget: Widget = this.addComponent(Widget);
widget.isAlignLeft = widget.isAlignRight = widget.isAlignTop = widget.isAlignBottom = true;
widget.left = widget.right = widget.top = widget.bottom = 0;
widget.alignMode = 2;
widget.enabled = true;
this.layer = Layers.Enum.UI_2D;
LayerHelper.setFullScreen(this);
}
/**

View File

@@ -0,0 +1,20 @@
import { Layers } from "cc";
import { Node, Widget } from "cc";
/** 界面层辅助工具 */
export class LayerHelper {
/**
* 界面层全屏布局
* @param node 全屏布局的节点
*/
static setFullScreen(node: Node) {
const widget: Widget = node.addComponent(Widget);
widget.isAlignLeft = widget.isAlignRight = widget.isAlignTop = widget.isAlignBottom = true;
widget.left = widget.right = widget.top = widget.bottom = 0;
widget.alignMode = 2;
widget.enabled = true;
node.layer = Layers.Enum.UI_2D;
return widget;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "2e65136e-fc22-466b-a076-16dfbb12b505",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,14 +1,14 @@
import { Camera, Layers, Node, ResolutionPolicy, SafeArea, Widget, screen, view, warn } from "cc";
import { resLoader } from "../../common/loader/ResLoader";
import { oops } from "../../Oops";
import { LayerUIElement, UICallbacks } from "./LayerUIElement";
import { LayerDialog } from "./LayerDialog";
import { LayerCustomType, LayerTypeCls, UIConfigMap, Uiid } from "./LayerEnum";
import { LayerGame } from "./LayerGame";
import { LayerNotify } from "./LayerNotify";
import { LayerPopUp } from "./LayerPopup";
import { LayerUI } from "./LayerUI";
import { LayerUIElement, UICallbacks } from "./LayerUIElement";
import { UIConfig } from "./UIConfig";
import { LayerGame } from "./LayerGame";
/** 界面层级管理器 */
export class LayerManager {
@@ -372,7 +372,7 @@ export class LayerManager {
* @example
* oops.gui.clear();
*/
clear(isDestroy: boolean = false) {
clear(isDestroy: boolean = true) {
this.uiLayers.forEach((layer: LayerUI) => {
layer.clear(isDestroy);
})

View File

@@ -4,15 +4,14 @@
* @LastEditors: dgflash
* @LastEditTime: 2022-09-02 13:44:12
*/
import { BlockInputEvents, Layers, Node, Widget, instantiate } from "cc";
import { BlockInputEvents, Node, instantiate } from "cc";
import { EDITOR } from "cc/env";
import { ViewUtil } from "../../utils/ViewUtil";
import { PromptResType } from "../GuiEnum";
import { Notify } from "../prompt/Notify";
import { LayerHelper } from "./LayerHelper";
/*
* 滚动消息提示层
*/
/* 滚动消息提示层 */
export class LayerNotify extends Node {
private black!: BlockInputEvents;
/** 等待提示资源 */
@@ -24,14 +23,8 @@ export class LayerNotify extends Node {
constructor(name: string) {
super(name);
LayerHelper.setFullScreen(this);
const widget: Widget = this.addComponent(Widget);
widget.isAlignLeft = widget.isAlignRight = widget.isAlignTop = widget.isAlignBottom = true;
widget.left = widget.right = widget.top = widget.bottom = 0;
widget.alignMode = 2;
widget.enabled = true;
this.layer = Layers.Enum.UI_2D;
this.black = this.addComponent(BlockInputEvents);
this.black.enabled = false;
}

View File

@@ -3,13 +3,12 @@
* @LastEditors: dgflash
* @LastEditTime: 2022-09-02 13:44:28
*/
import { BlockInputEvents, EventTouch, Layers, Node } from "cc";
import { BlockInputEvents, EventTouch, Node } from "cc";
import { ViewUtil } from "../../utils/ViewUtil";
import { PromptResType } from "../GuiEnum";
import { LayerUI } from "./LayerUI";
import { UIConfig } from "./UIConfig";
import { UIParams } from "./LayerUIElement";
import { UIConfig } from "./UIConfig";
/* 弹窗层,允许同时弹出多个窗口 */
export class LayerPopUp extends LayerUI {
@@ -21,7 +20,6 @@ export class LayerPopUp extends LayerUI {
constructor(name: string) {
super(name);
this.layer = Layers.Enum.UI_2D;
this.on(Node.EventType.CHILD_ADDED, this.onChildAdded, this);
this.on(Node.EventType.CHILD_REMOVED, this.onChildRemoved, this);
}

View File

@@ -1,7 +1,8 @@
import { instantiate, Node, Prefab, SafeArea, Widget } from "cc";
import { instantiate, Node, Prefab, SafeArea } from "cc";
import { Collection } from "db://oops-framework/libs/collection/Collection";
import { oops } from "../../Oops";
import { Uiid } from "./LayerEnum";
import { LayerHelper } from "./LayerHelper";
import { LayerUIElement, UICallbacks, UIParams } from "./LayerUIElement";
import { UIConfig } from "./UIConfig";
@@ -20,12 +21,7 @@ export class LayerUI extends Node {
*/
constructor(name: string) {
super(name);
const widget: Widget = this.addComponent(Widget);
widget.isAlignLeft = widget.isAlignRight = widget.isAlignTop = widget.isAlignBottom = true;
widget.left = widget.right = widget.top = widget.bottom = 0;
widget.alignMode = 2;
widget.enabled = true;
LayerHelper.setFullScreen(this);
}
/**
@@ -59,36 +55,38 @@ export class LayerUI extends Node {
/**
* 加载界面资源
* @param vp 显示参数
* @param uip 显示参数
* @param bundle 远程资源包名,如果为空就是默认本地资源包
*/
protected async load(vp: UIParams, bundle?: string) {
protected async load(uip: UIParams, bundle?: string) {
// 加载界面资源超时提示
const timerId = setTimeout(this.onLoadingTimeoutGui, oops.config.game.loadingTimeoutGui);
if (vp && vp.node) {
await this.showUi(vp);
if (uip && uip.node) {
await this.showUi(uip);
}
else {
// 优先加载配置的指定资源包中资源,如果没配置则加载默认资源包资源
bundle = bundle || oops.res.defaultBundleName;
const res = await oops.res.loadAsync(bundle, vp.config.prefab, Prefab);
const res = await oops.res.loadAsync(bundle, uip.config.prefab, Prefab);
if (res) {
vp.node = instantiate(res);
uip.node = instantiate(res);
// 是否启动真机安全区域显示
if (vp.config.safeArea) vp.node.addComponent(SafeArea);
if (uip.config.safeArea) uip.node.addComponent(SafeArea);
// 窗口事件委托
const dc = vp.node.addComponent(LayerUIElement);
dc.params = vp;
const dc = uip.node.addComponent(LayerUIElement);
dc.params = uip;
//@ts-ignore
dc.onCloseWindow = this.onCloseWindow.bind(this);
// 显示界面
await this.showUi(vp);
await this.showUi(uip);
}
else {
console.warn(`路径为【${vp.config.prefab}】的预制加载失败`);
this.failure(vp);
console.warn(`路径为【${uip.config.prefab}】的预制加载失败`);
this.failure(uip);
}
}
@@ -145,20 +143,20 @@ export class LayerUI extends Node {
if (isDestroy !== undefined) release = isDestroy;
// 界面移出舞台
const vp = this.ui_nodes.get(prefabPath);
if (vp) {
const uip = this.ui_nodes.get(prefabPath);
if (uip) {
// 优先使用参数中控制的释放条件,如果未传递参数则用配置中的释放条件,默认不缓存关闭的界面
if (release === undefined) {
release = vp.config.destroy !== undefined ? vp.config.destroy : true;
release = uip.config.destroy !== undefined ? uip.config.destroy : true;
}
// 不释放界面,缓存起来待下次使用
if (release === false) {
this.ui_cache.set(vp.config.prefab, vp);
this.ui_cache.set(uip.config.prefab, uip);
}
const childNode = vp.node;
const comp = childNode.getComponent(LayerUIElement)!;
const node = uip.node;
const comp = node.getComponent(LayerUIElement)!;
comp.remove(release);
}
@@ -172,12 +170,10 @@ export class LayerUI extends Node {
if (vp) {
this.onCloseWindow(vp);
this.ui_cache.delete(prefabPath);
const childNode = vp.node;
const comp = childNode.getComponent(LayerUIElement)!;
if (comp) {
comp.remove(true);
}
childNode.destroy();
const node = vp.node;
const comp = node.getComponent(LayerUIElement)!;
comp.remove(true);
node.destroy();
}
}
@@ -187,8 +183,7 @@ export class LayerUI extends Node {
*/
get(prefabPath: string): Node {
const vp = this.ui_nodes.get(prefabPath);
if (vp)
return vp.node;
if (vp) return vp.node;
return null!;
}
@@ -206,10 +201,11 @@ export class LayerUI extends Node {
*/
clear(isDestroy: boolean): void {
// 清除所有显示的界面
this.ui_nodes.forEach((value: UIParams, key: string) => {
this.remove(value.config.prefab, isDestroy);
value.valid = false;
});
const length = this.ui_nodes.array.length - 1;
for (let i = length; i >= 0; i--) {
const uip = this.ui_nodes.array[i];
this.remove(uip.config.prefab, isDestroy);
}
this.ui_nodes.clear();
// 清除缓存中的界面

View File

@@ -8,12 +8,12 @@ import { Component, Node, _decorator } from "cc";
import { oops } from "../../Oops";
import { UIConfig } from "./UIConfig";
const { ccclass } = _decorator;
const EventOnAdded: string = "onAdded";
const EventOnBeforeRemove: string = "onBeforeRemove";
const EventOnRemoved: string = "onRemoved";
const { ccclass } = _decorator;
/** 窗口元素组件 */
@ccclass('LayerUIElement')
export class LayerUIElement extends Component {
@@ -22,7 +22,7 @@ export class LayerUIElement extends Component {
/** 关闭窗口之前 */
onCloseWindowBefore: Function = null!;
/** 界面关闭回调 - 包括关闭动画播放完(辅助框架内存业务流程使用) */
onCloseWindow: Function = null!;
private onCloseWindow: Function = null!;
/** 窗口添加 */
add(): Promise<boolean> {
@@ -56,25 +56,20 @@ export class LayerUIElement extends Component {
// 通知外部对象窗口组件上移除之前的事件(关闭窗口前的关闭动画处理)
if (typeof this.params.callbacks.onBeforeRemove === "function") {
this.params.callbacks.onBeforeRemove(
this.node,
this.onBeforeRemoveNext.bind(this, isDestroy));
this.params.callbacks.onBeforeRemove(this.node, this.onBeforeRemoveNext.bind(this, isDestroy));
}
else {
this.removed(this.params, isDestroy);
this.onBeforeRemoveNext(isDestroy);
}
}
else {
this.removed(this.params, isDestroy);
this.onBeforeRemoveNext(isDestroy);
}
}
/** 窗口关闭前动画处理完后的回调方法,主要用于释放资源 */
private onBeforeRemoveNext(isDestroy?: boolean) {
if (this.onCloseWindowBefore) {
this.onCloseWindowBefore();
this.onCloseWindowBefore = null!;
}
this.onCloseWindowBefore && this.onCloseWindowBefore();
this.removed(this.params, isDestroy);
}

View File

@@ -183,9 +183,9 @@ export class EffectSingleCase {
*/
put(node: Node) {
//@ts-ignore
let name = node.res_path;
const name = node.res_path;
if (name) {
let np = this.effects.get(name);
const np = this.effects.get(name);
if (np) {
// 回收使用的节点
this.effects_use.delete(node);
@@ -202,7 +202,7 @@ export class EffectSingleCase {
*/
clear(path?: string) {
if (path) {
var np = this.effects.get(path);
const np = this.effects.get(path);
if (np) np.clear();
}
else {

View File

@@ -1,6 +1,5 @@
import { AudioClip, Component, EventTouch, Node, _decorator, game } from "cc";
import { oops } from "../../../core/Oops";
import { resLoader } from "../../../core/common/loader/ResLoader";
const { ccclass, property, menu } = _decorator;
@@ -23,10 +22,15 @@ export default class ButtonSimple extends Component {
type: AudioClip
})
private effect: AudioClip = null!;
// private effectIds: number[] = [];
private touchCount = 0;
private touchtEndTime = 0;
private static effectPath: string = null!;
/** 批量设置触摸音效 */
static setBatchEffect(path: string) {
this.effectPath = path;
}
onLoad() {
this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
this.node.on(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
@@ -56,23 +60,16 @@ export default class ButtonSimple extends Component {
/** 短按触摸音效 */
protected async playEffect() {
if (this.effect) {
if (ButtonSimple.effectPath) {
oops.audio.playEffect(ButtonSimple.effectPath);
}
else if (this.effect) {
oops.audio.playEffect(this.effect);
// const effectId = await oops.audio.playEffect(this.effect, resLoader.defaultBundleName, () => {
// this.effectIds.remove(effectId);
// });
// if (effectId > 0) this.effectIds.push(effectId);
}
}
onDestroy() {
this.node.off(Node.EventType.TOUCH_END, this.onTouchEnd, this);
this.node.off(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
// if (this.effect) {
// this.effectIds.forEach(effectId => {
// oops.audio.putEffect(effectId, this.effect);
// });
// }
}
}

View File

@@ -32,6 +32,12 @@ export default class UIButton extends Button {
/** 触摸结束时间 */
private _touchEndTime = 0;
private static effectPath: string = null!;
/** 批量设置触摸音效 */
static setBatchEffect(path: string) {
this.effectPath = path;
}
/** 触摸结束 */
protected _onTouchEnded(event: EventTouch) {
if (!this._interactable || !this.enabledInHierarchy) {
@@ -74,7 +80,10 @@ export default class UIButton extends Button {
/** 短按触摸音效 */
protected playEffect() {
if (this.effect) {
if (UIButton.effectPath) {
oops.audio.playEffect(UIButton.effectPath);
}
else if (this.effect) {
oops.audio.playEffect(this.effect);
}
}

View File

@@ -6,6 +6,7 @@
*/
import { Asset, Button, Component, EventHandler, EventKeyboard, EventTouch, Input, Node, Prefab, Sprite, SpriteFrame, __private, _decorator, input, isValid } from "cc";
import { oops } from "../../core/Oops";
import { AudioEffect } from "../../core/common/audio/AudioEffect";
import { IAudioParams } from "../../core/common/audio/IAudio";
import { EventDispatcher } from "../../core/common/event/EventDispatcher";
import { EventMessage, ListenerFunc } from "../../core/common/event/EventMessage";
@@ -17,8 +18,7 @@ const { ccclass } = _decorator;
/** 加载资源类型 */
enum ResType {
Load,
LoadDir,
Audio
LoadDir
}
/** 资源加载记录 */
@@ -33,7 +33,6 @@ interface ResRecord {
resId?: number
}
/**
* 游戏显示对象组件模板
* 1、当前对象加载的资源会在对象释放时自动释放引用的资源
@@ -138,7 +137,7 @@ export class GameComponent extends Component {
* @param bundleName 资源包名
* @param paths 资源路径
*/
private addPathToRecord<T>(type: ResType, bundleName: string, paths?: string | string[] | AssetType<T> | ProgressCallback | CompleteCallback | null, resId?: number) {
private addPathToRecord<T>(type: ResType, bundleName: string, paths?: string | string[] | AssetType<T> | ProgressCallback | CompleteCallback | null) {
if (this.resPaths == null) this.resPaths = new Map();
var rps = this.resPaths.get(type);
@@ -151,45 +150,44 @@ export class GameComponent extends Component {
let realBundle = bundleName;
for (let index = 0; index < paths.length; index++) {
let realPath = paths[index];
let key = this.getResKey(realBundle, realPath, resId);
let key = this.getResKey(realBundle, realPath);
let rp = rps.get(key);
if (rp) {
rp.refCount++;
}
else {
rps.set(key, { path: realPath, bundle: realBundle, refCount: 1, resId: resId });
rps.set(key, { path: realPath, bundle: realBundle, refCount: 1 });
}
}
}
else if (typeof paths === "string") {
let realBundle = bundleName;
let realPath = paths;
let key = this.getResKey(realBundle, realPath, resId);
let key = this.getResKey(realBundle, realPath);
let rp = rps.get(key);
if (rp) {
rp.refCount++;
}
else {
rps.set(key, { path: realPath, bundle: realBundle, refCount: 1, resId: resId });
rps.set(key, { path: realPath, bundle: realBundle, refCount: 1 });
}
}
else {
let realBundle = oops.res.defaultBundleName;
let realPath = bundleName;
let key = this.getResKey(realBundle, realPath, resId);
let key = this.getResKey(realBundle, realPath);
let rp = rps.get(key);
if (rp) {
rp.refCount++;
}
else {
rps.set(key, { path: realPath, bundle: realBundle, refCount: 1, resId: resId });
rps.set(key, { path: realPath, bundle: realBundle, refCount: 1 });
}
}
}
private getResKey(realBundle: string, realPath: string, resId?: number): string {
private getResKey(realBundle: string, realPath: string): string {
let key = `${realBundle}:${realPath}`;
if (resId != null) key += ":" + resId;
return key;
}
@@ -302,18 +300,6 @@ export class GameComponent extends Component {
}
}
/** 释放音效资源 */
releaseAudioEffect() {
if (this.resPaths) {
const rps = this.resPaths.get(ResType.Audio);
if (rps) {
rps.forEach((value: ResRecord) => {
oops.audio.putEffect(value.resId!, value.path, value.bundle); // 回收音乐效到音效池中等下次使用
});
}
}
}
/**
* 设置图片资源
* @param target 目标精灵对象
@@ -351,32 +337,13 @@ export class GameComponent extends Component {
* @param url 资源地址
* @param params 音效播放参数
*/
async playEffect(url: string, params?: IAudioParams): Promise<number> {
async playEffect(url: string, params?: IAudioParams): Promise<AudioEffect> {
return new Promise(async (resolve, reject) => {
let bundleName = resLoader.defaultBundleName;
if (params == null) {
params = { bundle: bundleName }
}
else if (params.bundle != null) {
bundleName = params.bundle;
}
// 音效播放完,关闭正在播放状态的音乐效果
params.onPlayComplete = (aeid: number, url: string, bundleName: string) => {
// 音效播放完前,界面被释放
if (!this.isValid) return;
// 删除界面音效的播放记录
const rps = this.resPaths.get(ResType.Audio);
if (rps) {
const key = this.getResKey(bundleName, url, aeid);
rps.delete(key);
}
}
let resId = await oops.audio.playEffect(url, params);
this.addPathToRecord(ResType.Audio, bundleName, url, resId); // 支持界面释放时,立即停止所有音效的播放
resolve(resId);
if (params == null) params = {};
let ae = await oops.audio.playEffect(url, params);
this.addPathToRecord(ResType.Load, ae.params!.bundle!, url);
resolve(ae);
});
}
//#endregion
@@ -528,7 +495,6 @@ export class GameComponent extends Component {
// 自动释放资源
if (this.resPaths) {
this.releaseAudioEffect();
this.release();
this.releaseDir();
this.resPaths.clear();

View File

@@ -1,15 +1,16 @@
import { Node, __private } from "cc";
import { oops } from "../../core/Oops";
import { resLoader } from "../../core/common/loader/ResLoader";
import { LayerUIElement, UICallbacks } from "../../core/gui/layer/LayerUIElement";
import { Uiid } from "../../core/gui/layer/LayerEnum";
import { UIConfig } from "../../core/gui/layer/UIConfig";
import { LayerUIElement, UICallbacks } from "../../core/gui/layer/LayerUIElement";
import { ViewUtil } from "../../core/utils/ViewUtil";
import { ecs } from "../../libs/ecs/ECS";
import { CompType } from "../../libs/ecs/ECSModel";
import { CCComp } from "./CCComp";
import { CCVMParentComp } from "./CCVMParentComp";
export type ECSCtor<T extends ecs.Comp> = __private.__types_globals__Constructor<T> | __private.__types_globals__AbstractedConstructor<T>;
export class ModuleUtil {
/**
* 添加界面组件
@@ -18,11 +19,7 @@ export class ModuleUtil {
* @param uiId 界面资源编号
* @param uiArgs 界面参数
*/
static addViewUi<T extends CCVMParentComp | CCComp>(
ent: ecs.Entity,
ctor: __private.__types_globals__Constructor<T> | __private.__types_globals__AbstractedConstructor<T>,
uiId: number,
uiArgs: any = null) {
static addViewUi<T extends CCVMParentComp | CCComp>(ent: ecs.Entity, ctor: ECSCtor<T>, uiId: Uiid, uiArgs: any = null) {
const uic: UICallbacks = {
onAdded: (node: Node, params: any) => {
const comp = node.getComponent(ctor) as ecs.Comp;
@@ -41,11 +38,7 @@ export class ModuleUtil {
* @param uiArgs 界面参数
* @returns 界面节点
*/
static addViewUiAsync<T extends CCVMParentComp | CCComp>(
ent: ecs.Entity,
ctor: __private.__types_globals__Constructor<T> | __private.__types_globals__AbstractedConstructor<T>,
uiId: number | UIConfig,
uiArgs: any = null): Promise<Node | null> {
static addViewUiAsync<T extends CCVMParentComp | CCComp>(ent: ecs.Entity, ctor: ECSCtor<T>, uiId: Uiid, uiArgs: any = null): Promise<Node | null> {
return new Promise<Node | null>((resolve, reject) => {
const uic: UICallbacks = {
onAdded: (node: Node, params: any) => {
@@ -69,12 +62,7 @@ export class ModuleUtil {
* @param url 显示资源地址
* @param bundleName 资源包名称
*/
static addView<T extends CCVMParentComp | CCComp>(
ent: ecs.Entity,
ctor: __private.__types_globals__Constructor<T> | __private.__types_globals__AbstractedConstructor<T>,
parent: Node,
url: string,
bundleName: string = resLoader.defaultBundleName) {
static addView<T extends CCVMParentComp | CCComp>(ent: ecs.Entity, ctor: ECSCtor<T>, parent: Node, url: string, bundleName: string = resLoader.defaultBundleName) {
const node = ViewUtil.createPrefabNode(url, bundleName);
const comp = node.getComponent(ctor)!;
ent.add(comp);
@@ -98,28 +86,12 @@ export class ModuleUtil {
const comp = node.getComponent(LayerUIElement);
if (comp) {
if (comp.params.callbacks.onBeforeRemove) {
comp.onCloseWindowBefore = () => {
ent.remove(ctor, isDestroy);
if (onRemoved) onRemoved();
};
}
else if (comp.params.callbacks.onRemoved) {
comp.onCloseWindow = () => {
ent.remove(ctor, isDestroy);
if (onRemoved) onRemoved();
};
}
else {
ent.remove(ctor, isDestroy);
comp.onCloseWindowBefore = () => {
// 移除ECS显示组件
if (isDestroy) ent.remove(ctor, isDestroy);
if (onRemoved) onRemoved();
}
};
oops.gui.remove(uiId, isDestroy);
}
else {
ent.remove(ctor, isDestroy);
if (onRemoved) onRemoved();
}
oops.gui.remove(uiId, isDestroy);
}
}