Files
oops-plugin-framework/assets/libs/gui/button/ButtonInterceptor.ts
dgflash d4f140077e 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 类型定义与导出规范
优化接口注释与类型约束,提升开发体验
2026-05-29 22:55:13 +08:00

188 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}