推荐一个偷懒的方式,使用装饰器自动绑定节点到脚本的属性
背景
用 Cocos Creator 写脚本组件的时候,有时需要场景中一个节点作为这个脚本的属性值。
按照官方文档推荐的方法,需要以下两步
添加一个
@property
属性,在场景中拖入这个节点。
为了省去场景中的拖拽,也有这样写法
添加属性
getChildByName
当属性多了,就要写一排相似的代码
使用
环境
Cocos Creator 3.8.1
只是为了偷懒
从上面的背景来看,相似的代码可以用装饰器去简化
添加一个
@child
属性,
这样就会直接去组件的子节点中寻找对应的需要的节点或组件,实现自动绑定啦!
代码
这代码不是我写的,是一起工作的扫地僧写的。他说这个东西没什么难度,可以分享给大家。
//Decorator.ts
type PropertyDecorator = (
$class: Record<string, any>, $propertyKey: string | symbol, $descriptorOrInitializer?: any,
) => void;
import { Node } from "cc"
const searchChild = function (node: Node, name: string) {
let ret = node.getChildByName(name);
if (ret) return ret;
for (let i = 0; i < node.children.length; i++) {
let child = node.children[i];
if (!child.isValid) continue;
ret = searchChild(child, name);
if (ret) return ret;
}
return null;
}
const CookDecoratorKey = ($desc: string) => `__ccc_decorator_${$desc}__`
const KeyChild = CookDecoratorKey("child_cache");
type ParamType = {
name?: string,
};
export function child($opt?: ParamType): PropertyDecorator {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return ($target, $propertyKey: string, $descriptorOrInitializer) => {
const cache: { propertyKey: string, childName: string }[] = $target[KeyChild] ??= [];
if (!cache.some($vo => $vo.propertyKey === $propertyKey)) {
cache.push({ propertyKey: $propertyKey, childName: $opt?.name || $propertyKey });
} else {
throw new Error(`child 装饰器重复绑定属性:${$propertyKey},class:${$target.name}`);
}
if (cache.length === 1) {
const oldOnLoad: () => void = $target.onLoad || undefined;//$target.onLoad也可以拿到父类的实现
$target.onLoad = function () {
cache.forEach($vo => this[$vo.propertyKey] = searchChild(this.node, $vo.childName));
oldOnLoad && oldOnLoad.apply(this);
};
}
};
}
import { Component } from "cc";
interface INewable<T = any> extends Function {
new(...args: any[]): T;
}
const KeyComp = CookDecoratorKey("comp_cache");
export function comp($compoentClass: INewable<Component>, $childName?: string, $mute = false): PropertyDecorator {
return ($target, $propertyKey: string, $descriptorOrInitializer) => {
const cache: { propertyKey: string, compClass: INewable<Component>, childName: string }[] = $target[KeyComp] ??= [];
if (!cache.some($vo => $vo.propertyKey === $propertyKey)) {
cache.push({ propertyKey: $propertyKey, compClass: $compoentClass, childName: $childName || $propertyKey });
} else {
if (!$mute) {
throw new Error(`comp装饰器重复绑定属性:${$propertyKey},class:${$target.name}`);
}
return;
}
if (cache.length === 1) {
const oldOnLoad: () => void = $target.onLoad || undefined;//$target.onLoad也可以拿到父类的实现
$target.onLoad = function () {
cache.forEach($vo => {
const node = ($vo.childName ? searchChild(this.node, $vo.childName) : this.node);
if (!node) {
if (!$mute) {
throw new Error(`comp装饰器没有找到适合的node节点:class:${$target.name},组件:${$compoentClass.name},childName:${$childName}`);
} else {
return;
}
}
this[$vo.propertyKey] = node.getComponent($vo.compClass) || node.addComponent($vo.compClass);
});
oldOnLoad && oldOnLoad.apply(this);
};
}
};
}
小结
装饰器实现其实就是面向切面的编程思想吧,貌似,可以在这个切面上面进行封装,偷懒写少点代码,然后高阶的实现目的就是依赖注入之类的思想,其实都是为了极限解耦 --BY 扫地僧
“点赞“ ”在看” 鼓励一下▼