/* * @Author: dgflash * @Date: 2022-04-14 17:08:01 * @LastEditors: dgflash * @LastEditTime: 2022-12-13 11:36:00 */ import { Asset, Button, Component, EventHandler, EventKeyboard, EventTouch, Input, Node, Prefab, Sprite, SpriteFrame, __private, _decorator, input, isValid } from "cc"; import { oops } from "../../core/Oops"; import { EventDispatcher } from "../../core/common/event/EventDispatcher"; import { EventMessage, ListenerFunc } from "../../core/common/event/EventMessage"; import { AssetType, CompleteCallback, Paths, ProgressCallback, resLoader } from "../../core/common/loader/ResLoader"; import { ViewUtil } from "../../core/utils/ViewUtil"; const { ccclass } = _decorator; /** 加载资源类型 */ enum ResType { Load, LoadDir, Audio } /** 资源加载记录 */ interface ResRecord { /** 资源包名 */ bundle: string, /** 资源路径 */ path: string, /** 引用计数 */ refCount: number, /** 资源编号 */ resId?: number } /** * 游戏显示对象组件模板 * 1、当前对象加载的资源,会在对象释放时,自动释放引用的资源 * 2、当前对象支持启动游戏引擎提供的各种常用逻辑事件 */ @ccclass("GameComponent") export class GameComponent extends Component { //#region 全局事件管理 private _event: EventDispatcher | null = null; /** 全局事件管理器 */ private get event(): EventDispatcher { if (this._event == null) this._event = new EventDispatcher(); return this._event; } /** * 注册全局事件 * @param event 事件名 * @param listener 处理事件的侦听器函数 * @param object 侦听函数绑定的this对象 */ on(event: string, listener: ListenerFunc, object: any) { this.event.on(event, listener, object); } /** * 移除全局事件 * @param event 事件名 */ off(event: string) { this.event.off(event); } /** * 触发全局事件 * @param event 事件名 * @param args 事件参数 */ dispatchEvent(event: string, ...args: any) { this.event.dispatchEvent(event, ...args); } //#endregion //#region 预制节点管理 /** 摊平的节点集合(所有节点不能重名) */ nodes: Map = null!; /** 通过节点名获取预制上的节点,整个预制不能有重名节点 */ getNode(name: string): Node | undefined { if (this.nodes) { return this.nodes.get(name); } return undefined; } /** 平摊所有节点存到Map中通过get(name: string)方法获取 */ nodeTreeInfoLite() { this.nodes = new Map(); ViewUtil.nodeTreeInfoLite(this.node, this.nodes); } /** * 从资源缓存中找到预制资源名并创建一个显示对象 * @param path 资源路径 */ createPrefabNode(path: string, bundleName: string = oops.res.defaultBundleName): Node { return ViewUtil.createPrefabNode(path, bundleName); } /** * 加载预制并创建预制节点 * @param path 资源路径 * @param bundleName 资源包名 */ createPrefabNodeAsync(path: string, bundleName: string = oops.res.defaultBundleName): Promise { return new Promise(async (resolve, reject) => { await this.loadAsync(bundleName, path, Prefab); let node = ViewUtil.createPrefabNode(path, bundleName); resolve(node); }); } //#endregion //#region 资源加载管理 /** 资源路径 */ private resPaths: Map> = null!; // 资源使用记录 /** * 获取资源 * @param path 资源路径 * @param type 资源类型 * @param bundleName 远程资源包名 */ getRes(path: string, type?: __private.__types_globals__Constructor | null, bundleName?: string): T | null { return oops.res.get(path, type, bundleName); } /** * 添加资源使用记录 * @param type 资源类型 * @param bundleName 资源包名 * @param paths 资源路径 */ private addPathToRecord(type: ResType, bundleName: string, paths?: string | string[] | AssetType | ProgressCallback | CompleteCallback | null, resId?: number) { if (this.resPaths == null) this.resPaths = new Map(); var rps = this.resPaths.get(type); if (rps == null) { rps = new Map(); this.resPaths.set(type, rps); } if (paths instanceof Array) { let realBundle = bundleName; for (let index = 0; index < paths.length; index++) { let realPath = paths[index]; let key = this.getResKey(realBundle, realPath, resId); let rp = rps.get(key); if (rp) { rp.refCount++; } else { rps.set(key, { path: realPath, bundle: realBundle, refCount: 1, resId: resId }); } } } else if (typeof paths === "string") { let realBundle = bundleName; let realPath = paths; let key = this.getResKey(realBundle, realPath, 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); let rp = rps.get(key); if (rp) { rp.refCount++; } else { rps.set(key, { path: realPath, bundle: realBundle, refCount: 1, resId: resId }); } } } private getResKey(realBundle: string, realPath: string, resId?: number): string { let key = `${realBundle}:${realPath}`; if (resId != null) key += ":" + resId; return key; } /** * 加载一个资源 * @param bundleName 远程包名 * @param paths 资源路径 * @param type 资源类型 * @param onProgress 加载进度回调 * @param onComplete 加载完成回调 */ load(bundleName: string, paths: Paths, type: AssetType, onProgress: ProgressCallback, onComplete: CompleteCallback): void; load(bundleName: string, paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void; load(bundleName: string, paths: Paths, onComplete?: CompleteCallback): void; load(bundleName: string, paths: Paths, type: AssetType, onComplete?: CompleteCallback): void; load(paths: Paths, type: AssetType, onProgress: ProgressCallback, onComplete: CompleteCallback): void; load(paths: Paths, onProgress: ProgressCallback, onComplete: CompleteCallback): void; load(paths: Paths, onComplete?: CompleteCallback): void; load(paths: Paths, type: AssetType, onComplete?: CompleteCallback): void; load( bundleName: string, paths?: Paths | AssetType | ProgressCallback | CompleteCallback, type?: AssetType | ProgressCallback | CompleteCallback, onProgress?: ProgressCallback | CompleteCallback, onComplete?: CompleteCallback, ) { this.addPathToRecord(ResType.Load, bundleName, paths); oops.res.load(bundleName, paths, type, onProgress, onComplete); } /** * 异步加载一个资源 * @param bundleName 远程包名 * @param paths 资源路径 * @param type 资源类型 */ loadAsync(bundleName: string, paths: Paths, type: AssetType): Promise; loadAsync(bundleName: string, paths: Paths): Promise; loadAsync(paths: Paths, type: AssetType): Promise; loadAsync(paths: Paths): Promise; loadAsync(bundleName: string, paths?: Paths | AssetType | ProgressCallback | CompleteCallback, type?: AssetType | ProgressCallback | CompleteCallback): Promise { this.addPathToRecord(ResType.Load, bundleName, paths); return oops.res.loadAsync(bundleName, paths, type); } /** * 加载文件夹中的资源 * @param bundleName 远程包名 * @param dir 文件夹名 * @param type 资源类型 * @param onProgress 加载进度回调 * @param onComplete 加载完成回调 */ loadDir(bundleName: string, dir: string, type: AssetType, onProgress: ProgressCallback, onComplete: CompleteCallback): void; loadDir(bundleName: string, dir: string, onProgress: ProgressCallback, onComplete: CompleteCallback): void; loadDir(bundleName: string, dir: string, onComplete?: CompleteCallback): void; loadDir(bundleName: string, dir: string, type: AssetType, onComplete?: CompleteCallback): void; loadDir(dir: string, type: AssetType, onProgress: ProgressCallback, onComplete: CompleteCallback): void; loadDir(dir: string, onProgress: ProgressCallback, onComplete: CompleteCallback): void; loadDir(dir: string, onComplete?: CompleteCallback): void; loadDir(dir: string, type: AssetType, onComplete?: CompleteCallback): void; loadDir( bundleName: string, dir?: string | AssetType | ProgressCallback | CompleteCallback, type?: AssetType | ProgressCallback | CompleteCallback, onProgress?: ProgressCallback | CompleteCallback, onComplete?: CompleteCallback, ) { let realDir: string; let realBundle: string; if (typeof dir === "string") { realDir = dir; realBundle = bundleName; } else { realDir = bundleName; realBundle = oops.res.defaultBundleName; } this.addPathToRecord(ResType.LoadDir, realBundle, realDir); oops.res.loadDir(bundleName, dir, type, onProgress, onComplete); } /** 释放资源 */ release() { if (this.resPaths) { const rps = this.resPaths.get(ResType.Load); if (rps) { rps.forEach((value: ResRecord) => { for (let i = 0; i < value.refCount; i++) { oops.res.release(value.path, value.bundle); } }); rps.clear(); } } } /** 释放文件夹的资源 */ releaseDir() { if (this.resPaths) { const rps = this.resPaths.get(ResType.LoadDir); if (rps) { rps.forEach((value: ResRecord) => { oops.res.releaseDir(value.path, value.bundle); }); } } } /** 释放音效资源 */ 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 目标精灵对象 * @param path 图片资源地址 * @param bundle 资源包名 */ async setSprite(target: Sprite, path: string, bundle: string = resLoader.defaultBundleName) { const spriteFrame = await this.loadAsync(bundle, path, SpriteFrame); if (!spriteFrame || !isValid(target)) { const rps = this.resPaths.get(ResType.Load); if (rps) { const key = this.getResKey(bundle, path); rps.delete(key); oops.res.release(path, bundle); } return; } spriteFrame.addRef(); target.spriteFrame = spriteFrame; } //#endregion //#region 音频播放管理 /** * 播放背景音乐(不受自动释放资源管理) * @param url 资源地址 * @param callback 资源加载完成回调 * @param bundleName 资源包名 */ playMusic(url: string, callback?: Function, bundleName?: string) { oops.audio.playMusic(url, callback, bundleName); } /** * 循环播放背景音乐(不受自动释放资源管理) * @param url 资源地址 * @param bundleName 资源包名 */ playMusicLoop(url: string, bundleName?: string) { oops.audio.stopMusic(); oops.audio.playMusicLoop(url, bundleName); } /** * 播放音效 * @param url 资源地址 * @param callback 资源加载完成回调 * @param bundleName 资源包名 */ async playEffect(url: string, bundleName?: string) { if (bundleName == null) bundleName = oops.res.defaultBundleName; 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, resId); } //#endregion //#region 游戏逻辑事件 /** * 批量设置当前界面按钮事件 * @param bindRootEvent 是否对预制根节点绑定触摸事件 * @example * 注:按钮节点Label1、Label2必须绑定UIButton等类型的按钮组件才会生效,方法名必须与节点名一致 * this.setButton(); * * Label1(event: EventTouch) { console.log(event.target.name); } * Label2(event: EventTouch) { console.log(event.target.name); } */ protected setButton(bindRootEvent: boolean = true) { // 自定义按钮批量绑定触摸事件 if (bindRootEvent) { this.node.on(Node.EventType.TOUCH_END, (event: EventTouch) => { const self: any = this; const func = self[event.target.name]; if (func) { func.call(this, event); } // 不触发界面根节点触摸事件、不触发长按钮组件的触摸事件 // else if (event.target != this.node && event.target.getComponent(ButtonTouchLong) == null) { // console.warn(`名为【${event.target.name}】的按钮事件方法不存在`); // } }, this); } // Cocos Creator Button组件批量绑定触摸事件(使用UIButton支持放连点功能) const regex = /<([^>]+)>/; const buttons = this.node.getComponentsInChildren