mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-09-03 04:03:46 +08:00
feat (core): 完善框架核心功能模块 - 音频、对象池、资源加载与组件系统
新增与优化核心模块 🎯 GUI 交互增强 ButtonInterceptor:按钮点击事件劫持器 劫持 EventHandler.emitEvents / ButtonSimple.onClick 核心方法 支持多类型按钮音效配置注册,按按钮类型自动匹配音效 单例模式管理,支持全局动态激活 / 停用拦截功能 🧩 对象池管理系统 GameNodePool:通用游戏节点对象池管理器 基于预制体(Prefab)UUID 实现池化隔离管理 支持特效、UI 元素等高频对象预加载、复用与回收 全局单例访问,统一管控对象创建 / 销毁逻辑 🔊 音频管理系统 AudioManager:音频核心管理器 独立背景音乐(AudioMusic)+ 音效池(AudioEffectPool)双模块 支持全局音量控制、播放状态恢复与场景切换管理 AudioEffect:基础音效播放器 AudioEnum:音频类型、音效分类枚举定义 📦 资源加载系统 ResLoader:资源加载核心控制器 资源包(Bundle)统一管理 + 并发加载限流控制 远程资源本地缓存、自动释放与内存优化 完整加载进度回调 + 错误异常处理机制 ResTypes:资源类型规范定义 ResErrors:资源加载错误码与异常处理 ResUtils:资源路径、格式转换等工具方法 🧱 模块化组件系统 GameComponent:游戏显示对象组件基类 集成资源自动引用计数(RC)管理 模块化部件(GamePartRegistry)插拔式架构 内置音频、按钮、事件、键盘、节点、对象池、资源七大部件 部件封装: GamePartAudio:音频功能部件 GamePartButton:按钮交互部件 GamePartEvent:全局事件部件 GamePartKeyboard:键盘输入部件 GamePartNode:节点操作部件 GamePartNodePool:对象池调用部件 GamePartRes:资源加载部件 📝 类型定义完善 新增 IAudio 音频数据与播放参数接口 统一全模块 TS 类型定义与导出规范 优化接口注释与类型约束,提升开发体验
This commit is contained in:
BIN
assets/.DS_Store
vendored
Normal file
BIN
assets/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
assets/core/.DS_Store
vendored
Normal file
BIN
assets/core/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -1,11 +1,4 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-02-11 09:32:47
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-08-21 15:19:56
|
||||
*/
|
||||
import { DEBUG } from 'cc/env';
|
||||
import { EffectSingleCase } from '../libs/animator-effect/EffectSingleCase';
|
||||
import { ecs } from '../libs/ecs/ECS';
|
||||
import type { ECSRootSystem } from '../libs/ecs/ECSSystem';
|
||||
import { LanguageManager } from '../libs/gui/language/Language';
|
||||
@@ -57,8 +50,6 @@ export class oops {
|
||||
static ecs: ECSRootSystem = new ecs.RootSystem();
|
||||
/** MVVM */
|
||||
static mvvm = VM;
|
||||
/** 对象池 */
|
||||
static pool = EffectSingleCase.instance;
|
||||
}
|
||||
|
||||
// 引入oops全局变量以方便调试
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-07-03 16:13:17
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-08-28 10:02:57
|
||||
*/
|
||||
import { _decorator, Component, director, Game, game, JsonAsset, Node, profiler, resources, screen, sys } from 'cc';
|
||||
import { GameConfig } from '../module/config/GameConfig';
|
||||
import { GameQueryConfig } from '../module/config/GameQueryConfig';
|
||||
@@ -28,14 +22,14 @@ export class Root extends Component {
|
||||
type: Node,
|
||||
tooltip: '游戏层'
|
||||
})
|
||||
game: Node = null!; // 可使用多摄像机自定义二维或三维游戏场景
|
||||
game: Node = null!; // 可使用多摄像机自定义二维或三维游戏场景
|
||||
|
||||
/** 界面层节点 */
|
||||
@property({
|
||||
type: Node,
|
||||
tooltip: '界面层'
|
||||
})
|
||||
gui: Node = null!;
|
||||
gui: Node = null!;
|
||||
|
||||
/** 框架常驻节点 */
|
||||
private persist: Node = null!;
|
||||
@@ -46,7 +40,7 @@ export class Root extends Component {
|
||||
|
||||
this.initModule();
|
||||
this.iniStart();
|
||||
this.loadConfig().then();
|
||||
this.loadConfig();
|
||||
}
|
||||
|
||||
private initModule() {
|
||||
@@ -68,11 +62,11 @@ export class Root extends Component {
|
||||
oops.gui = new LayerManager();
|
||||
}
|
||||
|
||||
private async loadConfig() {
|
||||
private loadConfig() {
|
||||
const config_name = 'config';
|
||||
resources.load(config_name, JsonAsset, (err, config) => {
|
||||
if (err) {
|
||||
this.loadConfig().then();
|
||||
this.loadConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,18 +155,18 @@ export class Root extends Component {
|
||||
}
|
||||
|
||||
private onShow() {
|
||||
oops.timer.load(); // 处理回到游戏时减去逝去时间
|
||||
oops.audio.resumeAll(); // 恢复所有暂停的音乐播放
|
||||
director.resume(); // 恢复暂停场景的游戏逻辑,如果当前场景没有暂停将没任何事情发生
|
||||
game.resume(); // 恢复游戏主循环。包含:游戏逻辑,渲染,事件处理,背景音乐和所有音效
|
||||
oops.timer.load(); // 处理回到游戏时减去逝去时间
|
||||
oops.audio.resumeAll(); // 恢复所有暂停的音乐播放
|
||||
director.resume(); // 恢复暂停场景的游戏逻辑,如果当前场景没有暂停将没任何事情发生
|
||||
game.resume(); // 恢复游戏主循环。包含:游戏逻辑,渲染,事件处理,背景音乐和所有音效
|
||||
oops.message.dispatchEvent(EventMessage.GAME_SHOW);
|
||||
}
|
||||
|
||||
private onHide() {
|
||||
oops.timer.save(); // 处理切到后台后记录切出时间
|
||||
oops.audio.pauseAll(); // 暂停所有音乐播放
|
||||
director.pause(); // 暂停正在运行的场景,该暂停只会停止游戏逻辑执行,但是不会停止渲染和 UI 响应。 如果想要更彻底得暂停游戏,包含渲染,音频和事件
|
||||
game.pause(); // 暂停游戏主循环。包含:游戏逻辑、渲染、输入事件派发(Web 和小游戏平台除外)
|
||||
oops.timer.save(); // 处理切到后台后记录切出时间
|
||||
oops.audio.pauseAll(); // 暂停所有音乐播放
|
||||
director.pause(); // 暂停正在运行的场景,该暂停只会停止游戏逻辑执行,但是不会停止渲染和 UI 响应。 如果想要更彻底得暂停游戏,包含渲染,音频和事件
|
||||
game.pause(); // 暂停游戏主循环。包含:游戏逻辑、渲染、输入事件派发(Web 和小游戏平台除外)
|
||||
oops.message.dispatchEvent(EventMessage.GAME_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
import { AudioClip } from 'cc';
|
||||
import { resLoader } from '../loader/ResLoader';
|
||||
|
||||
/** 加载结果 */
|
||||
export interface ILoadResult {
|
||||
/** 加载成功的 AudioClip */
|
||||
clip: AudioClip;
|
||||
/** 原始路径(URL 或 bundle 内路径) */
|
||||
path: string;
|
||||
/** 资源包名(远程资源时为 null) */
|
||||
bundle: string | null;
|
||||
/** 是否为远程资源 */
|
||||
isRemote: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频资源加载器
|
||||
* 统一处理三种来源的 AudioClip 获取:
|
||||
* 1. 直接传入 AudioClip 实例
|
||||
* 2. 远程 URL 加载
|
||||
* 3. Bundle 内资源加载
|
||||
*/
|
||||
export class AudioClipLoader {
|
||||
/** 加载中缓存,避免同一资源重复加载 */
|
||||
private loadingCache: Map<string, Promise<AudioClip>> = new Map();
|
||||
/** 已加载的 AudioClip 缓存 */
|
||||
private clipCache: Map<string, AudioClip> = new Map();
|
||||
|
||||
/**
|
||||
* 从三种来源获取 AudioClip
|
||||
* @param path - AudioClip 实例、远程 URL、或 bundle 内路径
|
||||
* @param bundle - 资源包名(path 为 AudioClip 或 URL 时忽略)
|
||||
* @returns 加载结果,失败返回 null
|
||||
*/
|
||||
async load(
|
||||
path: string | AudioClip,
|
||||
bundle?: string
|
||||
): Promise<ILoadResult | null> {
|
||||
if (path instanceof AudioClip) {
|
||||
if (!path.isValid) {
|
||||
console.warn(`AudioClip 实例已失效`);
|
||||
return null;
|
||||
}
|
||||
// 外部传入的 AudioClip 实例,增加引擎引用计数
|
||||
path.addRef();
|
||||
return { clip: path, path: path.uuid, bundle: null, isRemote: false };
|
||||
}
|
||||
|
||||
const cacheKey = this.getCacheKey(path, bundle);
|
||||
const cached = this.clipCache.get(cacheKey);
|
||||
|
||||
if (cached && cached.isValid) {
|
||||
// 增加引擎引用计数
|
||||
cached.addRef();
|
||||
return { clip: cached, path, bundle: bundle || null, isRemote: path.indexOf('http') === 0 };
|
||||
}
|
||||
|
||||
if (path.indexOf('http') === 0) {
|
||||
return this.loadRemote(path);
|
||||
}
|
||||
|
||||
return this.loadBundle(path, bundle || resLoader.defaultBundleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放指定路径的音频资源引用
|
||||
* @param path - 资源路径或 URL
|
||||
* @param bundle - 资源包名(远程资源时忽略)
|
||||
*/
|
||||
release(path: string, bundle?: string): void {
|
||||
const { key, entry } = this.getCacheEntry(path, bundle);
|
||||
if (!entry) return;
|
||||
|
||||
// 减少引擎引用计数
|
||||
if (entry.isValid) {
|
||||
entry.decRef();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即释放指定路径的音频资源(不等待延迟)
|
||||
* @param path - 资源路径或 URL
|
||||
* @param bundle - 资源包名(远程资源时忽略)
|
||||
*/
|
||||
releaseImmediately(path: string, bundle?: string): void {
|
||||
const { key, entry } = this.getCacheEntry(path, bundle);
|
||||
if (!entry) return;
|
||||
|
||||
this.doRelease(key, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行真正的资源释放
|
||||
* @param key - 缓存键值
|
||||
* @param entry - 缓存条目
|
||||
*/
|
||||
private doRelease(key: string, entry: AudioClip): void {
|
||||
if (entry && entry.isValid) {
|
||||
entry.decRef();
|
||||
}
|
||||
this.clipCache.delete(key);
|
||||
}
|
||||
|
||||
/** 清空所有缓存 */
|
||||
clearCache(): void {
|
||||
this.clipCache.forEach((entry, key) => {
|
||||
this.doRelease(key, entry);
|
||||
});
|
||||
this.clipCache.clear();
|
||||
this.loadingCache.clear();
|
||||
}
|
||||
|
||||
/** 销毁加载器,释放所有资源 */
|
||||
destroy(): void {
|
||||
this.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计信息
|
||||
* @returns 缓存条目数量
|
||||
*/
|
||||
getStats(): { total: number } {
|
||||
return { total: this.clipCache.size };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存 key
|
||||
* @param path - 资源路径
|
||||
* @param bundle - 资源包名
|
||||
* @returns 缓存键值
|
||||
*/
|
||||
private getCacheKey(path: string, bundle?: string): string {
|
||||
if (path.indexOf('http') === 0) {
|
||||
return `remote_${path}`;
|
||||
}
|
||||
return `bundle_${bundle || resLoader.defaultBundleName}_${path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存条目
|
||||
* @param path - 资源路径
|
||||
* @param bundle - 资源包名
|
||||
* @returns 缓存键值和条目
|
||||
*/
|
||||
private getCacheEntry(path: string, bundle?: string): { key: string; entry: AudioClip | undefined } {
|
||||
const key = this.getCacheKey(path, bundle);
|
||||
const entry = this.clipCache.get(key);
|
||||
return { key, entry };
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存并返回加载结果
|
||||
* @param clip - 音频资源
|
||||
* @param path - 资源路径
|
||||
* @param cacheKey - 缓存键值
|
||||
* @param bundle - 资源包名
|
||||
* @param isRemote - 是否为远程资源
|
||||
* @returns 加载结果
|
||||
*/
|
||||
private setCacheAndReturn(
|
||||
clip: AudioClip,
|
||||
path: string,
|
||||
cacheKey: string,
|
||||
bundle: string | null,
|
||||
isRemote: boolean
|
||||
): ILoadResult {
|
||||
clip.addRef();
|
||||
this.clipCache.set(cacheKey, clip);
|
||||
return { clip, path, bundle, isRemote };
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载远程资源
|
||||
* @param path - 远程 URL
|
||||
* @returns 加载结果
|
||||
*/
|
||||
private async loadRemote(
|
||||
path: string
|
||||
): Promise<ILoadResult | null> {
|
||||
let loadPromise = this.loadingCache.get(path);
|
||||
if (!loadPromise) {
|
||||
loadPromise = this.doLoadRemotePromise(path);
|
||||
this.loadingCache.set(path, loadPromise);
|
||||
}
|
||||
|
||||
try {
|
||||
const clip = await loadPromise;
|
||||
if (!clip || !clip.isValid) {
|
||||
console.warn(`远程音频资源加载失败: ${path}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.setCacheAndReturn(clip, path, `remote_${path}`, null, true);
|
||||
}
|
||||
catch (e) {
|
||||
console.warn(`远程音频资源加载异常: ${path}`, e);
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
this.loadingCache.delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行远程资源加载
|
||||
* @param path - 远程 URL
|
||||
* @returns AudioClip 加载 Promise
|
||||
*/
|
||||
private async doLoadRemotePromise(path: string): Promise<AudioClip> {
|
||||
const extension = path.split('.').pop();
|
||||
return resLoader.loadRemote<AudioClip>(path, { ext: `.${extension}` });
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 Bundle 内资源
|
||||
* @param path - 资源路径
|
||||
* @param bundle - 资源包名
|
||||
* @returns 加载结果
|
||||
*/
|
||||
private async loadBundle(
|
||||
path: string,
|
||||
bundle: string
|
||||
): Promise<ILoadResult | null> {
|
||||
const cacheKey = `bundle_${bundle}_${path}`;
|
||||
|
||||
let clip = resLoader.get(path, AudioClip, bundle);
|
||||
if (clip) {
|
||||
if (!clip.isValid) {
|
||||
console.warn(`音频资源已失效: ${bundle}/${path}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const entry = this.clipCache.get(cacheKey);
|
||||
if (entry) {
|
||||
clip.addRef();
|
||||
return { clip, path, bundle, isRemote: false };
|
||||
}
|
||||
return this.setCacheAndReturn(clip, path, cacheKey, bundle, false);
|
||||
}
|
||||
|
||||
let loadPromise = this.loadingCache.get(cacheKey);
|
||||
if (!loadPromise) {
|
||||
loadPromise = resLoader.load(bundle, path, AudioClip);
|
||||
this.loadingCache.set(cacheKey, loadPromise);
|
||||
}
|
||||
|
||||
try {
|
||||
clip = await loadPromise;
|
||||
if (!clip || !clip.isValid) {
|
||||
console.warn(`音频资源加载失败: ${bundle}/${path}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.setCacheAndReturn(clip, path, cacheKey, bundle, false);
|
||||
}
|
||||
catch (e) {
|
||||
console.warn(`音频资源加载异常: ${bundle}/${path}`, e);
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
this.loadingCache.delete(cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,9 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 10:22:36
|
||||
*/
|
||||
import type { AudioClip } from 'cc';
|
||||
import { AudioSource, _decorator } from 'cc';
|
||||
import type { IAudioParams } from './IAudio';
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
/** 游戏音效 */
|
||||
/** 游戏音效播放器 */
|
||||
@ccclass('AudioEffect')
|
||||
export class AudioEffect extends AudioSource {
|
||||
/** 唯一编号 */
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { AudioClip, Node, NodePool } from 'cc';
|
||||
import { AudioClip, AudioSource, Node, NodePool } from 'cc';
|
||||
import { oops } from '../../Oops';
|
||||
import { AudioClipLoader } from './AudioClipLoader';
|
||||
import { AudioEffect } from './AudioEffect';
|
||||
import { AudioEffectType } from './AudioEnum';
|
||||
import type { IAudioData, IAudioParams } from './IAudio';
|
||||
import { resLoader } from '../loader/ResLoader';
|
||||
|
||||
/** 音乐效缓冲编号最大值 */
|
||||
const AE_ID_MAX = 30000;
|
||||
@@ -12,17 +10,14 @@ const AE_ID_MAX = 30000;
|
||||
/**
|
||||
* 音效池
|
||||
*
|
||||
* 内存管理思路:
|
||||
* 1. 引用计数机制:每个 AudioClip 通过 addRef/decRef 管理生命周期
|
||||
* 2. 自动释放:界面关闭时调用 releaseResByPath 减少引用计数
|
||||
* 3. 永久缓存:通过预加载时额外增加一次引用,使资源不会被界面释放清理
|
||||
* 4. 缓存复用:引用计数 > 0 的资源保留在 clipCache 中供后续界面复用
|
||||
* 职责:
|
||||
* 1. 不负责资源加载,只接收 AudioClip 实例进行播放
|
||||
* 2. 管理音效播放器对象池
|
||||
* 3. 资源加载与释放由外部(GameResModule + ResAutoTracker)管理
|
||||
*/
|
||||
export class AudioEffectPool {
|
||||
/** 音效配置数据 */
|
||||
private data: { [node: string]: IAudioData } = null!;
|
||||
/** 音频资源加载器 */
|
||||
private loader: AudioClipLoader = new AudioClipLoader();
|
||||
private data: { [node: string]: IAudioData } = {};
|
||||
/** 音效播放器节点对象池 */
|
||||
private pool: NodePool = new NodePool();
|
||||
/** 正在播放的音效播放器集合 */
|
||||
@@ -90,47 +85,31 @@ export class AudioEffectPool {
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载与播放音效
|
||||
* @param path 音效资源地址与音效资源
|
||||
* 播放音效
|
||||
* @param clip AudioClip 实例
|
||||
* @param params 音效附加参数
|
||||
* @returns
|
||||
*/
|
||||
async loadAndPlay(path: string | AudioClip, params?: IAudioParams): Promise<AudioEffect> {
|
||||
play(clip: AudioClip, params?: IAudioParams): AudioEffect | null {
|
||||
const finalParams = this.mergeParams(params);
|
||||
|
||||
const iad = this.data[finalParams.type!];
|
||||
if (!iad) {
|
||||
console.error(`类型为【${finalParams.type!}】的音效配置不存在`);
|
||||
return null!;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!iad.switch) {
|
||||
return null!;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (finalParams.volume == null) finalParams.volume = iad.volume;
|
||||
|
||||
const bundle = finalParams.bundle!;
|
||||
let key: string;
|
||||
const key = `${finalParams.type}_${clip.uuid}_${this.getAeId()}`;
|
||||
|
||||
if (path instanceof AudioClip) {
|
||||
key = `${finalParams.type}_${path.uuid}`;
|
||||
}
|
||||
else {
|
||||
key = `${finalParams.type}_${bundle}_${path}`;
|
||||
}
|
||||
|
||||
// 通过 loader 加载/获取资源(自动处理缓存和引用计数)
|
||||
const result = await this.loader.load(path, bundle);
|
||||
if (!result) {
|
||||
console.warn(`音效资源加载失败: ${key}`);
|
||||
return null!;
|
||||
}
|
||||
|
||||
const clip = result.clip;
|
||||
if (!clip.isValid) {
|
||||
console.warn(`音效资源【${key}】已失效`);
|
||||
return null!;
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取音效播放器播放音乐
|
||||
@@ -138,13 +117,8 @@ export class AudioEffectPool {
|
||||
let node: Node;
|
||||
|
||||
if (this.pool.size() === 0) {
|
||||
const aeid = this.getAeId();
|
||||
key = `${key}_${aeid}`;
|
||||
|
||||
node = new Node('AudioEffect');
|
||||
ae = node.addComponent(AudioEffect)!;
|
||||
ae.key = key;
|
||||
ae.aeid = aeid;
|
||||
ae.onComplete = this.onAudioEffectPlayComplete.bind(this);
|
||||
}
|
||||
else {
|
||||
@@ -152,12 +126,15 @@ export class AudioEffectPool {
|
||||
ae = node.getComponent(AudioEffect)!;
|
||||
}
|
||||
|
||||
ae.key = key;
|
||||
ae.aeid = this._aeId;
|
||||
|
||||
// 记录正在播放的音效播放器
|
||||
this.effects.set(ae.key, ae);
|
||||
|
||||
try {
|
||||
node.parent = oops.audio.node;
|
||||
ae.path = path;
|
||||
ae.path = clip;
|
||||
ae.params = finalParams;
|
||||
ae.loop = finalParams.loop!;
|
||||
ae.volume = finalParams.volume!;
|
||||
@@ -171,22 +148,14 @@ export class AudioEffectPool {
|
||||
this.effects.delete(ae.key);
|
||||
this.put(ae);
|
||||
console.warn(`音效播放异常,已回收: ${key}`, e);
|
||||
return null!;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 音效播放完成 */
|
||||
private onAudioEffectPlayComplete(ae: AudioEffect) {
|
||||
// 通过 loader 释放资源引用(自动处理延迟释放)
|
||||
if (ae.path instanceof AudioClip) {
|
||||
this.loader.release(ae.path.uuid);
|
||||
}
|
||||
else {
|
||||
this.loader.release(ae.path as string, ae.params?.bundle);
|
||||
}
|
||||
|
||||
// 循环播放的音效或自动释放音乐资源的音效,自动回收音乐播放器
|
||||
if (!ae.params.loop || ae.params.destroy) {
|
||||
// 非循环播放的音效,自动回收播放器
|
||||
if (!ae.params.loop) {
|
||||
ae.params && ae.params.onPlayComplete && ae.params.onPlayComplete(ae);
|
||||
this.put(ae);
|
||||
}
|
||||
@@ -194,7 +163,7 @@ export class AudioEffectPool {
|
||||
|
||||
/**
|
||||
* 回收音效播放器
|
||||
* @param ae loadAndPlay 方法返回的音效播放器对象
|
||||
* @param ae play 方法返回的音效播放器对象
|
||||
*/
|
||||
put(ae: AudioEffect) {
|
||||
const effect = this.effects.get(ae.key);
|
||||
@@ -218,9 +187,17 @@ export class AudioEffectPool {
|
||||
this.effects.clear();
|
||||
}
|
||||
|
||||
/** 恢复所有音效 */
|
||||
play() {
|
||||
this.effects.forEach((ae) => ae.play());
|
||||
/** 恢复或播放所有音效 */
|
||||
resume() {
|
||||
this.effects.forEach((ae) => {
|
||||
// 如果是暂停状态则恢复,如果是停止状态则播放
|
||||
if (ae.state === AudioSource.AudioState.PAUSED) {
|
||||
ae.play();
|
||||
}
|
||||
else if (ae.state === AudioSource.AudioState.INIT || ae.state === AudioSource.AudioState.STOPPED) {
|
||||
ae.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 暂停所有音效 */
|
||||
@@ -230,22 +207,12 @@ export class AudioEffectPool {
|
||||
for (let i = 0; i < effectsArray.length; i++) {
|
||||
const ae = effectsArray[i];
|
||||
ae.pause();
|
||||
this.onAudioEffectPlayComplete(ae);
|
||||
// 暂停时不回收音效播放器,只是暂停播放
|
||||
}
|
||||
this.effects.clear();
|
||||
}
|
||||
|
||||
/** 释放所有音效资源与对象池中播放器 */
|
||||
/** 释放所有音效播放器 */
|
||||
release() {
|
||||
// 释放池中音乐播放器
|
||||
this.releasePool();
|
||||
|
||||
// 清空 loader 缓存(强制释放所有音频资源)
|
||||
this.loader.clearCache();
|
||||
}
|
||||
|
||||
/** 释放池中音乐播放器 */
|
||||
releasePool() {
|
||||
this.pool.clear();
|
||||
|
||||
// 释放正在播放的音效对象
|
||||
@@ -276,38 +243,15 @@ export class AudioEffectPool {
|
||||
return destroyed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放指定远程音效资源(立即释放,不等待延迟)
|
||||
* @param path 远程资源 URL
|
||||
* @returns 是否成功释放
|
||||
*/
|
||||
releaseResRemoteByPath(path: string): boolean {
|
||||
this.loader.releaseImmediately(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放指定路径的音效资源引用
|
||||
* @param path 资源路径
|
||||
* @param bundle 资源包名(可选)
|
||||
*/
|
||||
releaseResByPath(path: string, bundle?: string): void {
|
||||
this.loader.release(path, bundle);
|
||||
}
|
||||
|
||||
private mergeParams(params?: IAudioParams): IAudioParams {
|
||||
return params ? {
|
||||
type: params.type ?? AudioEffectType.Effect,
|
||||
bundle: params.bundle ?? resLoader.defaultBundleName,
|
||||
loop: params.loop ?? false,
|
||||
destroy: params.destroy ?? false,
|
||||
volume: params.volume,
|
||||
onPlayComplete: params.onPlayComplete
|
||||
} : {
|
||||
type: AudioEffectType.Effect,
|
||||
bundle: resLoader.defaultBundleName,
|
||||
loop: false,
|
||||
destroy: false
|
||||
loop: false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,20 +26,20 @@ export class AudioManager extends Component {
|
||||
|
||||
/**
|
||||
* 播放背景音乐
|
||||
* @param path 资源路径
|
||||
* @param clip AudioClip 实例
|
||||
* @param params 音效参数
|
||||
*/
|
||||
playMusic(path: string, params?: IAudioParams) {
|
||||
this.music.loadAndPlay(path, params);
|
||||
playMusic(clip: AudioClip, params?: IAudioParams) {
|
||||
this.music.play(clip, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放音效
|
||||
* @param path 资源路径
|
||||
* @param clip AudioClip 实例
|
||||
* @param params 音效参数
|
||||
*/
|
||||
playEffect(path: string | AudioClip, params?: IAudioParams): Promise<AudioEffect> {
|
||||
return this.effect.loadAndPlay(path, params);
|
||||
playEffect(clip: AudioClip, params?: IAudioParams): AudioEffect | null {
|
||||
return this.effect.play(clip, params);
|
||||
}
|
||||
|
||||
/** 回收音效播放器 */
|
||||
@@ -47,18 +47,10 @@ export class AudioManager extends Component {
|
||||
this.effect.put(ae);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放指定远程音效资源
|
||||
* @param path 远程资源 URL
|
||||
* @returns 是否成功释放
|
||||
*/
|
||||
releaseEffectRemote(path: string): boolean {
|
||||
return this.effect.releaseResRemoteByPath(path);
|
||||
}
|
||||
|
||||
/** 恢复当前暂停的音乐与音效播放 */
|
||||
resumeAll() {
|
||||
this.music.resume();
|
||||
this.effect.resume();
|
||||
}
|
||||
|
||||
/** 暂停当前音乐与音效的播放 */
|
||||
@@ -126,10 +118,9 @@ export class AudioManager extends Component {
|
||||
this.save();
|
||||
}
|
||||
|
||||
/** 组件销毁时释放所有音频资源 */
|
||||
/** 组件销毁时停止所有音频 */
|
||||
onDestroy() {
|
||||
this.stopAll();
|
||||
this.music?.release();
|
||||
this.effect?.release();
|
||||
this.music = null!;
|
||||
this.data = null!;
|
||||
|
||||
@@ -1,36 +1,21 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-06-21 12:05:13
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-05-16 09:11:30
|
||||
*/
|
||||
import type { AudioClip } from 'cc';
|
||||
import { Node } from 'cc';
|
||||
import { resLoader } from '../loader/ResLoader';
|
||||
import { AudioClipLoader } from './AudioClipLoader';
|
||||
import type { IAudioData, IAudioParams } from './IAudio';
|
||||
import { AudioEffect } from './AudioEffect';
|
||||
import { AudioEffectType } from './AudioEnum';
|
||||
import type { IAudioData, IAudioParams } from './IAudio';
|
||||
|
||||
/**
|
||||
* 背景音乐
|
||||
* 1、播放一个新背景音乐时,先加载音乐资源,然后停止正在播放的背景资源同时释放当前背景音乐资源,最后播放新的背景音乐
|
||||
* 1、播放一个新背景音乐时,先停止正在播放的背景资源,最后播放新的背景音乐
|
||||
* 2、背景音乐循环播放时,不会触发播放完成事件
|
||||
* 3、不负责加载资源,也不负责释放资源,资源加载与引用计数完全由外部(GameResModule + ResAutoTracker)管理
|
||||
*/
|
||||
export class AudioMusic extends Node {
|
||||
/** 音效配置数据 */
|
||||
private data: { [node: string]: IAudioData } = null!;
|
||||
|
||||
/** 音频资源加载器(统一管理引用计数与延迟释放) */
|
||||
private loader: AudioClipLoader = new AudioClipLoader();
|
||||
private _progress = 0;
|
||||
private _isLoading = false;
|
||||
private _nextPath: string | null = null;
|
||||
private _nextParams: IAudioParams | null = null;
|
||||
private _ae: AudioEffect = null!;
|
||||
/** 当前播放的音乐路径(用于释放引用) */
|
||||
private _currentPath: string | null = null;
|
||||
/** 当前播放的音乐 bundle(用于释放引用) */
|
||||
private _currentBundle: string | null = null;
|
||||
|
||||
/**
|
||||
* 音效开关
|
||||
@@ -94,76 +79,21 @@ export class AudioMusic extends Node {
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载音乐并播放
|
||||
* @param path 音乐资源地址
|
||||
* 播放音乐
|
||||
* @param clip AudioClip 实例
|
||||
* @param params 背景音乐资源播放参数
|
||||
*/
|
||||
async loadAndPlay(path: string, params?: IAudioParams) {
|
||||
play(clip: AudioClip, params?: IAudioParams) {
|
||||
if (!this.getSwitch()) return;
|
||||
|
||||
if (this._isLoading) {
|
||||
this._nextPath = path;
|
||||
this._nextParams = params || null;
|
||||
return;
|
||||
}
|
||||
if (this._ae.playing) this.stop();
|
||||
|
||||
const finalParams = this.mergeParams(params);
|
||||
this._isLoading = true;
|
||||
|
||||
const result = await this.loader.load(path, finalParams.bundle);
|
||||
this._isLoading = false;
|
||||
|
||||
if (!result) {
|
||||
console.warn(`音乐资源加载失败: ${path}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._nextPath !== null) {
|
||||
const nextPath = this._nextPath;
|
||||
const nextParams = this._nextParams;
|
||||
this._nextPath = null;
|
||||
// 清理回调引用,防止闭包持有外部对象
|
||||
this._nextParams = null;
|
||||
|
||||
// 释放刚加载的资源引用(未实际播放)
|
||||
this.loader.release(path, finalParams.bundle);
|
||||
|
||||
this.loadAndPlay(nextPath, nextParams || undefined);
|
||||
}
|
||||
else {
|
||||
if (this._ae.playing) this.stop();
|
||||
|
||||
// 释放当前播放的资源引用
|
||||
this.release();
|
||||
|
||||
this._ae.params = finalParams;
|
||||
this._ae.path = path;
|
||||
this._ae.clip = result.clip;
|
||||
this._ae.loop = finalParams.loop!;
|
||||
this._ae.volume = finalParams.volume!;
|
||||
this._ae.currentTime = 0;
|
||||
this._ae.play();
|
||||
|
||||
// 记录当前播放的资源路径,用于后续释放
|
||||
this._currentPath = path;
|
||||
this._currentBundle = finalParams.bundle || null;
|
||||
}
|
||||
}
|
||||
|
||||
private mergeParams(params?: IAudioParams): IAudioParams {
|
||||
return params ? {
|
||||
type: params.type ?? AudioEffectType.Music,
|
||||
bundle: params.bundle ?? resLoader.defaultBundleName,
|
||||
loop: params.loop ?? true,
|
||||
volume: params.volume ?? this.getVolume(),
|
||||
destroy: params.destroy,
|
||||
onPlayComplete: params.onPlayComplete
|
||||
} : {
|
||||
type: AudioEffectType.Music,
|
||||
bundle: resLoader.defaultBundleName,
|
||||
loop: true,
|
||||
volume: this.getVolume()
|
||||
};
|
||||
this._ae.params = params!;
|
||||
this._ae.clip = clip;
|
||||
this._ae.loop = params?.loop ?? true;
|
||||
this._ae.volume = params?.volume ?? this.getVolume();
|
||||
this._ae.currentTime = 0;
|
||||
this._ae.play();
|
||||
}
|
||||
|
||||
/** 恢复当前暂停的音乐与音效播放 */
|
||||
@@ -181,28 +111,10 @@ export class AudioMusic extends Node {
|
||||
if (this._ae.playing) this._ae.stop();
|
||||
}
|
||||
|
||||
/** 释放当前背景音乐资源 */
|
||||
release() {
|
||||
if (this._ae && this._ae.clip) {
|
||||
this.stop();
|
||||
this._ae.clip = null;
|
||||
}
|
||||
|
||||
// 通过 loader 释放资源引用(自动处理延迟释放)
|
||||
if (this._currentPath) {
|
||||
this.loader.release(this._currentPath, this._currentBundle || undefined);
|
||||
this._currentPath = null;
|
||||
this._currentBundle = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 节点销毁时清理所有引用 */
|
||||
/** 节点销毁时清理 */
|
||||
onDestroy() {
|
||||
this.release();
|
||||
this._nextPath = null;
|
||||
this._nextParams = null;
|
||||
this.stop();
|
||||
this._ae = null!;
|
||||
this.data = null!;
|
||||
this.loader.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
export interface IAudioParams {
|
||||
/** 音乐分类 */
|
||||
type?: string,
|
||||
/** 资源包名 */
|
||||
bundle?: string,
|
||||
/** 音效类型 */
|
||||
type?: string;
|
||||
/** 是否循环播放 */
|
||||
loop?: boolean;
|
||||
/** 音效音量 */
|
||||
volume?: number;
|
||||
/** 是否在播放完后自动释放音乐资源(默认不释放) */
|
||||
destroy?: boolean;
|
||||
/** 播放完成事件 */
|
||||
onPlayComplete?: Function;
|
||||
}
|
||||
@@ -18,4 +14,4 @@ export interface IAudioData {
|
||||
switch: boolean;
|
||||
/** 音量 */
|
||||
volume: number;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-07-03 16:13:17
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 11:03:08
|
||||
*/
|
||||
|
||||
/**
|
||||
* 全局事件监听方法
|
||||
* @param event 事件名
|
||||
|
||||
9
assets/core/common/pool.meta
Normal file
9
assets/core/common/pool.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "97818807-d408-4c88-8303-857111cc148c",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
140
assets/core/common/pool/GameNodePool.ts
Normal file
140
assets/core/common/pool/GameNodePool.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import type { Node } from 'cc';
|
||||
import { instantiate, NodePool, Prefab } from 'cc';
|
||||
|
||||
/**
|
||||
* 通用对象池管理器
|
||||
* 基于 Prefab 的 UUID 管理对象池,支持全局单例访问
|
||||
*
|
||||
* 使用场景:
|
||||
* 1、特效对象池管理
|
||||
* 2、UI 对象池管理
|
||||
* 3、任意 Prefab 的对象池管理
|
||||
*
|
||||
* 注意:本类只管理对象池,不管理资源加载与释放
|
||||
* 资源管理请使用各模块自己的资源管理系统
|
||||
*/
|
||||
export class GameNodePool {
|
||||
private static _instance: GameNodePool;
|
||||
/** 获取单例实例 */
|
||||
static get instance(): GameNodePool {
|
||||
if (this._instance == null) {
|
||||
this._instance = new GameNodePool();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/** 对象池集合 - key 为 Prefab 的 UUID */
|
||||
private _pools: Map<string, NodePool> = new Map();
|
||||
|
||||
/**
|
||||
* 获取指定对象池中对象数量
|
||||
* @param prefab 预制体资源
|
||||
* @returns 对象池中可用对象数量
|
||||
*/
|
||||
getCount(prefab: Prefab): number {
|
||||
const pool = this._pools.get(prefab.uuid);
|
||||
if (pool) {
|
||||
return pool.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载对象到池中
|
||||
* @param count 预加载数量
|
||||
* @param prefab 预制体资源
|
||||
*/
|
||||
preload(count: number, prefab: Prefab): void {
|
||||
const uuid = prefab.uuid;
|
||||
let pool = this._pools.get(uuid);
|
||||
if (pool == null) {
|
||||
pool = new NodePool();
|
||||
this._pools.set(uuid, pool);
|
||||
}
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const node = instantiate(prefab);
|
||||
// @ts-ignore
|
||||
node._pool_uuid = uuid;
|
||||
pool.put(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从对象池获取对象
|
||||
* @param prefab 预制体资源
|
||||
* @param parent 父节点(可选)
|
||||
* @returns 节点对象
|
||||
*/
|
||||
get(prefab: Prefab, parent?: Node): Node {
|
||||
const uuid = prefab.uuid;
|
||||
let pool = this._pools.get(uuid);
|
||||
if (pool == null) {
|
||||
pool = new NodePool();
|
||||
this._pools.set(uuid, pool);
|
||||
}
|
||||
|
||||
let node: Node;
|
||||
// 池中无可用对象时创建新对象
|
||||
if (pool.size() == 0) {
|
||||
node = instantiate(prefab);
|
||||
// @ts-ignore
|
||||
node._pool_uuid = uuid;
|
||||
}
|
||||
// 从池中获取对象
|
||||
else {
|
||||
node = pool.get()!;
|
||||
}
|
||||
|
||||
// 设置父节点
|
||||
if (parent) {
|
||||
node.parent = parent;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回收对象到池中
|
||||
* @param node 节点
|
||||
*/
|
||||
put(node: Node) {
|
||||
// @ts-ignore
|
||||
const uuid = node._pool_uuid;
|
||||
if (uuid) {
|
||||
const pool = this._pools.get(uuid);
|
||||
if (pool) {
|
||||
// 从父节点移除
|
||||
if (node.parent) {
|
||||
node.removeFromParent();
|
||||
}
|
||||
|
||||
// 重置节点状态
|
||||
node.active = false;
|
||||
|
||||
// 回收到池中
|
||||
pool.put(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除对象池数据
|
||||
* @param prefab 预制体资源,为空时清除所有对象池数据
|
||||
*/
|
||||
clear(prefab?: Prefab) {
|
||||
if (prefab) {
|
||||
const uuid = prefab.uuid;
|
||||
const pool = this._pools.get(uuid);
|
||||
if (pool) {
|
||||
pool.clear();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._pools.forEach((pool) => {
|
||||
pool.clear();
|
||||
});
|
||||
this._pools.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/core/common/pool/GameNodePool.ts.meta
Normal file
9
assets/core/common/pool/GameNodePool.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "fcd4f08b-a148-4303-918d-317135283b33",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2023-01-19 11:09:38
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-01-19 14:28:05
|
||||
*/
|
||||
|
||||
/**
|
||||
* 定时触发组件
|
||||
* @help https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12037964&doc_id=2873565
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2023-01-19 10:33:49
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-01-19 14:37:19
|
||||
*/
|
||||
import { Component, game } from 'cc';
|
||||
import { StringUtil } from '../../utils/StringUtil';
|
||||
import { Timer } from './Timer';
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-02-10 09:50:41
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 12:09:55
|
||||
*/
|
||||
import type { Node } from 'cc';
|
||||
import { director, isValid } from 'cc';
|
||||
import { GameComponent } from '../../module/common/GameComponent';
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-07-03 16:13:17
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-07-24 17:14:57
|
||||
*/
|
||||
import type { Node } from 'cc';
|
||||
import { LayerPopUp } from './LayerPopup';
|
||||
import type { UIParam, UIState } from './LayerUIElement';
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2025-08-15 10:06:47
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2025-08-15 10:06:47
|
||||
*/
|
||||
import { Node, NodePool, Vec3, warn } from 'cc';
|
||||
import { resLoader } from '../../common/loader/ResLoader';
|
||||
import { ViewUtil } from '../../utils/ViewUtil';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Camera, Node, warn } from 'cc';
|
||||
import { oops } from '../../Oops';
|
||||
import { gui } from '../Gui';
|
||||
import { LayerDialog } from './LayerDialog';
|
||||
import type { UIConfigMap, Uiid } from './LayerEnum';
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-08-15 10:06:47
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 13:44:12
|
||||
*/
|
||||
import { BlockInputEvents, Node, instantiate } from 'cc';
|
||||
import { EDITOR } from 'cc/env';
|
||||
import { ViewUtil } from '../../utils/ViewUtil';
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* @Date: 2021-11-24 16:08:36
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 13:44:28
|
||||
*/
|
||||
import type { EventTouch } from 'cc';
|
||||
import { BlockInputEvents, Node } from 'cc';
|
||||
import { ViewUtil } from '../../utils/ViewUtil';
|
||||
@@ -103,9 +98,9 @@ export class LayerPopUp extends LayerUI {
|
||||
this.black.enabled = false;
|
||||
}
|
||||
|
||||
if (config.mask) {
|
||||
this.mask.parent = this;
|
||||
this.mask.uiSprite.enabled = true;
|
||||
if (config.mask) {
|
||||
this.mask.parent = this;
|
||||
this.mask.uiSprite.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-01-09 11:55:03
|
||||
*/
|
||||
import type { Node } from 'cc';
|
||||
import { Component, _decorator } from 'cc';
|
||||
import { oops } from '../../Oops';
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 14:08:39
|
||||
*/
|
||||
import { Component, Node, _decorator } from 'cc';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: bansomin
|
||||
* @LastEditTime: 2025-01-02 10:47:47
|
||||
*/
|
||||
import { Animation, Component, Label, _decorator } from 'cc';
|
||||
import { LanguageLabel } from '../../../libs/gui/language/LanguageLabel';
|
||||
|
||||
@@ -48,4 +42,4 @@ export class Notify extends Component {
|
||||
this.lab_content.string = msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-08-11 16:41:12
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 14:50:57
|
||||
*/
|
||||
|
||||
/** 数组工具 */
|
||||
export class ArrayUtil {
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-07-26 15:29:57
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 14:50:16
|
||||
*/
|
||||
import type { Camera } from 'cc';
|
||||
import type { Camera } from 'cc';
|
||||
import { Vec3, view } from 'cc';
|
||||
|
||||
/** 摄像机工具 */
|
||||
@@ -29,4 +23,4 @@ export class CameraUtil {
|
||||
&& (viewPos.x <= viewportRect.width) && (viewPos.x >= 0)
|
||||
&& (viewPos.y <= viewportRect.height) && (viewPos.y >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 14:49:42
|
||||
*/
|
||||
import { assetManager, Color, ImageAsset, sys, Texture2D } from 'cc';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-08-18 17:00:59
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-08-22 15:48:02
|
||||
*/
|
||||
|
||||
import { JsonAsset } from 'cc';
|
||||
import { ZipLoader } from 'db://oops-framework/core/common/loader/ZipLoader';
|
||||
import { resLoader } from '../common/loader/ResLoader';
|
||||
@@ -107,4 +100,4 @@ export class JsonUtil {
|
||||
static clear() {
|
||||
data.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-07-26 15:29:57
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 12:07:54
|
||||
*/
|
||||
|
||||
/** 对象工具 */
|
||||
export class ObjectUtil {
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-07-21 17:30:59
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 14:40:28
|
||||
*/
|
||||
import type { Node } from 'cc';
|
||||
|
||||
/** 物理分组数据 */
|
||||
@@ -54,4 +48,4 @@ export class PhysicsUtil {
|
||||
PhysicsUtil.setNodeLayer(item, n);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* @Date: 2021-08-14 16:17:03
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 14:39:03
|
||||
*/
|
||||
import type { __private } from 'cc';
|
||||
import { native, sys } from 'cc';
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-07-26 15:29:57
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 12:08:25
|
||||
*/
|
||||
|
||||
/** 正则工具 */
|
||||
export class RegexUtil {
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-07-26 15:29:57
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 12:08:28
|
||||
*/
|
||||
import type { Node } from 'cc';
|
||||
import type { Node } from 'cc';
|
||||
import { Quat, toRadian, Vec3 } from 'cc';
|
||||
import { Vec3Util } from './Vec3Util';
|
||||
|
||||
@@ -77,4 +71,4 @@ export class RotateUtil {
|
||||
|
||||
return vec3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-08-16 09:34:56
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-01-19 14:52:12
|
||||
*/
|
||||
import type { EventTouch, Node, Vec3 } from 'cc';
|
||||
import type { EventTouch, Node, Vec3 } from 'cc';
|
||||
import { Animation, AnimationClip, instantiate, Prefab, Size, UITransform, v3 } from 'cc';
|
||||
import { resLoader } from '../common/loader/ResLoader';
|
||||
|
||||
@@ -165,4 +159,4 @@ export class ViewUtil {
|
||||
anim.createState(clip, clip!.name);
|
||||
anim.play(clip!.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
assets/libs/.DS_Store
vendored
Normal file
BIN
assets/libs/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-03-31 18:03:50
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-22 14:53:47
|
||||
*/
|
||||
|
||||
import type { Camera } from 'cc';
|
||||
import { _decorator, Component, Node, Vec3 } from 'cc';
|
||||
import { oops } from '../../core/Oops';
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-08-11 16:41:12
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-22 14:54:17
|
||||
*/
|
||||
|
||||
import { Component, _decorator } from 'cc';
|
||||
import { EffectSingleCase } from './EffectSingleCase';
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-08-19 15:36:08
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-03-01 18:28:55
|
||||
*/
|
||||
|
||||
import { Animation, Component, ParticleSystem, _decorator, sp } from 'cc';
|
||||
import { EffectEvent } from './EffectEvent';
|
||||
import { message } from '../../core/common/event/MessageManager';
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-10-12 14:00:43
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-03-06 14:40:34
|
||||
*/
|
||||
import type { Node, Vec3 } from 'cc';
|
||||
import { Animation, NodePool, ParticleSystem, Prefab, sp } from 'cc';
|
||||
import { message } from '../../core/common/event/MessageManager';
|
||||
@@ -29,6 +23,7 @@ export interface IEffectParams {
|
||||
* 1、支持Spine动画
|
||||
* 2、支持Cocos Animation动画
|
||||
* 3、支持Cocos ParticleSystem粒子动画
|
||||
* @deprecated 已废弃,建议用 GameNodePool.ts
|
||||
*/
|
||||
export class EffectSingleCase {
|
||||
private static _instance: EffectSingleCase;
|
||||
|
||||
@@ -15,10 +15,10 @@ const ROTATION_ANGLE_THRESHOLD = 10;
|
||||
@ccclass('MoveRigidBody')
|
||||
export class MoveRigidBody extends Component {
|
||||
@property({ tooltip: '阻尼' })
|
||||
damping = 0.5;
|
||||
damping = 0.5;
|
||||
|
||||
@property({ tooltip: '重力' })
|
||||
gravity = -10;
|
||||
gravity = -10;
|
||||
|
||||
@property
|
||||
private _speed = 5;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-03-25 18:12:10
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-01-19 14:59:50
|
||||
*/
|
||||
import { Component, error, Node, Vec3, _decorator } from 'cc';
|
||||
import { Timer } from '../../core/common/timer/Timer';
|
||||
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-03-25 18:12:10
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-07-25 11:52:23
|
||||
*/
|
||||
import { Component, Node, Vec3, _decorator } from 'cc';
|
||||
import { Vec3Util } from '../../core/utils/Vec3Util';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
/** 角色坐标方式移动 */
|
||||
@ccclass('MoveTranslate')
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-06-30 13:56:26
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2021-11-04 10:46:00
|
||||
*/
|
||||
|
||||
import { AnimationClip, CCFloat, game, SkeletalAnimation, _decorator } from 'cc';
|
||||
import AnimatorAnimation from './AnimatorAnimation';
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-06-21 12:05:14
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-07-20 14:04:44
|
||||
*/
|
||||
import type { BTNodeJson } from './BTNodeJson';
|
||||
import type { IControl } from './IControl';
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-06-21 12:05:14
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-07-20 13:58:32
|
||||
*/
|
||||
import type { BTNodeJson } from './BTNodeJson';
|
||||
import { BTreeNode } from './BTreeNode';
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-06-21 12:05:14
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-07-20 14:05:02
|
||||
*/
|
||||
import { BehaviorTree } from './BehaviorTree';
|
||||
import type { BTNodeJson } from './BTNodeJson';
|
||||
import { BTreeNode } from './BTreeNode';
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-06-21 12:05:14
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-07-20 14:04:27
|
||||
*/
|
||||
import type { BTreeNode } from './BTreeNode';
|
||||
|
||||
/** 行为控制接口 */
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-06-21 12:05:14
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-07-20 14:08:10
|
||||
*/
|
||||
import { BranchNode } from './BranchNode';
|
||||
|
||||
/** 优先选择节点:首个成功的子节点即返回成功,全部失败则返回失败 */
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-06-21 12:05:14
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-07-20 14:05:40
|
||||
*/
|
||||
import { BranchNode } from './BranchNode';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-06-21 12:05:14
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-07-20 14:05:22
|
||||
*/
|
||||
import { BranchNode } from './BranchNode';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-06-21 12:05:14
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-07-20 11:43:20
|
||||
*/
|
||||
import { BTreeNode } from './BTreeNode';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-07-22 15:54:51
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-22 14:47:59
|
||||
*/
|
||||
|
||||
/** 支持Map与Array功能的集合对象 */
|
||||
export class Collection<K, V> extends Map<K, V> {
|
||||
private _array: V[] = [];
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-05 14:03:54
|
||||
*/
|
||||
import type { ecs } from './ECS';
|
||||
import type { ECSEntity } from './ECSEntity';
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-05 14:21:54
|
||||
*/
|
||||
import type { ecs } from './ECS';
|
||||
import type { ECSEntity } from './ECSEntity';
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-05-12 14:18:44
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-05-24 11:09:49
|
||||
*/
|
||||
import { ECSModel } from './ECSModel';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-05-12 14:18:44
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-05 16:37:10
|
||||
*/
|
||||
import type { ecs } from './ECS';
|
||||
import type { ECSEntity } from './ECSEntity';
|
||||
import { ECSGroup } from './ECSGroup';
|
||||
|
||||
@@ -254,7 +254,7 @@ declare global {
|
||||
},
|
||||
orderBy: {
|
||||
value: function () {
|
||||
const mappers = [];
|
||||
const mappers : any[] = [];
|
||||
for (let _i = 0; _i < arguments.length; _i++) {
|
||||
mappers[_i] = arguments[_i];
|
||||
}
|
||||
@@ -275,7 +275,7 @@ declare global {
|
||||
},
|
||||
orderByDesc: {
|
||||
value: function () {
|
||||
const mappers = [];
|
||||
const mappers : any[] = [];
|
||||
for (let _i = 0; _i < arguments.length; _i++) {
|
||||
mappers[_i] = arguments[_i];
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2023-01-30 14:00:41
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-02-09 10:54:28
|
||||
*/
|
||||
import type { EventTouch } from 'cc';
|
||||
import { Animation, AnimationClip, Node, Sprite, _decorator } from 'cc';
|
||||
import { oops } from '../../../core/Oops';
|
||||
@@ -11,9 +5,12 @@ import ButtonSimple from './ButtonSimple';
|
||||
|
||||
const { ccclass, property, menu } = _decorator;
|
||||
|
||||
/** 有特效按钮 */
|
||||
/**
|
||||
* 有特效按钮
|
||||
* @deprecated 该组件已废弃,请使用 UIButton 替代,废弃 button_scale_start,button_scale_end 动画
|
||||
*/
|
||||
@ccclass('ButtonEffect')
|
||||
@menu('OopsFramework/Button/ButtonEffect (有特效按钮)')
|
||||
@menu('OopsFramework/Button/ButtonEffect (有特效按钮)[已废弃]')
|
||||
export default class ButtonEffect extends ButtonSimple {
|
||||
@property({
|
||||
tooltip: '是否开启'
|
||||
|
||||
187
assets/libs/gui/button/ButtonInterceptor.ts
Normal file
187
assets/libs/gui/button/ButtonInterceptor.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import { AudioClip, Button, Component, EventHandler, Node } from 'cc';
|
||||
import { oops } from 'db://oops-framework/core/Oops';
|
||||
import ButtonSimple from './ButtonSimple';
|
||||
|
||||
/**
|
||||
* 按钮音效配置
|
||||
*/
|
||||
export interface IButtonSoundConfig {
|
||||
/** 按钮类构造函数,作为唯一标识 */
|
||||
class: typeof Component;
|
||||
/** 音效资源 */
|
||||
clip: AudioClip;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按钮点击事件劫持器
|
||||
* 用于拦截所有按钮点击事件,根据按钮类型播放不同音效
|
||||
*/
|
||||
export class ButtonInterceptor {
|
||||
private _isActive = false;
|
||||
/** 按钮类型配置映射,key 为组件类 */
|
||||
private _buttonConfigs: Map<typeof Component, IButtonSoundConfig> = new Map();
|
||||
/** 保存 EventHandler 原始 emitEvents 方法 */
|
||||
private _originalEmitEvents: Function | null = null;
|
||||
|
||||
private static _instance: ButtonInterceptor | null = null;
|
||||
/** 获取单例实例 */
|
||||
static get instance(): ButtonInterceptor {
|
||||
if (!this._instance) {
|
||||
this._instance = new ButtonInterceptor();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/** 是否已激活 */
|
||||
get isActive(): boolean {
|
||||
return this._isActive;
|
||||
}
|
||||
|
||||
/** ButtonSimple 原始 onClick 方法映射 */
|
||||
private _originalOnClickMap: Map<ButtonSimple, Function> = new Map();
|
||||
|
||||
/**
|
||||
* 注册按钮音效配置
|
||||
* @param config 按钮音效配置
|
||||
*/
|
||||
registerSound(config: IButtonSoundConfig): void {
|
||||
this._buttonConfigs.set(config.class, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活劫持器
|
||||
* 开始拦截所有按钮点击事件
|
||||
*/
|
||||
activate(): void {
|
||||
if (this._isActive) return;
|
||||
this._isActive = true;
|
||||
|
||||
// 劫持 EventHandler 的 emitEvents 方法
|
||||
this.hijackEmitEvents();
|
||||
|
||||
// 劫持 ButtonSimple 的 onClick 方法
|
||||
this.hijackButtonSimple();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用劫持器
|
||||
* 恢复所有按钮的原始点击事件
|
||||
*/
|
||||
deactivate(): void {
|
||||
if (!this._isActive) return;
|
||||
this._isActive = false;
|
||||
|
||||
// 恢复 EventHandler 原始方法
|
||||
if (this._originalEmitEvents) {
|
||||
// @ts-ignore
|
||||
EventHandler.emitEvents = this._originalEmitEvents;
|
||||
this._originalEmitEvents = null;
|
||||
}
|
||||
|
||||
// 恢复所有 ButtonSimple 的原始 onClick 方法
|
||||
this.restoreButtonSimple();
|
||||
}
|
||||
|
||||
/**
|
||||
* 劫持 ButtonSimple 的 onClick 方法
|
||||
* 在触发点击时播放音效
|
||||
*/
|
||||
private hijackButtonSimple(): void {
|
||||
const self = this;
|
||||
// @ts-ignore
|
||||
const originalOnClick = ButtonSimple.prototype.onClick;
|
||||
|
||||
// 保存原始方法
|
||||
// @ts-ignore
|
||||
ButtonSimple.prototype._originalOnClick = originalOnClick;
|
||||
|
||||
// 重写 onClick 方法
|
||||
// @ts-ignore
|
||||
ButtonSimple.prototype.onClick = function (this: ButtonSimple) {
|
||||
// 保存当前实例的原始方法引用
|
||||
if (!self._originalOnClickMap.has(this)) {
|
||||
self._originalOnClickMap.set(this, originalOnClick);
|
||||
}
|
||||
|
||||
// 播放音效
|
||||
const config = self.getButtonConfig(this.node);
|
||||
if (config) {
|
||||
self.playButtonSound(config);
|
||||
}
|
||||
|
||||
// 调用原始方法
|
||||
return originalOnClick.apply(this);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复 ButtonSimple 的原始 onClick 方法
|
||||
*/
|
||||
private restoreButtonSimple(): void {
|
||||
// @ts-ignore
|
||||
if (ButtonSimple.prototype._originalOnClick) {
|
||||
// @ts-ignore
|
||||
ButtonSimple.prototype.onClick = ButtonSimple.prototype._originalOnClick;
|
||||
// @ts-ignore
|
||||
ButtonSimple.prototype._originalOnClick = null;
|
||||
}
|
||||
this._originalOnClickMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 劫持 EventHandler 的 emitEvents 方法
|
||||
* 在触发点击事件时播放音效
|
||||
*/
|
||||
private hijackEmitEvents(): void {
|
||||
const self = this;
|
||||
|
||||
// 保存原始方法
|
||||
// @ts-ignore
|
||||
this._originalEmitEvents = EventHandler.emitEvents;
|
||||
|
||||
EventHandler.emitEvents = function (...args: any[]) {
|
||||
// 调用原始方法
|
||||
const result = self._originalEmitEvents!.apply(this, args);
|
||||
|
||||
// 检查是否是 touch-end 事件
|
||||
const event = args?.[1];
|
||||
if (event?.type === 'touch-end') {
|
||||
// 获取按钮节点
|
||||
const target = event?.currentTarget as Node;
|
||||
if (target) {
|
||||
const config = self.getButtonConfig(target);
|
||||
if (config) {
|
||||
self.playButtonSound(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过节点获取按钮配置
|
||||
* @param node 节点
|
||||
* @returns 按钮音效配置,未找到返回 null
|
||||
*/
|
||||
private getButtonConfig(node: Node): IButtonSoundConfig | null {
|
||||
// 遍历所有注册的配置,检查节点上是否有对应的组件
|
||||
for (const [classType, config] of this._buttonConfigs) {
|
||||
if (node.getComponent(classType)) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放按钮音效
|
||||
* @param config 按钮音效配置
|
||||
*/
|
||||
private playButtonSound(config: IButtonSoundConfig): void {
|
||||
if (typeof oops !== 'undefined' && oops.audio) {
|
||||
oops.audio.playEffect(config.clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/libs/gui/button/ButtonInterceptor.ts.meta
Normal file
9
assets/libs/gui/button/ButtonInterceptor.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "2d8b38ba-88ad-4e5f-8ec2-85c73dbc13f8",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { EventTouch } from 'cc';
|
||||
import { AudioClip, Component, Node, _decorator, game } from 'cc';
|
||||
import { oops } from '../../../core/Oops';
|
||||
import { Component, Node, _decorator, game } from 'cc';
|
||||
|
||||
const { ccclass, property, menu } = _decorator;
|
||||
|
||||
@@ -18,23 +17,11 @@ export default class ButtonSimple extends Component {
|
||||
})
|
||||
private interval = 500;
|
||||
|
||||
@property({
|
||||
tooltip: '触摸音效',
|
||||
type: AudioClip
|
||||
})
|
||||
private effect: AudioClip = null!;
|
||||
|
||||
/** 触摸次数计数 */
|
||||
private touchCount = 0;
|
||||
/** 上次触摸结束时间 */
|
||||
private touchEndTime = 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,28 +43,17 @@ export default class ButtonSimple extends Component {
|
||||
}
|
||||
else {
|
||||
this.touchEndTime = game.totalTime;
|
||||
|
||||
// 短按触摸音效
|
||||
this.playEffect();
|
||||
this.onClick();
|
||||
}
|
||||
}
|
||||
|
||||
/** 短按触摸音效 */
|
||||
protected playEffect() {
|
||||
if (ButtonSimple.effectPath) {
|
||||
oops.audio.playEffect(ButtonSimple.effectPath);
|
||||
}
|
||||
else if (this.effect) {
|
||||
oops.audio.playEffect(this.effect);
|
||||
}
|
||||
protected onClick(){
|
||||
|
||||
}
|
||||
|
||||
/** 组件销毁时的清理工作 */
|
||||
onDestroy() {
|
||||
this.node.off(Node.EventType.TOUCH_END, this.onTouchEnd, this);
|
||||
this.node.off(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
|
||||
|
||||
// 清理音效引用
|
||||
this.effect = null!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-04-14 18:15:42
|
||||
*/
|
||||
import type { EventTouch } from 'cc';
|
||||
import { EventHandler, _decorator } from 'cc';
|
||||
import ButtonEffect from './ButtonEffect';
|
||||
|
||||
const { ccclass, property, menu } = _decorator;
|
||||
|
||||
/** 长按按钮 */
|
||||
/**
|
||||
* 长按按钮
|
||||
* @deprecated 该组件已废弃,请使用 UIButton 替代
|
||||
*/
|
||||
@ccclass('ButtonTouchLong')
|
||||
@menu('OopsFramework/Button/ButtonTouchLong (长按按钮)')
|
||||
@menu('OopsFramework/Button/ButtonTouchLong (长按按钮)[已废弃]')
|
||||
export class ButtonTouchLong extends ButtonEffect {
|
||||
@property({
|
||||
tooltip: '长按时间(秒)'
|
||||
@@ -76,9 +73,6 @@ export class ButtonTouchLong extends ButtonEffect {
|
||||
event.emit([event.customEventData]);
|
||||
});
|
||||
|
||||
// 长按触摸音效(只播放一次)
|
||||
this.playEffect();
|
||||
|
||||
this.removeTouchLong();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import type { EventTouch } from 'cc';
|
||||
import { AudioClip, Button, EventHandler, _decorator, game } from 'cc';
|
||||
import { oops } from '../../../core/Oops';
|
||||
import { Button, Component, EventHandler, _decorator, game } from 'cc';
|
||||
|
||||
const { ccclass, property, menu } = _decorator;
|
||||
const { ccclass, property, menu, requireComponent } = _decorator;
|
||||
|
||||
/**
|
||||
* 通用按钮
|
||||
* 1、防连点
|
||||
* 2、按钮点击触发音效
|
||||
* 2、支持只触发一次
|
||||
*
|
||||
* 注意:此组件需要配合 Button 组件使用,会自动添加 Button 组件
|
||||
*/
|
||||
@ccclass('UIButton')
|
||||
@menu('OopsFramework/Button/UIButton (通用按钮)')
|
||||
export default class UIButton extends Button {
|
||||
@requireComponent(Button)
|
||||
export default class UIButton extends Component {
|
||||
@property({
|
||||
tooltip: '每次触发间隔'
|
||||
tooltip: '每次触发间隔(毫秒)'
|
||||
})
|
||||
private interval = 500;
|
||||
|
||||
@@ -22,31 +24,43 @@ export default class UIButton extends Button {
|
||||
})
|
||||
private once = false;
|
||||
|
||||
@property({
|
||||
tooltip: '触摸音效',
|
||||
type: AudioClip
|
||||
})
|
||||
private effect: AudioClip = null!;
|
||||
|
||||
/** 触摸次数 */
|
||||
private _touchCount = 0;
|
||||
/** 触摸结束时间 */
|
||||
private _touchEndTime = 0;
|
||||
/** 按钮组件引用 */
|
||||
private _button: Button | null = null;
|
||||
/** 原始触摸结束回调 */
|
||||
private _originalTouchEnded: Function | null = null;
|
||||
|
||||
private static effectPath: string = null!;
|
||||
/** 批量设置触摸音效 */
|
||||
static setBatchEffect(path: string) {
|
||||
this.effectPath = path;
|
||||
}
|
||||
|
||||
/** 触摸结束 */
|
||||
protected _onTouchEnded(event: EventTouch) {
|
||||
if (!this._interactable || !this.enabledInHierarchy) {
|
||||
onLoad() {
|
||||
this._button = this.getComponent(Button);
|
||||
if (!this._button) {
|
||||
console.warn('[UIButton] 未找到 Button 组件,请确保节点上有 Button 组件');
|
||||
return;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
if (this._pressed) {
|
||||
// 保存原始回调并劫持
|
||||
// @ts-ignore
|
||||
this._originalTouchEnded = this._button._onTouchEnded;
|
||||
// @ts-ignore
|
||||
this._button._onTouchEnded = this._onTouchEnded.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触摸结束事件处理
|
||||
* @param event 触摸事件
|
||||
*/
|
||||
private _onTouchEnded(event: EventTouch) {
|
||||
if (!this._button) return;
|
||||
|
||||
// @ts-ignore
|
||||
if (!this._button._interactable || !this._button.enabledInHierarchy) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (this._button._pressed) {
|
||||
// 是否只触发一次
|
||||
if (this.once) {
|
||||
if (this._touchCount > 0) {
|
||||
@@ -62,37 +76,28 @@ export default class UIButton extends Button {
|
||||
}
|
||||
else {
|
||||
this._touchEndTime = game.totalTime;
|
||||
EventHandler.emitEvents(this.clickEvents, event);
|
||||
this.node.emit(Button.EventType.CLICK, this);
|
||||
|
||||
// 短按触摸音效
|
||||
this.playEffect();
|
||||
EventHandler.emitEvents(this._button.clickEvents, event);
|
||||
this.node.emit(Button.EventType.CLICK, this._button);
|
||||
}
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
this._pressed = false;
|
||||
this._updateState();
|
||||
// @ts-ignore
|
||||
this._button._pressed = false;
|
||||
// @ts-ignore
|
||||
this._button._updateState();
|
||||
|
||||
if (event) {
|
||||
event.propagationStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** 短按触摸音效 */
|
||||
protected playEffect() {
|
||||
if (UIButton.effectPath) {
|
||||
oops.audio.playEffect(UIButton.effectPath);
|
||||
}
|
||||
else if (this.effect) {
|
||||
oops.audio.playEffect(this.effect);
|
||||
}
|
||||
}
|
||||
|
||||
/** 组件销毁时的清理工作 */
|
||||
onDestroy() {
|
||||
// 清理音效引用
|
||||
this.effect = null!;
|
||||
super.onDestroy();
|
||||
// 恢复原始回调
|
||||
if (this._button && this._originalTouchEnded) {
|
||||
// @ts-ignore
|
||||
this._button._onTouchEnded = this._originalTouchEnded;
|
||||
}
|
||||
this._button = null;
|
||||
this._originalTouchEnded = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-08-11 10:00:51
|
||||
*/
|
||||
import { _decorator } from 'cc';
|
||||
import LabelNumber from './LabelNumber';
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-08-11 10:27:04
|
||||
*/
|
||||
import { Label, _decorator } from 'cc';
|
||||
|
||||
const { ccclass, property, menu } = _decorator;
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import type { TTFFont } from 'cc';
|
||||
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-02-11 09:31:52
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-08-22 16:37:40
|
||||
*/
|
||||
|
||||
/** 框架支持的语言数据类型 */
|
||||
export enum LanguageDataType {
|
||||
/** Json格式配置 */
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-07-03 16:13:17
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-08-22 16:34:28
|
||||
*/
|
||||
import { director, error, JsonAsset, TTFFont } from 'cc';
|
||||
import { resLoader } from '../../../core/common/loader/ResLoader';
|
||||
import { Logger } from '../../../core/common/log/Logger';
|
||||
import { JsonUtil } from '../../../core/utils/JsonUtil';
|
||||
import { LanguageData, LanguageDataType, LanguageType } from './LanguageData';
|
||||
|
||||
export class LanguagePack {
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2023-07-25 10:44:38
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2023-07-25 11:48:52
|
||||
*/
|
||||
import { CCString, Component, _decorator, sp } from 'cc';
|
||||
import { EDITOR } from 'cc/env';
|
||||
import { resLoader } from '../../../core/common/loader/ResLoader';
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-11-24 15:51:01
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-02 10:04:57
|
||||
*/
|
||||
import type { Size } from 'cc';
|
||||
import { CCString, Component, Sprite, SpriteFrame, UITransform, _decorator } from 'cc';
|
||||
import { EDITOR } from 'cc/env';
|
||||
import { resLoader } from '../../../core/common/loader/ResLoader';
|
||||
|
||||
@@ -95,7 +95,7 @@ export class PromptBase extends GameComponent {
|
||||
}
|
||||
|
||||
protected onLoad(): void {
|
||||
this.button.setButton();
|
||||
this.button.bind();
|
||||
}
|
||||
|
||||
/** 确认按钮点击事件 */
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2024-03-08 10:00:00
|
||||
*
|
||||
* JsonOb 性能优化版本(默认实现)
|
||||
*
|
||||
* 优化特性:
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-09 18:10:50
|
||||
*/
|
||||
import { error, warn } from 'cc';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-09 18:31:18
|
||||
*/
|
||||
|
||||
/*
|
||||
* 网络相关接口定义
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-09-01 18:00:28
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-09 18:10:50
|
||||
*/
|
||||
import type { CallbackObject, IRequestProtocol, NetData } from './NetInterface';
|
||||
import type { NetConnectOptions, NetNode } from './NetNode';
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-21 13:45:51
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-04-21 13:51:33
|
||||
*/
|
||||
import type { IProtocolHelper, IRequestProtocol, IResponseProtocol, NetData } from './NetInterface';
|
||||
|
||||
const unzip = function (str: string) {
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-07-03 16:13:17
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-09 17:42:19
|
||||
*/
|
||||
import { Logger } from '../../core/common/log/Logger';
|
||||
import { oops } from '../../core/Oops';
|
||||
import type { ISocket, MessageFunc, NetData } from './NetInterface';
|
||||
|
||||
type Connected = (event: any) => void;
|
||||
@@ -31,7 +26,7 @@ export class WebSock implements ISocket {
|
||||
connect(options: any) {
|
||||
if (this._ws) {
|
||||
if (this._ws.readyState === WebSocket.CONNECTING) {
|
||||
Logger.logNet('websocket connecting, wait for a moment...');
|
||||
oops.log.logNet('websocket connecting, wait for a moment...');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
assets/module/.DS_Store
vendored
Normal file
BIN
assets/module/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -1,30 +1,14 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2025-09-18 10:20:51
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2025-09-18 17:20:51
|
||||
*/
|
||||
|
||||
import { EventDispatcher } from '../../core/common/event/EventDispatcher';
|
||||
import type { ListenerFunc, ListenerFuncTyped } from '../../core/common/event/EventMessage';
|
||||
import { GamePartEvent } from './part/GamePartEvent';
|
||||
import { GamePartRegistry, GamePartKey, createPart } from './GamePartRegistry';
|
||||
import type { CCEntity } from './CCEntity';
|
||||
|
||||
/** 业务逻辑 */
|
||||
export class CCBusiness<T extends CCEntity> {
|
||||
private _destroyed: boolean = false;
|
||||
|
||||
/** 当前业务逻辑是否有效(未销毁) */
|
||||
get isValid(): boolean {
|
||||
return !this._destroyed;
|
||||
}
|
||||
|
||||
private _ent: T | null = null;
|
||||
|
||||
/** 所属实体引用 */
|
||||
get ent(): T {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试访问已销毁的业务逻辑的实体引用');
|
||||
}
|
||||
return this._ent!;
|
||||
}
|
||||
|
||||
@@ -32,42 +16,35 @@ export class CCBusiness<T extends CCEntity> {
|
||||
this._ent = value;
|
||||
}
|
||||
|
||||
private _parts: GamePartRegistry | null = null;
|
||||
|
||||
/** 获取模块注册表(懒加载) */
|
||||
private get parts(): GamePartRegistry {
|
||||
return (this._parts ??= createPart(this));
|
||||
}
|
||||
|
||||
/** 获取事件模块 */
|
||||
get event(): GamePartEvent {
|
||||
return this.parts.get(GamePartKey.Event);
|
||||
}
|
||||
|
||||
/** 业务逻辑初始化(由 CCEntity.addBusiness 自动调用) */
|
||||
protected init() {
|
||||
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '业务逻辑已销毁,无需重复销毁');
|
||||
return;
|
||||
}
|
||||
|
||||
this._destroyed = true;
|
||||
|
||||
// 释放消息对象
|
||||
if (this._event) {
|
||||
this._event.clear();
|
||||
this._event = null;
|
||||
// 销毁所有模块
|
||||
if (this._parts) {
|
||||
this._parts.destroy();
|
||||
this._parts = null;
|
||||
}
|
||||
|
||||
// 清空实体引用,避免循环引用导致的内存泄漏
|
||||
this._ent = null;
|
||||
}
|
||||
|
||||
//#region 全局事件管理
|
||||
|
||||
private _event: EventDispatcher | null = null;
|
||||
/** 全局事件管理器 */
|
||||
private get event(): EventDispatcher {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试访问已销毁的业务逻辑的事件管理器');
|
||||
}
|
||||
if (this._event == null) this._event = new EventDispatcher();
|
||||
return this._event;
|
||||
}
|
||||
|
||||
//#region 强类型事件方法
|
||||
//#region ========== 兼容旧版本 API 如果是新项目可以把注释包起来的代码都删除 ==========
|
||||
|
||||
/**
|
||||
* 注册全局事件(强类型)
|
||||
@@ -76,11 +53,7 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param object 侦听函数绑定的this对象
|
||||
*/
|
||||
watch<K extends keyof OopsFramework.TypedEventMap>(event: K, listener: ListenerFuncTyped<K, OopsFramework.TypedEventMap[K]>, object: any): void {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上注册事件');
|
||||
return;
|
||||
}
|
||||
this.event.on(event as string, listener as ListenerFunc, object);
|
||||
this.event.watch(event, listener, object);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,11 +63,7 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param object 侦听函数绑定的this对象
|
||||
*/
|
||||
watchOnce<K extends keyof OopsFramework.TypedEventMap>(event: K, listener: ListenerFuncTyped<K, OopsFramework.TypedEventMap[K]>, object: any): void {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上注册一次性事件');
|
||||
return;
|
||||
}
|
||||
this.event.once(event as string, listener as ListenerFunc, object);
|
||||
this.event.watchOnce(event, listener, object);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,8 +73,7 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param object 侦听函数绑定的this对象(可选)
|
||||
*/
|
||||
unwatch<K extends keyof OopsFramework.TypedEventMap>(event: K, listener?: ListenerFuncTyped<K, OopsFramework.TypedEventMap[K]>, object?: any): void {
|
||||
if (this._destroyed) return;
|
||||
this.event.off(event as string, listener as ListenerFunc, object);
|
||||
this.event.unwatch(event, listener, object);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,10 +82,6 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param data 事件数据
|
||||
*/
|
||||
emit<K extends keyof OopsFramework.TypedEventMap>(event: K, data?: OopsFramework.TypedEventMap[K]): void {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上触发事件');
|
||||
return;
|
||||
}
|
||||
this.event.emit(event, data);
|
||||
}
|
||||
|
||||
@@ -127,17 +91,9 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param data 事件数据(必须完全匹配类型定义)
|
||||
*/
|
||||
emitAsync<K extends keyof OopsFramework.TypedEventMap>(event: K, data: OopsFramework.TypedEventMap[K]): Promise<void> {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上触发异步事件');
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.event.emitAsync(event, data);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 弱类型事件方法
|
||||
|
||||
/**
|
||||
* 注册全局事件
|
||||
* @param event 事件名
|
||||
@@ -145,10 +101,6 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param object 侦听函数绑定的this对象
|
||||
*/
|
||||
on(event: string, listener: ListenerFunc, object: object) {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上注册事件');
|
||||
return;
|
||||
}
|
||||
this.event.on(event, listener, object);
|
||||
}
|
||||
|
||||
@@ -159,10 +111,6 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param object 侦听函数绑定的this对象
|
||||
*/
|
||||
once(event: string, listener: ListenerFunc, object: object) {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上注册一次性事件');
|
||||
return;
|
||||
}
|
||||
this.event.once(event, listener, object);
|
||||
}
|
||||
|
||||
@@ -173,7 +121,6 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param object 侦听函数绑定的this对象(可选)
|
||||
*/
|
||||
off(event: string, listener?: ListenerFunc, object?: object) {
|
||||
if (this._destroyed) return;
|
||||
this.event.off(event, listener, object);
|
||||
}
|
||||
|
||||
@@ -183,10 +130,6 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param args 事件参数
|
||||
*/
|
||||
dispatchEvent(event: string, ...args: unknown[]) {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上触发事件');
|
||||
return;
|
||||
}
|
||||
this.event.dispatchEvent(event, ...args);
|
||||
}
|
||||
|
||||
@@ -196,10 +139,6 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* @param args 事件参数
|
||||
*/
|
||||
dispatchEventAsync(event: string, ...args: unknown[]): Promise<void> {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上触发异步事件');
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.event.dispatchEventAsync(event, ...args);
|
||||
}
|
||||
|
||||
@@ -212,22 +151,8 @@ export class CCBusiness<T extends CCEntity> {
|
||||
* onGlobal(event: string, args: unknown) { console.log(args) };
|
||||
*/
|
||||
protected setEvent(...args: string[]) {
|
||||
if (this._destroyed) {
|
||||
console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上批量设置事件');
|
||||
return;
|
||||
}
|
||||
for (const name of args) {
|
||||
const func = (this as Record<string, unknown>)[name];
|
||||
if (typeof func === 'function') {
|
||||
this.on(name, func as ListenerFunc, this);
|
||||
}
|
||||
else {
|
||||
console.error('[OopsFramework]', `名为【${name}】的全局事方法不存在`);
|
||||
}
|
||||
}
|
||||
this.event.setEvent(...args);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@@ -106,30 +106,36 @@ export abstract class CCEntity extends ecs.Entity {
|
||||
|
||||
node = result;
|
||||
|
||||
const comp = node.getComponent(ctor);
|
||||
if (comp) this.add(comp as unknown as ecs.Comp);
|
||||
|
||||
// 检查实体是否已销毁
|
||||
if ( !this.isValid) {
|
||||
console.warn('[OopsFramework]', `实体已销毁,取消添加预制体组件: ${(ctor as any).name}`);
|
||||
// 移除已添加的 ECS 组件
|
||||
if (comp) this.remove(ctor as unknown as CompType<ecs.IComp>);
|
||||
node.destroy();
|
||||
return null;
|
||||
}
|
||||
|
||||
const comp = node.getComponent(ctor);
|
||||
if (comp) this.add(comp as unknown as ecs.Comp);
|
||||
node.parent = parent.node;
|
||||
}
|
||||
// 手动内存管理
|
||||
else {
|
||||
node = await ViewUtil.createPrefabNodeAsync(path, bundleName);
|
||||
|
||||
const comp = node.getComponent(ctor);
|
||||
if (comp) this.add(comp as unknown as ecs.Comp);
|
||||
|
||||
// 检查实体是否已销毁
|
||||
if (!this.isValid) {
|
||||
console.warn('[OopsFramework]', `实体已销毁,取消添加预制体组件: ${(ctor as any).name}`);
|
||||
// 移除已添加的 ECS 组件
|
||||
if (comp) this.remove(ctor as unknown as CompType<ecs.IComp>);
|
||||
node.destroy();
|
||||
return null;
|
||||
}
|
||||
|
||||
const comp = node.getComponent(ctor);
|
||||
if (comp) this.add(comp as unknown as ecs.Comp);
|
||||
node.parent = parent;
|
||||
}
|
||||
|
||||
@@ -175,16 +181,18 @@ export abstract class CCEntity extends ecs.Entity {
|
||||
|
||||
const node = await oops.gui.open(key, params);
|
||||
|
||||
const comp = node.getComponent(ctor) as unknown as ecs.Comp;
|
||||
if (comp) this.add(comp);
|
||||
|
||||
// 检查实体是否已销毁
|
||||
if (!this.isValid) {
|
||||
console.warn('[OopsFramework]', `实体已销毁,取消添加界面组件: ${key}`);
|
||||
// 移除已添加的 ECS 组件
|
||||
if (comp) this.remove(ctor as unknown as CompType<ecs.IComp>);
|
||||
oops.gui.remove(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
const comp = node.getComponent(ctor) as unknown as ecs.Comp;
|
||||
if (comp) this.add(comp);
|
||||
|
||||
oops.gui.show(key);
|
||||
return node;
|
||||
}
|
||||
@@ -288,7 +296,7 @@ export abstract class CCEntity extends ecs.Entity {
|
||||
this.businesss.delete(cls);
|
||||
|
||||
// 清理实体上的业务逻辑组件引用
|
||||
Reflect.set(this, cls.name, null);
|
||||
delete (this as any)[cls.name];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,6 +307,7 @@ export abstract class CCEntity extends ecs.Entity {
|
||||
if (this.singletons) {
|
||||
this.singletons.forEach((entity) => {
|
||||
if (entity && typeof entity.destroy === 'function') {
|
||||
this.removeChild(entity);
|
||||
entity.destroy();
|
||||
}
|
||||
});
|
||||
@@ -308,7 +317,11 @@ export abstract class CCEntity extends ecs.Entity {
|
||||
|
||||
// 2. 再销毁所有业务组件
|
||||
if (this.businesss) {
|
||||
this.businesss.forEach((business) => business.destroy());
|
||||
this.businesss.forEach((business, cls) => {
|
||||
business.destroy();
|
||||
// 清理实体上的业务逻辑组件引用
|
||||
delete (this as any)[cls.name];
|
||||
});
|
||||
this.businesss.clear();
|
||||
this.businesss = null!;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-11-11 19:05:32
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-06 17:20:51
|
||||
*/
|
||||
|
||||
import type { ecs } from '../../libs/ecs/ECS';
|
||||
import { ECSModel } from '../../libs/ecs/ECSModel';
|
||||
import { VM } from '../../libs/model-view/ViewModel';
|
||||
@@ -94,7 +87,7 @@ export abstract class CCView<T extends CCEntity> extends GameComponent implement
|
||||
|
||||
const data = this.data;
|
||||
if (data === undefined || data === null) {
|
||||
console.warn('[OopsFramework]', `${this.constructor.name}: mvvm=true 但 data 未定义,VM 绑定已跳过`);
|
||||
console.warn('[OopsFramework]', `${this.constructor.name}: mvvm = true 但 data 未定义,VM 绑定已跳过`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -195,13 +188,13 @@ export abstract class CCView<T extends CCEntity> extends GameComponent implement
|
||||
|
||||
const tid = this.tid;
|
||||
if (tid < 0) {
|
||||
console.error('[OopsFramework]', `组件 ${this.name} 移除失败,组件未注册 (tid=${tid})`);
|
||||
console.error('[OopsFramework]', `组件 ${this.name} 移除失败,组件未注册 (tid = ${tid})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const cct = ECSModel.compCtors[tid];
|
||||
if (!cct) {
|
||||
console.error('[OopsFramework]', `组件 ${this.name} 移除失败,组件构造函数不存在 (tid=${tid})`);
|
||||
console.error('[OopsFramework]', `组件 ${this.name} 移除失败,组件构造函数不存在 (tid = ${tid})`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2021-11-11 19:05:32
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-09-06 17:20:51
|
||||
*/
|
||||
|
||||
import type { CCEntity } from './CCEntity';
|
||||
import { CCView } from './CCView';
|
||||
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
/*
|
||||
* @Author: dgflash
|
||||
* @Date: 2022-04-14 17:08:01
|
||||
* @LastEditors: dgflash
|
||||
* @LastEditTime: 2022-12-13 11:36:00
|
||||
*/
|
||||
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 type { ListenerFunc, ListenerFuncTyped } 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 { 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';
|
||||
import type { GamePartAudio } from './part/GamePartAudio';
|
||||
import type { GamePartButton } from './part/GamePartButton';
|
||||
import type { GamePartNodePool } from './part/GamePartNodePool';
|
||||
import type { GamePartEvent } from './part/GamePartEvent';
|
||||
import type { GamePartKeyboard } from './part/GamePartKeyboard';
|
||||
import type { GamePartNode } from './part/GamePartNode';
|
||||
import type { GamePartRes } from './part/GamePartRes';
|
||||
import { createPart, GamePartRegistry, GamePartKey } from './GamePartRegistry';
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
@@ -47,40 +41,45 @@ const { ccclass } = _decorator;
|
||||
*/
|
||||
@ccclass('GameComponent')
|
||||
export class GameComponent extends Component {
|
||||
private _viewRegistry: GameViewModuleRegistry | null = null;
|
||||
private _parts: GamePartRegistry | null = null;
|
||||
|
||||
private get viewRegistry(): GameViewModuleRegistry {
|
||||
return (this._viewRegistry ??= new GameViewModuleRegistry(this));
|
||||
private get parts(): GamePartRegistry {
|
||||
return (this._parts ??= createPart(this));
|
||||
}
|
||||
|
||||
/** 获取事件模块 */
|
||||
get event(): GameEventModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Event);
|
||||
get event(): GamePartEvent {
|
||||
return this.parts.get(GamePartKey.Event);
|
||||
}
|
||||
|
||||
/** 获取节点模块 */
|
||||
get nodes(): GameNodeModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Nodes);
|
||||
get nodes(): GamePartNode {
|
||||
return this.parts.get(GamePartKey.Nodes);
|
||||
}
|
||||
|
||||
/** 获取资源模块 */
|
||||
get res(): GameResModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Res);
|
||||
get res(): GamePartRes {
|
||||
return this.parts.get(GamePartKey.Res);
|
||||
}
|
||||
|
||||
/** 获取音频模块 */
|
||||
get audio(): GameAudioModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Audio);
|
||||
get audio(): GamePartAudio {
|
||||
return this.parts.get(GamePartKey.Audio);
|
||||
}
|
||||
|
||||
/** 获取按钮模块 */
|
||||
get button(): GameButtonModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Button);
|
||||
get button(): GamePartButton {
|
||||
return this.parts.get(GamePartKey.Button);
|
||||
}
|
||||
|
||||
/** 获取键盘模块 */
|
||||
get keyboard(): GameKeyboardModule {
|
||||
return this.viewRegistry.get(ViewModuleKey.Keyboard);
|
||||
get keyboard(): GamePartKeyboard {
|
||||
return this.parts.get(GamePartKey.Keyboard);
|
||||
}
|
||||
|
||||
/** 游戏节点池模块 */
|
||||
get pool(): GamePartNodePool {
|
||||
return this.parts.get(GamePartKey.Pool);
|
||||
}
|
||||
|
||||
/** 移除当前节点 */
|
||||
@@ -90,20 +89,10 @@ export class GameComponent extends Component {
|
||||
|
||||
/** 组件销毁时调用 */
|
||||
protected onDestroy() {
|
||||
this._viewRegistry?.destroy();
|
||||
this._parts?.destroy();
|
||||
}
|
||||
|
||||
/** 打印全局资源状态 */
|
||||
static printGlobalResStatus() {
|
||||
resAutoTracker.printStatus();
|
||||
}
|
||||
|
||||
/** 设置资源调试模式 */
|
||||
static setResDebugMode(enabled: boolean) {
|
||||
resAutoTracker.enableDebug(enabled);
|
||||
}
|
||||
|
||||
//#region ========== 兼容旧版本 API ==========
|
||||
//#region ========== 兼容旧版本 API 如果是新项目可以把注释包起来的代码都删除 ==========
|
||||
|
||||
//#region 全局事件管理(兼容旧版本)
|
||||
/** @deprecated 请使用 this.event.watch() */
|
||||
@@ -160,7 +149,7 @@ export class GameComponent extends Component {
|
||||
//#region 预制节点管理(兼容旧版本)
|
||||
/** @deprecated 请使用 this.nodes.getNode() */
|
||||
getNode(name: string): Node | undefined {
|
||||
return this.nodes.getNode(name);
|
||||
return this.nodes.get(name);
|
||||
}
|
||||
|
||||
/** @deprecated 请使用 this.nodes.nodeTreeInfoLite() */
|
||||
@@ -177,7 +166,7 @@ export class GameComponent extends Component {
|
||||
//#region 资源加载管理(兼容旧版本)
|
||||
/** @deprecated 请使用 this.res.getRes() */
|
||||
getRes<T extends Asset>(path: string, type?: __private.__types_globals__Constructor<T> | null, bundleName?: string): T | null {
|
||||
return this.res.getRes(path, type, bundleName);
|
||||
return this.res.get(path, type, bundleName);
|
||||
}
|
||||
|
||||
/** @deprecated 请使用 this.res.load() */
|
||||
@@ -216,12 +205,12 @@ export class GameComponent extends Component {
|
||||
//#endregion
|
||||
|
||||
//#region 音频播放管理(兼容旧版本)
|
||||
/** @deprecated 请使用 this.audio.playMusic() */
|
||||
playMusic(url: string, params?: IAudioParams): void {
|
||||
this.audio.playMusic(url, params);
|
||||
/** @deprecated 请使用 await this.audio.playMusic() */
|
||||
async playMusic(url: string, params?: IAudioParams): Promise<void> {
|
||||
return this.audio.playMusic(url, params);
|
||||
}
|
||||
|
||||
/** @deprecated 请使用 this.audio.playEffect() */
|
||||
/** @deprecated 请使用 await this.audio.playEffect() */
|
||||
playEffect(url: string, params?: IAudioParams): Promise<AudioEffect | null> {
|
||||
return this.audio.playEffect(url, params);
|
||||
}
|
||||
@@ -230,7 +219,7 @@ export class GameComponent extends Component {
|
||||
//#region 游戏逻辑事件(兼容旧版本)
|
||||
/** @deprecated 请使用 this.button.setButton() */
|
||||
protected setButton(bindRootEvent = true): void {
|
||||
this.button.setButton(bindRootEvent);
|
||||
this.button.bind(bindRootEvent);
|
||||
}
|
||||
|
||||
/** @deprecated 请使用 this.event.setEvent() */
|
||||
|
||||
10
assets/module/common/GamePartBase.ts
Normal file
10
assets/module/common/GamePartBase.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/** GameComponent 子模块基类(可用于任意宿主对象) */
|
||||
export abstract class GamePartBase {
|
||||
/** 构造函数
|
||||
* @param comp 宿主对象(如 GameComponent 或 CCEntity)
|
||||
*/
|
||||
constructor(protected readonly comp: object) {}
|
||||
|
||||
/** 组件销毁时回调,子类按需覆盖 */
|
||||
destroy(): void {}
|
||||
}
|
||||
9
assets/module/common/GamePartBase.ts.meta
Normal file
9
assets/module/common/GamePartBase.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "59760005-af28-46f1-9fe5-f0db224c0f82",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
100
assets/module/common/GamePartRegistry.ts
Normal file
100
assets/module/common/GamePartRegistry.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { GamePartAudio } from './part/GamePartAudio';
|
||||
import { GamePartButton } from './part/GamePartButton';
|
||||
import { GamePartNodePool } from './part/GamePartNodePool';
|
||||
import { GamePartEvent } from './part/GamePartEvent';
|
||||
import { GamePartKeyboard } from './part/GamePartKeyboard';
|
||||
import { GamePartNode } from './part/GamePartNode';
|
||||
import { GamePartRes } from './part/GamePartRes';
|
||||
import { GamePartBase } from './GamePartBase';
|
||||
|
||||
/**
|
||||
* 子模块注册键
|
||||
* @remarks 枚举顺序即销毁顺序(先输入/音频/资源/特效,最后事件)
|
||||
*/
|
||||
export enum GamePartKey {
|
||||
/** 按钮 */
|
||||
Button = 'button',
|
||||
/** 键盘 */
|
||||
Keyboard = 'keyboard',
|
||||
/** 音频 */
|
||||
Audio = 'audio',
|
||||
/** 资源 */
|
||||
Res = 'res',
|
||||
/** 节点池 */
|
||||
Pool = 'pool',
|
||||
/** 节点树 */
|
||||
Nodes = 'nodes',
|
||||
/** 全局事件 */
|
||||
Event = 'event',
|
||||
}
|
||||
|
||||
/** 模块创建器类型 */
|
||||
type ModuleCreator = (comp: object) => GamePartBase;
|
||||
|
||||
/** 子模块懒加载注册表(统一登记、按序批量销毁) */
|
||||
export class GamePartRegistry {
|
||||
private instances: Map<GamePartKey, GamePartBase> | null = null;
|
||||
private creators: Map<GamePartKey, ModuleCreator>;
|
||||
|
||||
/** 构造函数
|
||||
* @param comp 宿主对象
|
||||
* @param creators 模块创建器映射表
|
||||
*/
|
||||
constructor(
|
||||
private readonly comp: object,
|
||||
creators: Map<GamePartKey, ModuleCreator>
|
||||
) {
|
||||
this.creators = creators;
|
||||
}
|
||||
|
||||
/** 获取实例映射表(延迟创建) */
|
||||
private getInstances(): Map<GamePartKey, GamePartBase> {
|
||||
if (!this.instances) {
|
||||
this.instances = new Map<GamePartKey, GamePartBase>();
|
||||
}
|
||||
return this.instances;
|
||||
}
|
||||
|
||||
/** 获取模块实例
|
||||
* @param key 模块键
|
||||
* @returns 模块实例
|
||||
*/
|
||||
get<M extends GamePartBase = GamePartBase>(key: GamePartKey): M {
|
||||
const instances = this.getInstances();
|
||||
let module = instances.get(key) as M | undefined;
|
||||
if (!module) {
|
||||
const creator = this.creators.get(key);
|
||||
if (!creator) {
|
||||
throw new Error(`未找到模块创建器: ${key}`);
|
||||
}
|
||||
module = creator(this.comp) as M;
|
||||
instances.set(key, module);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
/** 销毁所有模块 */
|
||||
destroy(): void {
|
||||
if (this.instances) {
|
||||
for (const key of Object.values(GamePartKey)) {
|
||||
this.instances.get(key)?.destroy();
|
||||
}
|
||||
this.instances.clear();
|
||||
this.instances = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GAME_PART = new Map<GamePartKey, ModuleCreator>([
|
||||
[GamePartKey.Button, (comp) => new GamePartButton(comp)],
|
||||
[GamePartKey.Keyboard, (comp) => new GamePartKeyboard(comp)],
|
||||
[GamePartKey.Audio, (comp) => new GamePartAudio(comp)],
|
||||
[GamePartKey.Res, (comp) => new GamePartRes(comp)],
|
||||
[GamePartKey.Pool, (comp) => new GamePartNodePool(comp)],
|
||||
[GamePartKey.Nodes, (comp) => new GamePartNode(comp)],
|
||||
[GamePartKey.Event, (comp) => new GamePartEvent(comp)],
|
||||
]);
|
||||
|
||||
export function createPart(comp: object): GamePartRegistry {
|
||||
return new GamePartRegistry(comp, GAME_PART);
|
||||
}
|
||||
9
assets/module/common/GamePartRegistry.ts.meta
Normal file
9
assets/module/common/GamePartRegistry.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "f0d4afee-df23-475e-871c-72a80c535ede",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/module/common/part.meta
Normal file
9
assets/module/common/part.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "cc86c2b1-3471-46ff-878d-b9efc9feee0f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
128
assets/module/common/part/GamePartAudio.ts
Normal file
128
assets/module/common/part/GamePartAudio.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { AudioClip } from 'cc';
|
||||
import type { GameComponent } from '../GameComponent';
|
||||
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 { GamePartBase } from '../GamePartBase';
|
||||
|
||||
/** 音频播放选项 */
|
||||
export interface IAudioPlayOptions extends IAudioParams {
|
||||
/** 资源包名 */
|
||||
bundle?: string;
|
||||
/** 是否为远程资源 */
|
||||
isRemote?: boolean;
|
||||
}
|
||||
|
||||
/** 音频播放
|
||||
* 资源内存由 ResAutoTracker 自动管理,界面销毁时自动释放
|
||||
*/
|
||||
export class GamePartAudio extends GamePartBase {
|
||||
/** 宿主组件 */
|
||||
protected declare comp: GameComponent;
|
||||
|
||||
/** 当前播放的背景音乐资源 */
|
||||
private currentMusic: AudioClip | null = null;
|
||||
|
||||
/** 检查音乐功能是否启用 */
|
||||
private isMusicEnabled(): boolean {
|
||||
return oops.audio?.music?.getSwitch?.() ?? true;
|
||||
}
|
||||
|
||||
/** 检查音效功能是否启用 */
|
||||
private isEffectEnabled(): boolean {
|
||||
return oops.audio?.effect?.getSwitch?.() ?? true;
|
||||
}
|
||||
|
||||
/** 播放背景音乐
|
||||
* @param url 音频资源路径或URL
|
||||
* @param options 音频播放选项
|
||||
*/
|
||||
async playMusic(url: string, options?: IAudioPlayOptions): Promise<void> {
|
||||
// 音乐功能被禁用时直接返回
|
||||
if (!this.isMusicEnabled()) return;
|
||||
|
||||
const clip = await this.loadAudioClip(url, options);
|
||||
|
||||
if (!clip) {
|
||||
console.warn(`背景音乐资源加载失败: ${url}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用 this.comp.isValid 检查组件是否有效
|
||||
if (!this.comp.isValid) {
|
||||
// 界面已销毁,不播放
|
||||
return;
|
||||
}
|
||||
|
||||
// 再次检查音乐功能是否启用(异步加载期间可能被禁用)
|
||||
if (!this.isMusicEnabled()) return;
|
||||
|
||||
// 记录当前背景音乐
|
||||
this.currentMusic = clip;
|
||||
|
||||
// 使用已加载的 AudioClip 播放
|
||||
oops.audio.playMusic(clip, options);
|
||||
}
|
||||
|
||||
/** 播放音效
|
||||
* @param url 音频资源路径或URL
|
||||
* @param options 音频播放选项
|
||||
* @returns 音效对象
|
||||
*/
|
||||
async playEffect(url: string, options?: IAudioPlayOptions): Promise<AudioEffect | null> {
|
||||
// 音效功能被禁用时直接返回
|
||||
if (!this.isEffectEnabled()) return null;
|
||||
|
||||
const clip = await this.loadAudioClip(url, options);
|
||||
|
||||
if (!clip) {
|
||||
console.warn(`音效资源加载失败: ${url}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 使用 this.comp.isValid 检查组件是否有效
|
||||
if (!this.comp.isValid) {
|
||||
// 界面已销毁,不播放
|
||||
return null;
|
||||
}
|
||||
|
||||
// 再次检查音效功能是否启用(异步加载期间可能被禁用)
|
||||
if (!this.isEffectEnabled()) return null;
|
||||
|
||||
// 使用已加载的 AudioClip 播放
|
||||
const ae = oops.audio.playEffect(clip, options);
|
||||
return ae;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件销毁时停止音乐并清理
|
||||
* 资源释放由 ResAutoTracker 自动处理
|
||||
*/
|
||||
destroy(): void {
|
||||
// 停止当前背景音乐
|
||||
if (this.currentMusic) {
|
||||
oops.audio.music.stop();
|
||||
this.currentMusic = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载音频资源
|
||||
* @param url 资源路径或URL
|
||||
* @param options 音频播放选项
|
||||
* @returns 音频资源
|
||||
*/
|
||||
private async loadAudioClip(url: string, options?: IAudioPlayOptions): Promise<AudioClip | null> {
|
||||
const isRemote = options?.isRemote ?? false;
|
||||
const bundle = options?.bundle ?? resLoader.defaultBundleName;
|
||||
|
||||
if (isRemote) {
|
||||
// 加载远程资源(ResAutoTracker 自动管理)
|
||||
return await this.comp.res.loadRemote<AudioClip>(url);
|
||||
} else {
|
||||
// 加载本地资源(ResAutoTracker 自动管理)
|
||||
return await this.comp.res.load(bundle, url, AudioClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/module/common/part/GamePartAudio.ts.meta
Normal file
9
assets/module/common/part/GamePartAudio.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "adfbddd7-4af6-4b88-8da9-20c924249f08",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
/*
|
||||
* @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';
|
||||
import type { GameComponent } from '../GameComponent';
|
||||
import { GamePartBase } from '../GamePartBase';
|
||||
|
||||
/** 界面按钮批量绑定 */
|
||||
export class GameButtonModule extends GameViewModule {
|
||||
export class GamePartButton extends GamePartBase {
|
||||
/** 宿主组件 */
|
||||
protected declare comp: GameComponent;
|
||||
|
||||
/** 设置按钮事件绑定
|
||||
* @param bindRootEvent 是否绑定根节点事件,默认为 true
|
||||
*/
|
||||
setButton(bindRootEvent = true): void {
|
||||
bind(bindRootEvent = true): void {
|
||||
if (bindRootEvent) {
|
||||
this.comp.node.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
|
||||
const self: any = this.comp;
|
||||
9
assets/module/common/part/GamePartButton.ts.meta
Normal file
9
assets/module/common/part/GamePartButton.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "1443b95a-646b-46d6-a15c-286b7f586b81",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
/*
|
||||
* @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';
|
||||
import { GamePartBase } from '../GamePartBase';
|
||||
|
||||
/** 全局事件管理(含游戏前后台、画布、全屏、旋转等生命周期) */
|
||||
export class GameEventModule extends GameViewModule {
|
||||
export class GamePartEvent extends GamePartBase {
|
||||
private _event: EventDispatcher | null = null;
|
||||
|
||||
/** 获取事件分发器 */
|
||||
@@ -178,4 +173,4 @@ export class GameEventModule extends GameViewModule {
|
||||
this._event = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/module/common/part/GamePartEvent.ts.meta
Normal file
9
assets/module/common/part/GamePartEvent.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "33227a0d-d0a5-4c97-970d-b082c713cd1e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
/*
|
||||
* @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';
|
||||
import { GamePartBase } from '../GamePartBase';
|
||||
|
||||
/** 键盘事件回调 */
|
||||
export interface KeyboardCallbacks {
|
||||
@@ -15,7 +10,7 @@ export interface KeyboardCallbacks {
|
||||
}
|
||||
|
||||
/** 键盘输入 */
|
||||
export class GameKeyboardModule extends GameViewModule {
|
||||
export class GamePartKeyboard extends GamePartBase {
|
||||
private _enabled = false;
|
||||
private _callbacks: KeyboardCallbacks | null = null;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user