Cocoscreator: 用装饰器自动绑定节点

🚀有时候,你有没有觉得在Cocos Creator里一遍又一遍地手动绑定节点是一件非常无聊的事?如果你同意,那么告诉你一个秘密:有一种方法!只需一行代码的"魔法",就可以自动绑定节点,省下频繁重复的手动拖拽节点的苦差事!好吧,这并不是真正的魔法,而是装饰器!🌈

装饰器是什么?

简而言之,装饰器在编程中就像是一个魔法药水。它们可以注入到类、属性、方法等中,提供某些额外的功能或修改它们的行为。而在TypeScript中,这种能力变得更加强大,因为装饰器可以和类型系统无缝结合。

进入正题:自动绑定节点的装饰器

🧙‍下面是一个名为bindComp的装饰器函数。这个小家伙是我们今天的主角,它的使命是在onLoad时,自动为所有注入的成员变量设置set&get方法,并在首次访问时为它赋值。它会让你在第一次访问这个属性的时候,自动在节点树上找到它并赋值给这个属性。嗯,就是这么酷!

typescript 复制代码
// 查找指定路径下的节点或组件
function __find<T>(path: string, node: cc.Node, type: FIND_TYPE<T>) {
    let temp = cc.find(path, node);
    if (cc.js.isChildClassOf(type, cc.Component)) {
        let comp = temp?.getComponent(type)
        return comp;
    }
    return temp;
}

const _FIND_OPTIONS_ = "_FIND_OPTIONS_"

// 目标类型,通常用于查找组件
interface FIND_TYPE<T> {
    new(): T;
}

// 查找选项的配置 
interface FindOption<T> {
    path: string; // 查找路径
    type: FIND_TYPE<T>; // 目标类型
    member: string; // 成员变量名称
    root?: string; // 根节点的路径
}

/**
 * @description 当onLoad时,自动对所有注入的成员变量设置set&get方法,当成员变量首次调用时对成员变量赋值
 * @param path 相对于当前脚本this.node的搜索路径,当rootPath非空,则以rootPath为根节点查找
 * @param type 查找组件类型
 * @param rootPath 相对于this.node 的搜索路径,不传入时,以当的this.node为根节点进行查找
 */
export function bindComp<T extends cc.Component | cc.Node>(path: string, type: FIND_TYPE<T>, rootPath?: string) {
    return function (target: any, member: string) {
        if (!(target instanceof cc.Component)) {
            Log.e("无法注入,仅支持 Component 组件")
            return;
        }
        let obj: any = target;
        if (!Reflect.has(target, _FIND_OPTIONS_)) {
            // 重写onLoad,当onLoad时,自动对所有注入的成员变量设置set&get方法,当成员变量首次调用时对成员变量赋值
            let __onLoad = obj.onLoad
            obj.onLoad = function () {
                let self = this;
                let fOption = Reflect.get(self, _FIND_OPTIONS_)
                for (let key in fOption) {
                    let ele: FindOption<T> = Reflect.get(fOption, key)
                    if (!Reflect.get(self, ele.member)) {
                        Reflect.defineProperty(self, ele.member, {
                            enumerable: true,
                            configurable: true,
                            get() {
                                let node = self.node;
                                if (ele.root) {
                                    let rootMemberName = `__${ele.root.replace(/\//g, "_")}`
                                    if (!cc.isValid(self[rootMemberName])) {
                                        self[rootMemberName] = __find(ele.root, node, cc.Node);
                                    }
                                    node = self[rootMemberName];
                                    if (CC_DEBUG && !cc.isValid(node)) {
                                        Log.e(`${cc.js.getClassName(self)}.${ele.root}节点不存在!!!`)
                                    }
                                }

                                if (!cc.isValid(self[key])) {
                                    self[key] = __find(ele.path, node, ele.type);
                                }
                                return self[key];
                            },
                            set(v) {
                                self[key] = v;
                            }
                        })
                    }
                }
                __onLoad && Reflect.apply(__onLoad, this, arguments);
            }


            // 重写onDestroy,当onDestroy时,删除所有注入的成员变量
            let __onDestroy = obj.onDestroy
            obj.onDestroy = function () {
                let self = this;
                let fOption = Reflect.get(self, _FIND_OPTIONS_)
                for (let key in fOption) {
                    let ele: FindOption<T> = Reflect.get(fOption, key)
                    Reflect.deleteProperty(self, ele.member);
                }
                __onDestroy && Reflect.apply(__onDestroy, this, arguments);
            }

            Reflect.defineProperty(target, _FIND_OPTIONS_, { value: {} })
        }

        // 将装饰器的配置信息存储到一个 `_FIND_OPTIONS_` 属性中,以便后续在组件的 `onLoad` 方法中使用
        let option: FindOption<T> = { path: path, type: type, member: member, root: rootPath }
        let attribute = `__${member}`
        let fOption = Reflect.get(target, _FIND_OPTIONS_)
        Reflect.defineProperty(fOption, attribute, { value: option, enumerable: true })
    }
}

