每个开发者在调用UI节点的时候都需要提前定义:
@property({type:Node})
private target: Node = null;
如果一个场景或预制体内有很多节点都要调用,就要定义N次
我觉得应该有更好的办法实现智能解析绑定这些节点。
目前我自己实现的懒人方式,如下:
1、如果你要调用某个节点,那么在创建这个UI节点时前面加个@符号,如:
@loginBtn这个按钮节点位于Login这个根节点
2、然后我在绑定的ts脚本写了一段遍历注册所有@开头节点的代码:
/**
* 注册预制体的所有以@开头的子节点。
* @param rootNode 预制体的根节点。
*/
public registerNodes(rootNode: Node): void {
if (!rootNode) {
logMgr.err("根节点不存在。");
return;
}
// 从根节点开始递归遍历并注册节点,传递根节点名称作为初始路径
this.traverseAndRegister(rootNode, rootNode.name);
}
/**
* 递归遍历所有子节点并注册以@开头的节点。
* 使用节点的完整路径作为键。
* @param node 当前遍历的节点。
* @param path 当前节点的完整路径。
*/
private traverseAndRegister(node: Node, path: string): void {
// 如果节点名称以@开头,则使用当前的完整路径作为键
if (node.name.startsWith('@')) {
this._nodeMap.set(path, node);
logMgr.debug(`注册节点 ${path}`);
}
// 遍历并递归注册子节点,更新路径以包含当前节点的名称
node.children.forEach(child => {
const childPath = `${path}/${child.name}`;
this.traverseAndRegister(child, childPath);
});
}
/**
* 根据节点的完整路径获取节点。
* @param path 节点的完整路径。
* @returns 对应的节点或未找到时返回undefined。
*/
public getNode(path: string): Node | undefined {
return this._nodeMap.get(path);
}
这样我就实现了把Login预制体下所有@开头的节点都注册了
以后在任何地方只要使用:
xxx.getNode("Login/@loginBtn");
--------------------------------------------------------具体的代码,如下:
import { Node } from ‘cc’;
import { logMgr } from './LogMgr';
import { audioMgr } from './AudioMgr';
import { eventMgr } from './EventMgr';
/**
* 组件管理器
* 提供遍历预制体的所有子节点功能,并允许通过定义的键映射操作这些节点。
*/
class CompMgr {
private static _instance: CompMgr; // 单例实例
private _nodeMap: Map<string, Node> = new Map(); // 存储节点名称到节点的映射
// 私有构造函数,确保外部无法直接通过new创建实例
private constructor() {}
// 获取单例实例
public static get instance(): CompMgr {
if (!this._instance) {
this._instance = new CompMgr();
}
return this._instance;
}
/**
* 注册预制体的所有以@开头的子节点。
* @param rootNode 预制体的根节点。
*/
public registerNodes(rootNode: Node): void {
if (!rootNode) {
logMgr.err("根节点不存在。");
return;
}
// 从根节点开始递归遍历并注册节点,传递根节点名称作为初始路径
this.traverseAndRegister(rootNode, rootNode.name);
}
/**
* 递归遍历所有子节点并注册以@开头的节点。
* 使用节点的完整路径作为键。
* @param node 当前遍历的节点。
* @param path 当前节点的完整路径。
*/
private traverseAndRegister(node: Node, path: string): void {
// 如果节点名称以@开头,则使用当前的完整路径作为键
if (node.name.startsWith('@')) {
this._nodeMap.set(path, node);
logMgr.debug(`注册节点 ${path}`);
}
// 遍历并递归注册子节点,更新路径以包含当前节点的名称
node.children.forEach(child => {
const childPath = `${path}/${child.name}`;
this.traverseAndRegister(child, childPath);
});
}
/**
* 根据节点的完整路径获取节点。
* @param path 节点的完整路径。
* @returns 对应的节点或未找到时返回undefined。
*/
public getNode(path: string): Node | undefined {
return this._nodeMap.get(path);
}
/**
* 清除所有以指定根节点名称为前缀的子节点。
* @param rootNode 根节点。
*/
public removeNode(rootNode: Node): void {
const keysToRemove: string[] = [];
// 查找所有以rootNode.name为前缀的键
this._nodeMap.forEach((value, key) => {
if (key.startsWith(`${rootNode.name}/`)) {
keysToRemove.push(key);
}
});
// 移除找到的键
keysToRemove.forEach(key => {
this._nodeMap.delete(key);
logMgr.debug(`移除节点 ${key}`);
});
if (keysToRemove.length > 0) {
logMgr.debug(`已清除所有以 ${rootNode.name} 为前缀的节点`);
} else {
logMgr.warn(`未找到以 ${rootNode.name} 为前缀的节点`);
}
}
/**
* 清除所有注册的节点,释放内存。
*/
public clearAllNodes(): void {
this._nodeMap.clear();
logMgr.debug("清除所有节点");
}
/**
* 为指定的按钮设置点击播放音效并派发事件。
* @param tag 事件标签,用于把事件交给事件管理器统一处理。
* @param btnNode 按钮节点。
* @param bundleName 资源包名称,默认为 'resources'。
* @param name 音效资源名称,默认为 "audio/btn_click"。
*/
public setBtnSound(
tag: string,
btnNode: Node,
bundleName: string = 'resources',
name: string = "audio/btn_click"
): void {
btnNode.on(Node.EventType.TOUCH_START, () => {
// 播放点击音效
audioMgr.playEffect(bundleName, name);
// 派发点击事件
eventMgr.emit(tag);
}, this);
}
}
// 导出CompMgr的单例实例
export const compMgr = CompMgr.instance;