mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-06-07 18:52:23 +08:00
优化
This commit is contained in:
@@ -101,7 +101,7 @@ export abstract class CCEntity extends ecs.Entity {
|
||||
|
||||
// 跟随父节点释放自动释放当前资源
|
||||
if (parent instanceof GameComponent) {
|
||||
const result = await parent.createPrefabNode(path, bundleName);
|
||||
const result = await parent.nodes.createPrefabNode(path, bundleName);
|
||||
if (result == null) return null;
|
||||
|
||||
node = result;
|
||||
@@ -315,4 +315,4 @@ export abstract class CCEntity extends ecs.Entity {
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,22 @@
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-12-13 11:36:00
|
||||
*/
|
||||
import type { Asset, EventKeyboard, EventTouch, Sprite, __private } from 'cc';
|
||||
import { Button, Component, EventHandler, Input, Node, Prefab, SpriteFrame, _decorator, input, instantiate, isValid } from 'cc';
|
||||
import { oops } from '../../core/Oops';
|
||||
import type { Asset, EventKeyboard, Node, Sprite, __private } from 'cc';
|
||||
import { Component, _decorator } from 'cc';
|
||||
import type { AudioEffect } from '../../core/common/audio/AudioEffect';
|
||||
import type { IAudioParams } from '../../core/common/audio/IAudio';
|
||||
import { EventDispatcher } from '../../core/common/event/EventDispatcher';
|
||||
import type { ListenerFunc, ListenerFuncTyped } from '../../core/common/event/EventMessage';
|
||||
import { EventMessage } from '../../core/common/event/EventMessage';
|
||||
import { resAutoTracker } from '../../core/common/loader/ResAutoTracker';
|
||||
import type { AssetType, CompleteCallback, Paths, ProgressCallback } from '../../core/common/loader/ResLoader';
|
||||
import { resLoader } from '../../core/common/loader/ResLoader';
|
||||
import { ViewUtil } from '../../core/utils/ViewUtil';
|
||||
import { resRef } from '../../core/common/loader/ResRefManager';
|
||||
import { oops } from '../../core/Oops';
|
||||
import type { GameAudioModule } from './view/GameAudioModule';
|
||||
import type { GameButtonModule } from './view/GameButtonModule';
|
||||
import type { GameEventModule } from './view/GameEventModule';
|
||||
import type { GameKeyboardModule } from './view/GameKeyboardModule';
|
||||
import type { GameNodeModule } from './view/GameNodeModule';
|
||||
import type { GameResModule } from './view/GameResModule';
|
||||
import { GameViewModuleRegistry, ViewModuleKey } from './view/GameViewModuleRegistry';
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
@@ -23,275 +27,170 @@ const { ccclass } = _decorator;
|
||||
* 游戏显示对象组件模板
|
||||
*
|
||||
* 特性:
|
||||
* 1. 自动管理资源引用计数 - 多组件共享资源时不会错误释放
|
||||
* 2. 组件销毁时自动释放资源引用 - 开发者无需手动管理
|
||||
* 3. 全局资源追踪 - 可查看任意资源的引用者和引用计数
|
||||
* 1. 基于引擎 Asset.addRef/decRef + 递归依赖保护,与其它持有者共享时不误释放
|
||||
* 2. 组件销毁时自动 release 本产品登记的资源条目
|
||||
* 3. ResAutoTracker 全局调试视图(持有者 / 条目数)
|
||||
*
|
||||
* 使用示例:
|
||||
* ```typescript
|
||||
* // 加载资源(自动注册引用)
|
||||
* const spriteFrame = await this.load('textures/avatar', SpriteFrame);
|
||||
* const spriteFrame = await this.res.load('common', 'textures/avatar', SpriteFrame);
|
||||
* this.nodes.nodeTreeInfoLite();
|
||||
* this.event.on('MyEvent', this.onMyEvent, this);
|
||||
* this.event.setEvent('onGlobal');
|
||||
* this.button.setButton();
|
||||
* this.keyboard.setKeyboard(true, { onKeyDown: (e) => {} });
|
||||
* this.event.setGameShow(() => {});
|
||||
*
|
||||
* // 组件销毁时自动释放引用(无需手动调用)
|
||||
* // 只有当所有引用者都销毁时,资源才会被真正释放
|
||||
*
|
||||
* // 调试:查看资源引用情况
|
||||
* GameComponent.printGlobalResStatus();
|
||||
* GameComponent.setResDebugMode(true); // 开启详细日志
|
||||
* GameComponent.setResDebugMode(true);
|
||||
* ```
|
||||
*/
|
||||
@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;
|
||||
private _viewRegistry: GameViewModuleRegistry | null = null;
|
||||
|
||||
private get viewRegistry(): GameViewModuleRegistry {
|
||||
return (this._viewRegistry ??= new GameViewModuleRegistry(this));
|
||||
}
|
||||
|
||||
/** 标记是否已注册键盘事件 */
|
||||
private _keyboardEnabled = false;
|
||||
/** 标记是否已注册按钮事件 */
|
||||
private _buttonEnabled = false;
|
||||
/** 获取事件模块 */
|
||||
get event(): GameEventModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Event);
|
||||
}
|
||||
|
||||
//#region 强类型事件方法(提供给 Agent 自动生成用)
|
||||
/** 获取节点模块 */
|
||||
get nodes(): GameNodeModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册全局事件(强类型)
|
||||
* @param event 事件名(枚举)
|
||||
* @param listener 处理事件的侦听器函数
|
||||
* @param object 侦听函数绑定的this对象
|
||||
*/
|
||||
/** 获取资源模块 */
|
||||
get res(): GameResModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Res);
|
||||
}
|
||||
|
||||
/** 获取音频模块 */
|
||||
get audio(): GameAudioModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Audio);
|
||||
}
|
||||
|
||||
/** 获取按钮模块 */
|
||||
get button(): GameButtonModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Button);
|
||||
}
|
||||
|
||||
/** 获取键盘模块 */
|
||||
get keyboard(): GameKeyboardModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Keyboard);
|
||||
}
|
||||
|
||||
/** 移除当前节点 */
|
||||
remove() {
|
||||
oops.gui.removeByNode(this.node);
|
||||
}
|
||||
|
||||
/** 组件销毁时调用 */
|
||||
protected onDestroy() {
|
||||
this._viewRegistry?.destroy();
|
||||
}
|
||||
|
||||
/** 打印全局资源状态 */
|
||||
static printGlobalResStatus() {
|
||||
resAutoTracker.printStatus();
|
||||
}
|
||||
|
||||
/** 设置资源调试模式 */
|
||||
static setResDebugMode(enabled: boolean) {
|
||||
resAutoTracker.enableDebug(enabled);
|
||||
}
|
||||
|
||||
//#region ========== 兼容旧版本 API ==========
|
||||
|
||||
//#region 全局事件管理(兼容旧版本)
|
||||
/** @deprecated 请使用 this.event.watch() */
|
||||
watch<K extends keyof OopsFramework.TypedEventMap>(event: K, listener: ListenerFuncTyped<K, OopsFramework.TypedEventMap[K]>, object: any): void {
|
||||
this.event.on(event as string, listener as ListenerFunc, object);
|
||||
this.event.watch(event, listener, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听一次事件,事件响应后,该监听自动移除(强类型)
|
||||
* @param event 事件名(枚举)
|
||||
* @param listener 事件触发回调方法
|
||||
* @param object 侦听函数绑定的this对象
|
||||
*/
|
||||
/** @deprecated 请使用 this.event.watchOnce() */
|
||||
watchOnce<K extends keyof OopsFramework.TypedEventMap>(event: K, listener: ListenerFuncTyped<K, OopsFramework.TypedEventMap[K]>, object: any): void {
|
||||
this.event.once(event as string, listener as ListenerFunc, object);
|
||||
this.event.watchOnce(event, listener, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除全局事件(强类型)
|
||||
* @param event 事件名(枚举)
|
||||
* @param listener 处理事件的侦听器函数(可选)
|
||||
* @param object 侦听函数绑定的this对象(可选)
|
||||
*/
|
||||
/** @deprecated 请使用 this.event.unwatch() */
|
||||
unwatch<K extends keyof OopsFramework.TypedEventMap>(event: K, listener?: ListenerFuncTyped<K, OopsFramework.TypedEventMap[K]>, object?: any): void {
|
||||
this.event.off(event as string, listener as ListenerFunc, object);
|
||||
this.event.unwatch(event, listener, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发强类型全局事件
|
||||
* @param event 事件名(枚举)
|
||||
* @param data 事件数据
|
||||
*/
|
||||
/** @deprecated 请使用 this.event.emit() */
|
||||
emit<K extends keyof OopsFramework.TypedEventMap>(event: K, data?: OopsFramework.TypedEventMap[K]): void {
|
||||
this.event.emit(event, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发强类型异步全局事件(严格类型检查)
|
||||
* @param event 事件名(枚举)
|
||||
* @param data 事件数据(必须完全匹配类型定义)
|
||||
*/
|
||||
/** @deprecated 请使用 this.event.emitAsync() */
|
||||
emitAsync<K extends keyof OopsFramework.TypedEventMap>(event: K, data: OopsFramework.TypedEventMap[K]): Promise<void> {
|
||||
return this.event.emitAsync(event, data);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 弱类型事件方法
|
||||
/**
|
||||
* 注册全局事件
|
||||
* @param event 事件名
|
||||
* @param listener 处理事件的侦听器函数
|
||||
* @param object 侦听函数绑定的this对象
|
||||
*/
|
||||
/** @deprecated 请使用 this.event.on() */
|
||||
on(event: string, listener: ListenerFunc, object: any): void {
|
||||
this.event.on(event, listener, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听一次事件,事件响应后,该监听自动移除
|
||||
* @param event 事件名
|
||||
* @param listener 事件触发回调方法
|
||||
* @param object 侦听函数绑定的this对象
|
||||
*/
|
||||
/** @deprecated 请使用 this.event.once() */
|
||||
once(event: string, listener: ListenerFunc, object: any): void {
|
||||
this.event.once(event, listener, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除全局事件
|
||||
* @param event 事件名
|
||||
* @param listener 处理事件的侦听器函数(可选)
|
||||
* @param object 侦听函数绑定的this对象(可选)
|
||||
*/
|
||||
/** @deprecated 请使用 this.event.off() */
|
||||
off(event: string, listener?: ListenerFunc, object?: object): void {
|
||||
this.event.off(event, listener, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发全局事件
|
||||
* @param event 事件名
|
||||
* @param args 事件参数
|
||||
*/
|
||||
/** @deprecated 请使用 this.event.dispatchEvent() */
|
||||
dispatchEvent(event: string, ...args: any[]): void {
|
||||
this.event.dispatchEvent(event, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发全局事件,支持同步与异步处理
|
||||
* @param event 事件名
|
||||
* @param args 事件参数
|
||||
*/
|
||||
/** @deprecated 请使用 this.event.dispatchEventAsync() */
|
||||
dispatchEventAsync(event: string, ...args: any[]): Promise<void> {
|
||||
return this.event.dispatchEventAsync(event, ...args);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 预制节点管理
|
||||
|
||||
/** 摊平的节点集合(所有节点不能重名) */
|
||||
nodes: Map<string, Node> = null!;
|
||||
|
||||
/** 通过节点名获取预制上的节点,整个预制不能有重名节点 */
|
||||
//#region 预制节点管理(兼容旧版本)
|
||||
/** @deprecated 请使用 this.nodes.getNode() */
|
||||
getNode(name: string): Node | undefined {
|
||||
if (this.nodes) {
|
||||
return this.nodes.get(name);
|
||||
}
|
||||
return undefined;
|
||||
return this.nodes.getNode(name);
|
||||
}
|
||||
|
||||
/** 平摊所有节点存到Map<string, Node>中通过get(name: string)方法获取 */
|
||||
nodeTreeInfoLite() {
|
||||
this.nodes = new Map();
|
||||
ViewUtil.nodeTreeInfoLite(this.node, this.nodes);
|
||||
/** @deprecated 请使用 this.nodes.nodeTreeInfoLite() */
|
||||
nodeTreeInfoLite(): void {
|
||||
this.nodes.nodeTreeInfoLite();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从资源缓存中找到预制资源名并创建一个显示对象
|
||||
* @param path 资源路径
|
||||
* @param bundleName 资源包名
|
||||
* @returns 预制节点,加载失败返回 null
|
||||
*/
|
||||
/** @deprecated 请使用 this.nodes.createPrefabNode() */
|
||||
async createPrefabNode(path: string, bundleName: string = oops.res.defaultBundleName): Promise<Node | null> {
|
||||
const prefab = await this.load(bundleName, path, Prefab);
|
||||
if (!prefab) {
|
||||
console.warn('[OopsFramework]', `预制体加载失败: ${path}`);
|
||||
return null;
|
||||
}
|
||||
return instantiate(prefab);
|
||||
return this.nodes.createPrefabNode(path, bundleName);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 资源加载管理
|
||||
/**
|
||||
* 获取资源
|
||||
* @param path 资源路径
|
||||
* @param type 资源类型
|
||||
* @param bundleName 远程资源包名
|
||||
*/
|
||||
//#region 资源加载管理(兼容旧版本)
|
||||
/** @deprecated 请使用 this.res.getRes() */
|
||||
getRes<T extends Asset>(path: string, type?: __private.__types_globals__Constructor<T> | null, bundleName?: string): T | null {
|
||||
return oops.res.get(path, type, bundleName);
|
||||
return this.res.getRes(path, type, bundleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载一个资源(自动管理引用计数)
|
||||
* @param bundleName 远程包名
|
||||
* @param paths 资源路径
|
||||
* @param type 资源类型
|
||||
* @param onProgress 加载进度回调
|
||||
* @remarks
|
||||
* - 资源引用会自动注册到全局管理器
|
||||
* - 组件销毁时会自动减少引用计数
|
||||
* - 只有引用计数为0时才会真正释放资源
|
||||
*/
|
||||
/** @deprecated 请使用 this.res.load() */
|
||||
async load<T extends Asset>(bundleName: string, paths: Paths | AssetType<T>, type?: AssetType<T>): Promise<T> {
|
||||
let realBundle: string;
|
||||
let realPath: string;
|
||||
|
||||
if (typeof paths === 'string') {
|
||||
realBundle = bundleName;
|
||||
realPath = paths;
|
||||
}
|
||||
else {
|
||||
realBundle = oops.res.defaultBundleName;
|
||||
realPath = bundleName;
|
||||
}
|
||||
|
||||
resRef.addRef(realBundle, realPath, this);
|
||||
|
||||
try {
|
||||
const result = await oops.res.load(bundleName, paths, type);
|
||||
if (!result) {
|
||||
resRef.removeRef(realBundle, realPath, this);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
resRef.removeRef(realBundle, realPath, this);
|
||||
throw error;
|
||||
}
|
||||
return this.res.load(bundleName, paths, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定资源包中的多个任意类型资源(回调模式)
|
||||
* @param bundleName 远程包名或资源路径数组
|
||||
* @param paths 资源路径数组或进度回调
|
||||
* @param onProgress 加载进度回调
|
||||
* @param onComplete 加载完成回调
|
||||
*/
|
||||
/** @deprecated 请使用 this.res.loadAny() */
|
||||
loadAny(bundleName: string | string[], paths: string[] | ProgressCallback, onProgress?: ProgressCallback | CompleteCallback, onComplete?: CompleteCallback): void {
|
||||
const originalComplete = onComplete as ((err: Error | null, data: Asset[]) => void) | undefined;
|
||||
const pathsToTrack: { bundle: string; path: string }[] = [];
|
||||
|
||||
if (typeof bundleName === 'string' && Array.isArray(paths)) {
|
||||
paths.forEach(p => {
|
||||
resRef.addRef(bundleName, p, this);
|
||||
pathsToTrack.push({ bundle: bundleName, path: p });
|
||||
});
|
||||
}
|
||||
else if (Array.isArray(bundleName)) {
|
||||
bundleName.forEach(p => {
|
||||
resRef.addRef(resLoader.defaultBundleName, p, this);
|
||||
pathsToTrack.push({ bundle: resLoader.defaultBundleName, path: p });
|
||||
});
|
||||
}
|
||||
else if (typeof bundleName === 'string' && typeof paths === 'function') {
|
||||
resRef.addRef(resLoader.defaultBundleName, bundleName, this);
|
||||
pathsToTrack.push({ bundle: resLoader.defaultBundleName, path: bundleName });
|
||||
}
|
||||
|
||||
const wrappedComplete = (err: Error | null, data: Asset[]) => {
|
||||
if (err || !data) {
|
||||
pathsToTrack.forEach(({ bundle, path }) => {
|
||||
resRef.removeRef(bundle, path, this);
|
||||
});
|
||||
}
|
||||
originalComplete?.(err, data);
|
||||
};
|
||||
|
||||
oops.res.loadAny(bundleName, paths, onProgress, wrappedComplete);
|
||||
this.res.loadAny(bundleName, paths, onProgress, onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载文件夹中的资源(回调模式)
|
||||
* @param bundleName 远程包名
|
||||
* @param dir 文件夹名
|
||||
* @param type 资源类型
|
||||
* @param onProgress 加载进度回调
|
||||
* @param onComplete 加载完成回调
|
||||
*/
|
||||
/** @deprecated 请使用 this.res.loadDir() */
|
||||
loadDir<T extends Asset>(bundleName: string, dir: string, type: AssetType<T>, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
|
||||
loadDir<T extends Asset>(bundleName: string, dir: string, onProgress: ProgressCallback, onComplete: CompleteCallback): void;
|
||||
loadDir<T extends Asset>(bundleName: string, dir: string, onComplete?: CompleteCallback): void;
|
||||
@@ -307,333 +206,101 @@ export class GameComponent extends Component {
|
||||
onProgress?: ProgressCallback | CompleteCallback,
|
||||
onComplete?: CompleteCallback,
|
||||
): void {
|
||||
let realDir: string;
|
||||
let realBundle: string;
|
||||
if (typeof dir === 'string') {
|
||||
realDir = dir;
|
||||
realBundle = bundleName;
|
||||
}
|
||||
else {
|
||||
realDir = bundleName;
|
||||
realBundle = oops.res.defaultBundleName;
|
||||
}
|
||||
|
||||
resRef.addRef(realBundle, realDir, this);
|
||||
|
||||
const originalComplete = onComplete as ((err: Error | null, data: T[]) => void) | undefined;
|
||||
const wrappedComplete = (err: Error | null, data: T[]) => {
|
||||
if (err || !data) {
|
||||
resRef.removeRef(realBundle, realDir, this);
|
||||
}
|
||||
originalComplete?.(err, data);
|
||||
};
|
||||
|
||||
oops.res.loadDir(bundleName, dir, type, onProgress, wrappedComplete);
|
||||
this.res.loadDir(bundleName, dir, type, onProgress, onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动释放指定资源引用
|
||||
* @param path 资源路径
|
||||
* @param bundleName 资源包名
|
||||
* @remarks
|
||||
* - 只减少当前组件对该资源的引用计数
|
||||
* - 只有引用计数为0时才会真正释放资源
|
||||
* - 其他组件的引用不受影响
|
||||
*/
|
||||
releaseRes(path: string, bundleName: string = resLoader.defaultBundleName) {
|
||||
resRef.removeRef(bundleName, path, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放当前组件所有资源引用
|
||||
* @remarks
|
||||
* - 自动减少所有资源的引用计数
|
||||
* - 只有引用计数为0的资源才会被真正释放
|
||||
* - 共享资源不会被错误释放
|
||||
*/
|
||||
release() {
|
||||
const released = resRef.releaseAllByComponent(this);
|
||||
if (released.length > 0) {
|
||||
console.log(`[GameComponent] ${this.node?.name} 释放了 ${released.length} 个资源:`, released);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放所有文件夹资源引用
|
||||
* @deprecated 文件夹资源现在也通过全局引用计数管理,直接调用 release() 即可
|
||||
*/
|
||||
releaseDir() {
|
||||
console.warn('[GameComponent] releaseDir() 已废弃,请直接使用 release()');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源的全局引用计数
|
||||
* @param path 资源路径
|
||||
* @param bundleName 资源包名
|
||||
* @returns 全局引用计数
|
||||
*/
|
||||
getResRefCount(path: string, bundleName: string = resLoader.defaultBundleName): number {
|
||||
return resRef.getRefCount(bundleName, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源的所有引用者
|
||||
* @param path 资源路径
|
||||
* @param bundleName 资源包名
|
||||
* @returns 引用者列表
|
||||
*/
|
||||
getResReferrers(path: string, bundleName: string = resLoader.defaultBundleName): string[] {
|
||||
return resRef.getReferrers(bundleName, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印当前组件的资源引用情况
|
||||
*/
|
||||
printResUsage() {
|
||||
resRef.printComponentStatus(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印全局资源引用状态(调试用)
|
||||
*/
|
||||
static printGlobalResStatus() {
|
||||
resRef.printStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启/关闭全局资源调试模式
|
||||
* @param enabled 是否开启
|
||||
*/
|
||||
static setResDebugMode(enabled: boolean) {
|
||||
resRef.enableDebug(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图片资源
|
||||
* @param target 目标精灵对象
|
||||
* @param path 图片资源地址
|
||||
* @param bundle 资源包名
|
||||
* @returns 是否设置成功
|
||||
* @remarks 资源引用计数由 load 方法自动管理,加载失败时会自动回滚
|
||||
*/
|
||||
/** @deprecated 请使用 this.res.setSprite() */
|
||||
async setSprite(target: Sprite, path: string, bundle: string = resLoader.defaultBundleName): Promise<boolean> {
|
||||
const spriteFrame = await this.load(bundle, path, SpriteFrame);
|
||||
if (!spriteFrame) {
|
||||
return false;
|
||||
}
|
||||
if (!isValid(target)) {
|
||||
this.releaseRes(path, bundle);
|
||||
return false;
|
||||
}
|
||||
target.spriteFrame = spriteFrame;
|
||||
return true;
|
||||
return this.res.setSprite(target, path, bundle);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 音频播放管理
|
||||
/**
|
||||
* 播放背景音乐(不受自动释放资源管理)
|
||||
* @param url 资源地址
|
||||
* @param params 背景音乐资源播放参数
|
||||
*/
|
||||
playMusic(url: string, params?: IAudioParams) {
|
||||
oops.audio.music.loadAndPlay(url, params);
|
||||
//#region 音频播放管理(兼容旧版本)
|
||||
/** @deprecated 请使用 this.audio.playMusic() */
|
||||
playMusic(url: string, params?: IAudioParams): void {
|
||||
this.audio.playMusic(url, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放音效
|
||||
* @param url 资源地址
|
||||
* @param params 音效播放参数
|
||||
* @returns 音效实例,播放失败返回 null
|
||||
* @remarks 注意:音效资源由 AudioEffectPool 自动管理,不需要在此组件中记录
|
||||
*/
|
||||
/** @deprecated 请使用 this.audio.playEffect() */
|
||||
playEffect(url: string, params?: IAudioParams): Promise<AudioEffect | null> {
|
||||
return new Promise((resolve) => {
|
||||
if (params == null) {
|
||||
params = { bundle: resLoader.defaultBundleName };
|
||||
}
|
||||
else if (params.bundle == null) {
|
||||
params.bundle = resLoader.defaultBundleName;
|
||||
}
|
||||
|
||||
oops.audio.playEffect(url, params).then((ae) => {
|
||||
resolve(ae ?? null);
|
||||
});
|
||||
});
|
||||
return this.audio.playEffect(url, params);
|
||||
}
|
||||
//#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 = true) {
|
||||
this._buttonEnabled = 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 match = this.name.match(regex);
|
||||
if (!match || !match[1]) {
|
||||
console.warn('[OopsFramework]', `组件名 "${this.name}" 不符合 "<组件名>" 格式,跳过按钮事件绑定`);
|
||||
return;
|
||||
}
|
||||
const componentName = match[1];
|
||||
const buttons = this.node.getComponentsInChildren<Button>(Button);
|
||||
buttons.forEach((b: Button) => {
|
||||
const node = b.node;
|
||||
const self: any = this;
|
||||
const func = self[node.name];
|
||||
if (func) {
|
||||
const event = new EventHandler();
|
||||
event.target = this.node;
|
||||
event.handler = b.node.name;
|
||||
event.component = componentName;
|
||||
b.clickEvents.push(event);
|
||||
}
|
||||
// else {
|
||||
// console.warn(`名为【${node.name}】的按钮事件方法不存在`);
|
||||
// }
|
||||
});
|
||||
//#region 游戏逻辑事件(兼容旧版本)
|
||||
/** @deprecated 请使用 this.button.setButton() */
|
||||
protected setButton(bindRootEvent = true): void {
|
||||
this.button.setButton(bindRootEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置全局事件
|
||||
* @example
|
||||
* this.setEvent("onGlobal");
|
||||
* this.dispatchEvent("onGlobal", "全局事件");
|
||||
*
|
||||
* onGlobal(event: string, args: any) { console.log(args) };
|
||||
*/
|
||||
protected setEvent(...args: string[]) {
|
||||
const self: any = this;
|
||||
for (const name of args) {
|
||||
const func = self[name];
|
||||
if (func)
|
||||
this.on(name, func, this);
|
||||
else
|
||||
console.error(`名为【${name}】的全局事方法不存在`);
|
||||
}
|
||||
/** @deprecated 请使用 this.event.setEvent() */
|
||||
protected setEvent(...args: string[]): void {
|
||||
this.event.setEvent(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 键盘事件开关
|
||||
* @param on 打开键盘事件为true
|
||||
*/
|
||||
setKeyboard(on: boolean) {
|
||||
/** @deprecated 请使用 this.keyboard.setKeyboard() */
|
||||
setKeyboard(on: boolean): void {
|
||||
if (on) {
|
||||
this._keyboardEnabled = true;
|
||||
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
||||
input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
|
||||
input.on(Input.EventType.KEY_PRESSING, this.onKeyPressing, this);
|
||||
this.keyboard.setKeyboard(true, {
|
||||
onKeyDown: this.onKeyDown.bind(this),
|
||||
onKeyUp: this.onKeyUp.bind(this),
|
||||
onKeyPressing: this.onKeyPressing.bind(this)
|
||||
});
|
||||
}
|
||||
else {
|
||||
this._keyboardEnabled = false;
|
||||
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
||||
input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
|
||||
input.off(Input.EventType.KEY_PRESSING, this.onKeyPressing, this);
|
||||
this.keyboard.setKeyboard(false);
|
||||
}
|
||||
}
|
||||
|
||||
/** 键按下 */
|
||||
protected onKeyDown(event: EventKeyboard) { }
|
||||
/** @deprecated 请使用 this.keyboard.setKeyboard() 传入 callbacks */
|
||||
protected onKeyDown(event: EventKeyboard): void { }
|
||||
|
||||
/** 键放开 */
|
||||
protected onKeyUp(event: EventKeyboard) { }
|
||||
/** @deprecated 请使用 this.keyboard.setKeyboard() 传入 callbacks */
|
||||
protected onKeyUp(event: EventKeyboard): void { }
|
||||
|
||||
/** 键长按 */
|
||||
protected onKeyPressing(event: EventKeyboard) { }
|
||||
/** @deprecated 请使用 this.keyboard.setKeyboard() 传入 callbacks */
|
||||
protected onKeyPressing(event: EventKeyboard): void { }
|
||||
|
||||
/** 监听游戏从后台进入事件 */
|
||||
protected setGameShow() {
|
||||
this.on(EventMessage.GAME_SHOW, this.onGameShow, this);
|
||||
/** @deprecated 请使用 this.event.setGameShow() */
|
||||
protected setGameShow(): void {
|
||||
this.event.setGameShow(this.onGameShow.bind(this));
|
||||
}
|
||||
|
||||
/** 监听游戏切到后台事件 */
|
||||
protected setGameHide() {
|
||||
this.on(EventMessage.GAME_HIDE, this.onGameHide, this);
|
||||
/** @deprecated 请使用 this.event.setGameHide() */
|
||||
protected setGameHide(): void {
|
||||
this.event.setGameHide(this.onGameHide.bind(this));
|
||||
}
|
||||
|
||||
/** 监听游戏画笔尺寸变化事件 */
|
||||
protected setGameResize() {
|
||||
this.on(EventMessage.GAME_RESIZE, this.onGameResize, this);
|
||||
/** @deprecated 请使用 this.event.setGameResize() */
|
||||
protected setGameResize(): void {
|
||||
this.event.setGameResize(this.onGameResize.bind(this));
|
||||
}
|
||||
|
||||
/** 监听游戏全屏事件 */
|
||||
protected setGameFullScreen() {
|
||||
this.on(EventMessage.GAME_FULL_SCREEN, this.onGameFullScreen, this);
|
||||
/** @deprecated 请使用 this.event.setGameFullScreen() */
|
||||
protected setGameFullScreen(): void {
|
||||
this.event.setGameFullScreen(this.onGameFullScreen.bind(this));
|
||||
}
|
||||
|
||||
/** 监听游戏旋转屏幕事件 */
|
||||
protected setGameOrientation() {
|
||||
this.on(EventMessage.GAME_ORIENTATION, this.onGameOrientation, this);
|
||||
/** @deprecated 请使用 this.event.setGameOrientation() */
|
||||
protected setGameOrientation(): void {
|
||||
this.event.setGameOrientation(this.onGameOrientation.bind(this));
|
||||
}
|
||||
|
||||
/** 游戏从后台进入事件回调 */
|
||||
/** @deprecated 请配合 setGameShow() 使用 */
|
||||
protected onGameShow(): void { }
|
||||
|
||||
/** 游戏切到后台事件回调 */
|
||||
/** @deprecated 请配合 setGameHide() 使用 */
|
||||
protected onGameHide(): void { }
|
||||
|
||||
/** 游戏画笔尺寸变化事件回调 */
|
||||
/** @deprecated 请配合 setGameResize() 使用 */
|
||||
protected onGameResize(): void { }
|
||||
|
||||
/** 游戏全屏事件回调 */
|
||||
/** @deprecated 请配合 setGameFullScreen() 使用 */
|
||||
protected onGameFullScreen(): void { }
|
||||
|
||||
/** 游戏旋转屏幕事件回调 */
|
||||
/** @deprecated 请配合 setGameOrientation() 使用 */
|
||||
protected onGameOrientation(): void { }
|
||||
//#endregion
|
||||
|
||||
/** 移除自己 */
|
||||
remove() {
|
||||
oops.gui.removeByNode(this.node);
|
||||
}
|
||||
|
||||
protected onDestroy() {
|
||||
if (this._keyboardEnabled) {
|
||||
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
||||
input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
|
||||
input.off(Input.EventType.KEY_PRESSING, this.onKeyPressing, this);
|
||||
this._keyboardEnabled = false;
|
||||
}
|
||||
|
||||
if (this._buttonEnabled) {
|
||||
this.node.off(Node.EventType.TOUCH_END);
|
||||
this._buttonEnabled = false;
|
||||
}
|
||||
|
||||
if (this._event) {
|
||||
this._event.clear();
|
||||
this._event = null;
|
||||
}
|
||||
|
||||
if (this.nodes) {
|
||||
this.nodes.clear();
|
||||
this.nodes = null!;
|
||||
}
|
||||
|
||||
this.release();
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
9
assets/module/common/view.meta
Normal file
9
assets/module/common/view.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "d2d11e7b-7d36-4417-8e97-537c7219488a",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
84
assets/module/common/view/GameAudioModule.ts
Normal file
84
assets/module/common/view/GameAudioModule.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
*/
|
||||
import { oops } from '../../../core/Oops';
|
||||
import type { AudioEffect } from '../../../core/common/audio/AudioEffect';
|
||||
import type { IAudioParams } from '../../../core/common/audio/IAudio';
|
||||
import { resLoader } from '../../../core/common/loader/ResLoader';
|
||||
import { GameViewModule } from './GameViewModuleBase';
|
||||
|
||||
/** 音频资源使用记录 */
|
||||
interface IAudioUsage {
|
||||
/** 资源路径 */
|
||||
path: string;
|
||||
/** 资源包名 */
|
||||
bundle: string | null;
|
||||
}
|
||||
|
||||
/** 音频播放 */
|
||||
export class GameAudioModule extends GameViewModule {
|
||||
/** 当前界面使用的音效资源记录 */
|
||||
private usedAudios: IAudioUsage[] = [];
|
||||
/** 是否已销毁 */
|
||||
private isDestroyed = false;
|
||||
|
||||
/** 播放背景音乐(全局唯一,不由界面管理生命周期)
|
||||
* @param url 音频资源路径
|
||||
* @param params 音频参数
|
||||
*/
|
||||
playMusic(url: string, params?: IAudioParams): void {
|
||||
oops.audio.music.loadAndPlay(url, params);
|
||||
}
|
||||
|
||||
/** 播放音效
|
||||
* @param url 音频资源路径
|
||||
* @param params 音频参数
|
||||
* @returns 音效对象
|
||||
*/
|
||||
playEffect(url: string, params?: IAudioParams): Promise<AudioEffect | null> {
|
||||
return new Promise((resolve) => {
|
||||
if (params == null) {
|
||||
params = { bundle: resLoader.defaultBundleName };
|
||||
}
|
||||
else if (params.bundle == null) {
|
||||
params.bundle = resLoader.defaultBundleName;
|
||||
}
|
||||
|
||||
oops.audio.playEffect(url, params).then((ae) => {
|
||||
// 资源加载成功且界面未销毁时才记录
|
||||
if (ae && !this.isDestroyed) {
|
||||
this.recordAudioUsage(url, params!.bundle);
|
||||
}
|
||||
resolve(ae ?? null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录音频资源使用
|
||||
* @param path 资源路径
|
||||
* @param bundle 资源包名
|
||||
*/
|
||||
private recordAudioUsage(path: string, bundle?: string): void {
|
||||
const usage: IAudioUsage = {
|
||||
path,
|
||||
bundle: bundle || null
|
||||
};
|
||||
this.usedAudios.push(usage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件销毁时释放所有使用的音效资源
|
||||
*/
|
||||
destroy(): void {
|
||||
this.isDestroyed = true;
|
||||
|
||||
// 释放音效资源
|
||||
for (const usage of this.usedAudios) {
|
||||
oops.audio.effect.releaseResByPath(usage.path, usage.bundle || undefined);
|
||||
}
|
||||
this.usedAudios = [];
|
||||
}
|
||||
}
|
||||
9
assets/module/common/view/GameAudioModule.ts.meta
Normal file
9
assets/module/common/view/GameAudioModule.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "68e344ff-705b-4472-9053-bc70bd7363da",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
47
assets/module/common/view/GameButtonModule.ts
Normal file
47
assets/module/common/view/GameButtonModule.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
*/
|
||||
import type { EventTouch } from 'cc';
|
||||
import { Button, EventHandler, Node } from 'cc';
|
||||
import { GameViewModule } from './GameViewModuleBase';
|
||||
|
||||
/** 界面按钮批量绑定 */
|
||||
export class GameButtonModule extends GameViewModule {
|
||||
/** 设置按钮事件绑定
|
||||
* @param bindRootEvent 是否绑定根节点事件,默认为 true
|
||||
*/
|
||||
setButton(bindRootEvent = true): void {
|
||||
if (bindRootEvent) {
|
||||
this.comp.node.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
|
||||
const self: any = this.comp;
|
||||
const func = self[event.target.name];
|
||||
if (func) {
|
||||
func.call(this.comp, event);
|
||||
}
|
||||
}, this.comp);
|
||||
}
|
||||
|
||||
const regex = /<([^>]+)>/;
|
||||
const match = this.comp.name.match(regex);
|
||||
if (!match || !match[1]) {
|
||||
console.warn('[OopsFramework]', `组件名 "${this.comp.name}" 不符合 "<组件名>" 格式,跳过按钮事件绑定`);
|
||||
return;
|
||||
}
|
||||
const componentName = match[1];
|
||||
const buttons = this.comp.node.getComponentsInChildren<Button>(Button);
|
||||
buttons.forEach((b: Button) => {
|
||||
const node = b.node;
|
||||
const self: any = this.comp;
|
||||
const func = self[node.name];
|
||||
if (func) {
|
||||
const event = new EventHandler();
|
||||
event.target = this.comp.node;
|
||||
event.handler = b.node.name;
|
||||
event.component = componentName;
|
||||
b.clickEvents.push(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
9
assets/module/common/view/GameButtonModule.ts.meta
Normal file
9
assets/module/common/view/GameButtonModule.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "48bfef5e-fe8b-4ad4-88be-cd1872153689",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
181
assets/module/common/view/GameEventModule.ts
Normal file
181
assets/module/common/view/GameEventModule.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
*/
|
||||
import { EventDispatcher } from '../../../core/common/event/EventDispatcher';
|
||||
import { EventMessage, type ListenerFunc, type ListenerFuncTyped } from '../../../core/common/event/EventMessage';
|
||||
import { GameViewModule } from './GameViewModuleBase';
|
||||
|
||||
/** 全局事件管理(含游戏前后台、画布、全屏、旋转等生命周期) */
|
||||
export class GameEventModule extends GameViewModule {
|
||||
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 监听对象
|
||||
*/
|
||||
watch<K extends keyof OopsFramework.TypedEventMap>(
|
||||
event: K,
|
||||
listener: ListenerFuncTyped<K, OopsFramework.TypedEventMap[K]>,
|
||||
object: object,
|
||||
): void {
|
||||
this.event.on(event as string, listener as ListenerFunc, object);
|
||||
}
|
||||
|
||||
/** 监听事件(只触发一次)
|
||||
* @param event 事件类型
|
||||
* @param listener 监听回调
|
||||
* @param object 监听对象
|
||||
*/
|
||||
watchOnce<K extends keyof OopsFramework.TypedEventMap>(
|
||||
event: K,
|
||||
listener: ListenerFuncTyped<K, OopsFramework.TypedEventMap[K]>,
|
||||
object: object,
|
||||
): void {
|
||||
this.event.once(event as string, listener as ListenerFunc, object);
|
||||
}
|
||||
|
||||
/** 取消监听事件
|
||||
* @param event 事件类型
|
||||
* @param listener 监听回调
|
||||
* @param object 监听对象
|
||||
*/
|
||||
unwatch<K extends keyof OopsFramework.TypedEventMap>(
|
||||
event: K,
|
||||
listener?: ListenerFuncTyped<K, OopsFramework.TypedEventMap[K]>,
|
||||
object?: object,
|
||||
): void {
|
||||
this.event.off(event as string, listener as ListenerFunc, object);
|
||||
}
|
||||
|
||||
/** 触发事件
|
||||
* @param event 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
emit<K extends keyof OopsFramework.TypedEventMap>(event: K, data?: OopsFramework.TypedEventMap[K]): void {
|
||||
this.event.emit(event, data);
|
||||
}
|
||||
|
||||
/** 异步触发事件
|
||||
* @param event 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
emitAsync<K extends keyof OopsFramework.TypedEventMap>(event: K, data: OopsFramework.TypedEventMap[K]): Promise<void> {
|
||||
return this.event.emitAsync(event, data);
|
||||
}
|
||||
|
||||
/** 监听事件
|
||||
* @param event 事件名称
|
||||
* @param listener 监听回调
|
||||
* @param object 监听对象
|
||||
*/
|
||||
on(event: string, listener: ListenerFunc, object: object): void {
|
||||
this.event.on(event, listener, object);
|
||||
}
|
||||
|
||||
/** 监听事件(只触发一次)
|
||||
* @param event 事件名称
|
||||
* @param listener 监听回调
|
||||
* @param object 监听对象
|
||||
*/
|
||||
once(event: string, listener: ListenerFunc, object: object): void {
|
||||
this.event.once(event, listener, object);
|
||||
}
|
||||
|
||||
/** 取消监听事件
|
||||
* @param event 事件名称
|
||||
* @param listener 监听回调
|
||||
* @param object 监听对象
|
||||
*/
|
||||
off(event: string, listener?: ListenerFunc, object?: object): void {
|
||||
this.event.off(event, listener, object);
|
||||
}
|
||||
|
||||
/** 分发事件
|
||||
* @param event 事件名称
|
||||
* @param args 事件参数
|
||||
*/
|
||||
dispatchEvent(event: string, ...args: any[]): void {
|
||||
this.event.dispatchEvent(event, ...args);
|
||||
}
|
||||
|
||||
/** 异步分发事件
|
||||
* @param event 事件名称
|
||||
* @param args 事件参数
|
||||
*/
|
||||
dispatchEventAsync(event: string, ...args: any[]): Promise<void> {
|
||||
return this.event.dispatchEventAsync(event, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置全局事件(按组件上的方法名绑定)
|
||||
* @example
|
||||
* this.event.setEvent('onGlobal');
|
||||
* onGlobal(event: string, args: any) { console.log(args); }
|
||||
*/
|
||||
setEvent(...args: string[]): void {
|
||||
const self: any = this.comp;
|
||||
for (const name of args) {
|
||||
const func = self[name];
|
||||
if (func) {
|
||||
this.on(name, func, this.comp);
|
||||
}
|
||||
else {
|
||||
console.error(`名为【${name}】的全局事方法不存在`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置游戏显示回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
setGameShow(callback: () => void): void {
|
||||
this.on(EventMessage.GAME_SHOW, callback, this.comp);
|
||||
}
|
||||
|
||||
/** 设置游戏隐藏回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
setGameHide(callback: () => void): void {
|
||||
this.on(EventMessage.GAME_HIDE, callback, this.comp);
|
||||
}
|
||||
|
||||
/** 设置游戏尺寸变化回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
setGameResize(callback: () => void): void {
|
||||
this.on(EventMessage.GAME_RESIZE, callback, this.comp);
|
||||
}
|
||||
|
||||
/** 设置游戏全屏回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
setGameFullScreen(callback: () => void): void {
|
||||
this.on(EventMessage.GAME_FULL_SCREEN, callback, this.comp);
|
||||
}
|
||||
|
||||
/** 设置游戏方向变化回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
setGameOrientation(callback: () => void): void {
|
||||
this.on(EventMessage.GAME_ORIENTATION, callback, this.comp);
|
||||
}
|
||||
|
||||
/** 销毁事件模块 */
|
||||
override destroy(): void {
|
||||
if (this._event) {
|
||||
this._event.clear();
|
||||
this._event = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/module/common/view/GameEventModule.ts.meta
Normal file
9
assets/module/common/view/GameEventModule.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "b05268d5-1e17-4d87-bc27-47e100a55206",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
86
assets/module/common/view/GameKeyboardModule.ts
Normal file
86
assets/module/common/view/GameKeyboardModule.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
*/
|
||||
import type { EventKeyboard } from 'cc';
|
||||
import { Input, input } from 'cc';
|
||||
import { GameViewModule } from './GameViewModuleBase';
|
||||
|
||||
/** 键盘事件回调 */
|
||||
export interface KeyboardCallbacks {
|
||||
onKeyDown?: (event: EventKeyboard) => void;
|
||||
onKeyUp?: (event: EventKeyboard) => void;
|
||||
onKeyPressing?: (event: EventKeyboard) => void;
|
||||
}
|
||||
|
||||
/** 键盘输入 */
|
||||
export class GameKeyboardModule extends GameViewModule {
|
||||
private _enabled = false;
|
||||
private _callbacks: KeyboardCallbacks | null = null;
|
||||
|
||||
/**
|
||||
* 键盘事件开关
|
||||
* @param on 是否开启
|
||||
* @param callbacks 开启时传入对应键事件回调(可只传需要的项)
|
||||
*/
|
||||
setKeyboard(on: boolean, callbacks?: KeyboardCallbacks): void {
|
||||
if (on) {
|
||||
if (callbacks) {
|
||||
this._callbacks = callbacks;
|
||||
}
|
||||
if (!this._callbacks) {
|
||||
console.warn('[OopsFramework]', 'setKeyboard(true) 需传入 callbacks');
|
||||
return;
|
||||
}
|
||||
this._register(this._callbacks);
|
||||
this._enabled = true;
|
||||
}
|
||||
else {
|
||||
if (this._enabled && this._callbacks) {
|
||||
this._unregister(this._callbacks);
|
||||
}
|
||||
this._enabled = false;
|
||||
this._callbacks = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 销毁键盘模块 */
|
||||
override destroy(): void {
|
||||
if (this._enabled && this._callbacks) {
|
||||
this._unregister(this._callbacks);
|
||||
this._enabled = false;
|
||||
this._callbacks = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 注册键盘事件
|
||||
* @param callbacks 键盘事件回调
|
||||
*/
|
||||
private _register(callbacks: KeyboardCallbacks): void {
|
||||
if (callbacks.onKeyDown) {
|
||||
input.on(Input.EventType.KEY_DOWN, callbacks.onKeyDown, this.comp);
|
||||
}
|
||||
if (callbacks.onKeyUp) {
|
||||
input.on(Input.EventType.KEY_UP, callbacks.onKeyUp, this.comp);
|
||||
}
|
||||
if (callbacks.onKeyPressing) {
|
||||
input.on(Input.EventType.KEY_PRESSING, callbacks.onKeyPressing, this.comp);
|
||||
}
|
||||
}
|
||||
|
||||
/** 注销键盘事件
|
||||
* @param callbacks 键盘事件回调
|
||||
*/
|
||||
private _unregister(callbacks: KeyboardCallbacks): void {
|
||||
if (callbacks.onKeyDown) {
|
||||
input.off(Input.EventType.KEY_DOWN, callbacks.onKeyDown, this.comp);
|
||||
}
|
||||
if (callbacks.onKeyUp) {
|
||||
input.off(Input.EventType.KEY_UP, callbacks.onKeyUp, this.comp);
|
||||
}
|
||||
if (callbacks.onKeyPressing) {
|
||||
input.off(Input.EventType.KEY_PRESSING, callbacks.onKeyPressing, this.comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/module/common/view/GameKeyboardModule.ts.meta
Normal file
9
assets/module/common/view/GameKeyboardModule.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "bc13cdc4-f4d7-4425-8397-27382d30cc9c",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
48
assets/module/common/view/GameNodeModule.ts
Normal file
48
assets/module/common/view/GameNodeModule.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
*/
|
||||
import { instantiate, Node, Prefab } from 'cc';
|
||||
import { oops } from '../../../core/Oops';
|
||||
import { ViewUtil } from '../../../core/utils/ViewUtil';
|
||||
import { GameViewModule } from './GameViewModuleBase';
|
||||
|
||||
/** 预制节点与节点树管理 */
|
||||
export class GameNodeModule extends GameViewModule {
|
||||
/** 摊平的节点集合(所有节点不能重名) */
|
||||
readonly nodes: Map<string, Node> = new Map();
|
||||
|
||||
/** 获取节点
|
||||
* @param name 节点名称
|
||||
* @returns 节点对象
|
||||
*/
|
||||
getNode(name: string): Node | undefined {
|
||||
return this.nodes.get(name);
|
||||
}
|
||||
|
||||
/** 获取节点树信息(轻量版) */
|
||||
nodeTreeInfoLite(): void {
|
||||
this.nodes.clear();
|
||||
ViewUtil.nodeTreeInfoLite(this.comp.node, this.nodes);
|
||||
}
|
||||
|
||||
/** 创建预制体节点
|
||||
* @param path 预制体路径
|
||||
* @param bundleName 资源包名称
|
||||
* @returns 节点对象
|
||||
*/
|
||||
async createPrefabNode(path: string, bundleName: string = oops.res.defaultBundleName): Promise<Node | null> {
|
||||
const prefab = await this.comp.res.load(bundleName, path, Prefab);
|
||||
if (!prefab) {
|
||||
console.warn('[OopsFramework]', `预制体加载失败: ${path}`);
|
||||
return null;
|
||||
}
|
||||
return instantiate(prefab);
|
||||
}
|
||||
|
||||
/** 销毁节点模块 */
|
||||
override destroy(): void {
|
||||
this.nodes.clear();
|
||||
}
|
||||
}
|
||||
9
assets/module/common/view/GameNodeModule.ts.meta
Normal file
9
assets/module/common/view/GameNodeModule.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "d5ac263c-8b18-45d3-9e66-60ddb1346365",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
147
assets/module/common/view/GameResModule.ts
Normal file
147
assets/module/common/view/GameResModule.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
*/
|
||||
import type { Asset, Sprite, __private } from 'cc';
|
||||
import { SpriteFrame, assetManager, isValid } from 'cc';
|
||||
import { oops } from '../../../core/Oops';
|
||||
import type { AssetType, CompleteCallback, Paths, ProgressCallback } from '../../../core/common/loader/ResLoader';
|
||||
import { resLoader } from '../../../core/common/loader/ResLoader';
|
||||
import { resAutoTracker } from '../../../core/common/loader/ResAutoTracker';
|
||||
import { GameViewModule } from './GameViewModuleBase';
|
||||
|
||||
/** 资源加载与引用计数管理 */
|
||||
export class GameResModule extends GameViewModule {
|
||||
|
||||
/** 获取资源
|
||||
* @param path 资源路径
|
||||
* @param type 资源类型
|
||||
* @param bundleName 资源包名称
|
||||
* @returns 资源对象
|
||||
*/
|
||||
getRes<T extends Asset>(path: string, type?: __private.__types_globals__Constructor<T> | null, bundleName?: string): T | null {
|
||||
return oops.res.get(path, type, bundleName);
|
||||
}
|
||||
|
||||
/** 加载资源
|
||||
* @param bundleName 资源包名称
|
||||
* @param paths 资源路径
|
||||
* @param type 资源类型
|
||||
* @returns 资源对象
|
||||
*/
|
||||
async load<T extends Asset>(bundleName: string, paths: Paths | AssetType<T>, type?: AssetType<T>): Promise<T> {
|
||||
const result = await oops.res.load(bundleName, paths, type);
|
||||
if (result) {
|
||||
resAutoTracker.acquire(this.comp, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 加载任意资源
|
||||
* @param bundleName 资源包名称
|
||||
* @param paths 资源路径数组
|
||||
* @param onProgress 进度回调
|
||||
* @param onComplete 完成回调
|
||||
*/
|
||||
loadAny(
|
||||
bundleName: string | string[],
|
||||
paths: string[] | ProgressCallback,
|
||||
onProgress?: ProgressCallback | CompleteCallback,
|
||||
onComplete?: CompleteCallback,
|
||||
): void {
|
||||
const originalComplete = onComplete as ((err: Error | null, data: Asset[]) => void) | undefined;
|
||||
|
||||
const wrappedComplete = (err: Error | null, data: Asset[]) => {
|
||||
if (!err && data?.length) {
|
||||
resAutoTracker.acquireMany(this.comp, data);
|
||||
}
|
||||
originalComplete?.(err, data);
|
||||
};
|
||||
|
||||
oops.res.loadAny(bundleName, paths, onProgress, wrappedComplete);
|
||||
}
|
||||
|
||||
/** 加载目录资源
|
||||
* @param bundleName 资源包名称
|
||||
* @param dir 目录路径
|
||||
* @param type 资源类型
|
||||
* @param onProgress 进度回调
|
||||
* @param onComplete 完成回调
|
||||
*/
|
||||
loadDir<T extends Asset>(
|
||||
bundleName: string,
|
||||
dir?: string | AssetType<T> | ProgressCallback | CompleteCallback,
|
||||
type?: AssetType<T> | ProgressCallback | CompleteCallback,
|
||||
onProgress?: ProgressCallback | CompleteCallback,
|
||||
onComplete?: CompleteCallback,
|
||||
): void {
|
||||
const originalComplete = onComplete as ((err: Error | null, data: T[]) => void) | undefined;
|
||||
const wrappedComplete = (err: Error | null, data: T[]) => {
|
||||
if (!err && data?.length) {
|
||||
resAutoTracker.acquireMany(this.comp, data);
|
||||
}
|
||||
originalComplete?.(err, data);
|
||||
};
|
||||
|
||||
oops.res.loadDir(bundleName, dir, type, onProgress, wrappedComplete);
|
||||
}
|
||||
|
||||
/** 释放资源
|
||||
* @param path 资源路径
|
||||
* @param bundleName 资源包名称
|
||||
*/
|
||||
releaseRes(path: string, bundleName: string = resLoader.defaultBundleName): void {
|
||||
resAutoTracker.releaseByPath(this.comp, path, bundleName);
|
||||
}
|
||||
|
||||
/** 销毁资源模块 */
|
||||
override destroy(): void {
|
||||
const released = resAutoTracker.releaseAll(this.comp);
|
||||
if (released > 0) {
|
||||
console.log(`[GameComponent] ${this.comp.node?.name} 释放 ${released} 条资源登记`);
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取资源引用计数
|
||||
* @param path 资源路径
|
||||
* @param bundleName 资源包名称
|
||||
* @returns 引用计数
|
||||
*/
|
||||
getResRefCount(path: string, bundleName: string = resLoader.defaultBundleName): number {
|
||||
const bundle = assetManager.getBundle(bundleName);
|
||||
const a = bundle?.get(path) as Asset | null;
|
||||
return a ? a.refCount : 0;
|
||||
}
|
||||
|
||||
/** 获取追踪的资源根节点数量
|
||||
* @returns 资源根节点数量
|
||||
*/
|
||||
getTrackedResRootCount(): number {
|
||||
return resAutoTracker.getOwnerEntryCount(this.comp);
|
||||
}
|
||||
|
||||
/** 打印资源使用情况 */
|
||||
printResUsage(): void {
|
||||
resAutoTracker.printOwnerStatus(this.comp);
|
||||
}
|
||||
|
||||
/** 设置精灵图片
|
||||
* @param target 精灵组件
|
||||
* @param path 图片路径
|
||||
* @param bundle 资源包名称
|
||||
* @returns 是否设置成功
|
||||
*/
|
||||
async setSprite(target: Sprite, path: string, bundle: string = resLoader.defaultBundleName): Promise<boolean> {
|
||||
const spriteFrame = await this.load(bundle, path, SpriteFrame);
|
||||
if (!spriteFrame) {
|
||||
return false;
|
||||
}
|
||||
if (!isValid(target)) {
|
||||
this.releaseRes(path, bundle);
|
||||
return false;
|
||||
}
|
||||
target.spriteFrame = spriteFrame;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
9
assets/module/common/view/GameResModule.ts.meta
Normal file
9
assets/module/common/view/GameResModule.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "b834447c-db2b-48c2-b13d-7d986331afcd",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
17
assets/module/common/view/GameViewModuleBase.ts
Normal file
17
assets/module/common/view/GameViewModuleBase.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
*/
|
||||
import type { GameComponent } from '../GameComponent';
|
||||
|
||||
/** GameComponent 下 view 子模块基类 */
|
||||
export abstract class GameViewModule {
|
||||
/** 构造函数
|
||||
* @param comp 游戏组件
|
||||
*/
|
||||
constructor(protected readonly comp: GameComponent) {}
|
||||
|
||||
/** 组件销毁时回调,子类按需覆盖 */
|
||||
destroy(): void {}
|
||||
}
|
||||
9
assets/module/common/view/GameViewModuleBase.ts.meta
Normal file
9
assets/module/common/view/GameViewModuleBase.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "f8bc2eb5-9b07-480d-a77a-321f288bde2f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
90
assets/module/common/view/GameViewModuleRegistry.ts
Normal file
90
assets/module/common/view/GameViewModuleRegistry.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
*/
|
||||
import type { GameComponent } from '../GameComponent';
|
||||
import { GameAudioModule } from './GameAudioModule';
|
||||
import { GameButtonModule } from './GameButtonModule';
|
||||
import { GameEventModule } from './GameEventModule';
|
||||
import { GameKeyboardModule } from './GameKeyboardModule';
|
||||
import { GameNodeModule } from './GameNodeModule';
|
||||
import { GameResModule } from './GameResModule';
|
||||
import type { GameViewModule } from './GameViewModuleBase';
|
||||
|
||||
export { GameViewModule } from './GameViewModuleBase';
|
||||
|
||||
/**
|
||||
* view 子模块注册键
|
||||
* @remarks 枚举顺序即销毁顺序(先输入/音频/资源,最后事件)
|
||||
*/
|
||||
export enum ViewModuleKey {
|
||||
/** 按钮 */
|
||||
Button = 'button',
|
||||
/** 键盘 */
|
||||
Keyboard = 'keyboard',
|
||||
/** 音频 */
|
||||
Audio = 'audio',
|
||||
/** 资源 */
|
||||
Res = 'res',
|
||||
/** 节点树 */
|
||||
Nodes = 'nodes',
|
||||
/** 全局事件 */
|
||||
Event = 'event',
|
||||
}
|
||||
|
||||
/** view 子模块懒加载注册表(统一登记、按序批量销毁) */
|
||||
export class GameViewModuleRegistry {
|
||||
private readonly instances = new Map<ViewModuleKey, GameViewModule>();
|
||||
|
||||
/** 构造函数
|
||||
* @param comp 游戏组件
|
||||
*/
|
||||
constructor(private readonly comp: GameComponent) {}
|
||||
|
||||
/** 获取模块实例
|
||||
* @param key 模块键
|
||||
* @returns 模块实例
|
||||
*/
|
||||
get<T extends GameViewModule = GameViewModule>(key: ViewModuleKey): T {
|
||||
let module = this.instances.get(key) as T | undefined;
|
||||
if (!module) {
|
||||
module = this.createViewModule(key) as T;
|
||||
this.instances.set(key, module);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
/** 销毁所有模块 */
|
||||
destroy(): void {
|
||||
for (const key of Object.values(ViewModuleKey)) {
|
||||
this.instances.get(key)?.destroy();
|
||||
}
|
||||
this.instances.clear();
|
||||
}
|
||||
|
||||
/** 创建视图模块
|
||||
* @param key 模块键
|
||||
* @returns 模块实例
|
||||
*/
|
||||
private createViewModule(key: ViewModuleKey): GameViewModule {
|
||||
switch (key) {
|
||||
case ViewModuleKey.Event:
|
||||
return new GameEventModule(this.comp);
|
||||
case ViewModuleKey.Nodes:
|
||||
return new GameNodeModule(this.comp);
|
||||
case ViewModuleKey.Res:
|
||||
return new GameResModule(this.comp);
|
||||
case ViewModuleKey.Audio:
|
||||
return new GameAudioModule(this.comp);
|
||||
case ViewModuleKey.Button:
|
||||
return new GameButtonModule(this.comp);
|
||||
case ViewModuleKey.Keyboard:
|
||||
return new GameKeyboardModule(this.comp);
|
||||
default: {
|
||||
const _exhaustive: never = key;
|
||||
return _exhaustive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/module/common/view/GameViewModuleRegistry.ts.meta
Normal file
9
assets/module/common/view/GameViewModuleRegistry.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "ae82d7cd-1564-4c48-855f-4e9a0fc9e96f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user