Files
oops-plugin-framework/assets/core/utils/ViewUtil.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

163 lines
5.8 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 type { EventTouch, Node, Vec3 } from 'cc';
import { Animation, AnimationClip, instantiate, Prefab, Size, UITransform, v3 } from 'cc';
import { resLoader } from '../common/loader/ResLoader';
/** 显示对象工具 */
export class ViewUtil {
/**
* 把Node当前的节点树结构根据Node命名转成一个js对象,重名的组件会覆盖,
* Node的name不应该包含空格键否则将跳过
* @param parent 被遍历的Node组件
* @param obj 绑定的js对象 (可选)
*/
static nodeTreeInfoLite(parent: Node, obj?: Map<string, Node>): Map<string, Node> | null {
const map: Map<string, Node> = obj || new Map();
const items = parent.children;
for (let i = 0; i < items.length; i++) {
const _node = items[i];
if (_node.name.length > 0) {
if (map.has(_node.name))
console.error(`使用ViewUtil.nodeTreeInfoLite方法时发现重复的节点名称【${_node.name}`);
else
map.set(_node.name, _node);
}
ViewUtil.nodeTreeInfoLite(_node, map);
}
return map;
}
/**
* 正则搜索节点名字,符合条件的节点将会返回
* @param reg 正则表达式
* @param parent 要搜索的父节点
* @param nodes 返回的数组(可选)
*/
static findNodes(reg: RegExp, parent: Node, nodes?: Array<Node>): Array<Node> {
const ns: Array<Node> = nodes || [];
const items = parent.children;
for (let i = 0; i < items.length; i++) {
const _name: string = items[i].name;
if (reg.test(_name)) {
ns.push(items[i]);
}
ViewUtil.findNodes(reg, items[i], ns);
}
return ns;
};
/**
* 节点之间坐标互转
* @param a A节点
* @param b B节点
* @param aPos A节点空间中的相对位置
*/
static calculateASpaceToBSpacePos(a: Node, b: Node, aPos: Vec3): Vec3 {
const world: Vec3 = a.getComponent(UITransform)!.convertToWorldSpaceAR(aPos);
return b.getComponent(UITransform)!.convertToNodeSpaceAR(world);
}
/**
* 屏幕转空间坐标
* @param event 触摸事件
* @param space 转到此节点的坐标空间
*/
static calculateScreenPosToSpacePos(event: EventTouch, space: Node): Vec3 {
const uil = event.getUILocation();
const worldPos: Vec3 = v3(uil.x, uil.y);
return space.getComponent(UITransform)!.convertToNodeSpaceAR(worldPos);
}
/**
* 显示对象等比缩放
* @param targetWidth 目标宽
* @param targetHeight 目标高
* @param defaultWidth 默认宽
* @param defaultHeight 默认高
*/
static uniformScale(targetWidth: number, targetHeight: number, defaultWidth: number, defaultHeight: number) {
const widthRatio = defaultWidth / targetWidth;
const heightRatio = defaultHeight / targetHeight;
let ratio;
widthRatio < heightRatio ? ratio = widthRatio : ratio = heightRatio;
return new Size(Math.floor(targetWidth * ratio), Math.floor(targetHeight * ratio));
}
/**
* 从资源缓存中找到预制资源名并创建一个显示对象建议使用GameComponent里的同名方法能自动管理内存施放
* @param path 资源路径
* @param bundleName 资源包名
*/
static createPrefabNode(path: string, bundleName: string = resLoader.defaultBundleName): Node {
const p = resLoader.get(path, Prefab, bundleName);
if (p) {
return instantiate(p);
}
return null!;
}
/**
* 加载预制并创建预制节点建议使用GameComponent里的同名方法能自动管理内存施放
* @param path 资源路径
* @param bundleName 资源包名
*/
static createPrefabNodeAsync(path: string, bundleName: string = resLoader.defaultBundleName): Promise<Node> {
return new Promise(async (resolve, reject) => {
const p = await resLoader.load(bundleName, path, Prefab);
if (p) {
resolve(instantiate(p));
}
else {
console.error(`名为【${path}】的资源加载失败`);
resolve(null!);
}
});
}
/**
* 添加节点动画
* @param path 资源路径
* @param node 目标节点
* @param onlyOne 是否唯一
* @param isDefaultClip 是否播放默认动画剪辑
*/
static addNodeAnimation(path: string, node: Node, onlyOne = true, isDefaultClip = false) {
if (!node || !node.isValid) {
return;
}
let anim = node.getComponent(Animation);
if (anim == null) {
anim = node.addComponent(Animation);
}
const clip = resLoader.get(path, AnimationClip) as AnimationClip;
if (!clip) {
return;
}
if (onlyOne && anim.getState(clip.name) && anim.getState(clip.name).isPlaying) {
return;
}
if (isDefaultClip) {
anim.defaultClip = clip;
anim.play();
return;
}
// 播放完成后恢复播放默认动画
anim.once(Animation.EventType.FINISHED, () => {
if (anim!.defaultClip) {
anim!.play();
}
}, this);
if (anim.getState(clip.name)) {
anim.play(clip.name);
return;
}
anim.createState(clip, clip!.name);
anim.play(clip!.name);
}
}