这段代码初看起来可能有些吓人,但其实它很简单,让我们逐步深入了解。

🤹一步一步解释代码吧!

1️⃣ 首先,我们的这个bindComp装饰器接受几个参数:pathtyperootPath。前两个必传,最后一个可传可不传。

  • path是相对于当前脚本的this.node的搜索路径。
  • type是要查找的组件类型。
  • rootPath,这个小家伙是个可选项,如果你给它传递了值,它会从这个路径开始作为根节点进行搜索

2️⃣ 在onLoad方法中(也就是组件加载的时候),这个装饰器会走遍所有注入的成员变量,执行查找并绑定的任务。

3️⃣ 这个装饰器还会负责清理工作,在组件销毁的时候,它会在onDestroy方法里删除之前绑定的属性,确保没有留下可能引发内存泄漏的垃圾。

🧐用起来是什么样子呢?

typescript 复制代码
class MyComponent extends cc.Component {
    @bindComp("path/to/node", cc.Node)
    public myNode: cc.Node | null = null;
    
    onLoad() {
        console.log(this.myNode);
    }
}

🚴就这样!就定义了一个myNode,然后就可以直接用了,剩下的一切都不用担心!装饰器@bindComp会负责查找和绑定。你所要做的只是在组件的代码里,愉快地使用这个myNode就行了!

myNode首次被访问时,bindComp会触发getter,这个getter会负责查找节点,并将它绑定到myNode上。

🎉结束语

🚁装饰器真的是TypeScript的一大神器,它能让我们的代码更加干净、模块化。通过这种方法,我们不仅可以节省大量的时间,还可以减少出错的机会。

最后,请记住,任何技术手段都需要根据实际情况进行调整。虽然这种方法看起来很酷,但在实际项目中使用时,要确保它不会引入新的问题。

相关推荐
VaJoy15 天前
Cocos Creator Shader 入门 ⑺ —— 图层混合样式的实现与 Render Texture
cocos creator
VaJoy17 天前
Cocos Creator Shader 入门 ⑹ —— 灰阶、反色等滤镜的实现
cocos creator
VaJoy19 天前
Cocos Creator Shader 入门 ⑸ —— 代码复用与绿幕抠图技术
cocos creator
VaJoy21 天前
Cocos Creator Shader 入门 ⑷ —— 纹理采样与受击闪白的实现
cocos creator
VaJoy22 天前
Cocos Creator Shader 入门 ⑶ —— 给节点设置透明度
cocos creator
VaJoy24 天前
Cocos Creator Shader 入门 (2) —— 给节点染色
cocos creator
VaJoy25 天前
Cocos Creator Shader —— 附录
cocos creator
成长ing121381 个月前
多层背景视差滚动Parallax Scrolling
cocos creator
似水流年wxk1 个月前
cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins
运维·jenkins·cocos creator
成长ing121383 个月前
点击音效系统
前端·cocos creator