干货分享: 手撸一个属性自动绑定功能.

花7块钱下载了一个自动绑定插件, 使用之后让我十分怀疑人生,实在是无法满足自己简易的需求,于是亲自上阵手撸一个运行时绑定功能.

正常情况下:UI与脚本的绑定流程如下:

  1. 在脚本中声明@property 属性,
  2. 在编辑器中增加相应的节点,
  3. 拖拽节点到编辑器属性绑定.
    以上流程中如果所要绑定的节点数量较少时,根本不需要考虑自动绑定这种骚操作. 但通常有很多界面需要包含上百个属性, 一一拖拽十分麻烦. 因此有了需要自动绑定的需求.

使用了几个牛人开发的自动绑定插件, 几乎都是恨不得让开发者一行代码都不用写,希望能够一键生成要绑定的属性,并且自动绑定.
又或者是想要自动绑定,需要先配置一堆的命名白名单规则, 然后在编辑器中对每个节点的命名如旅薄冰.

越是想智能化, 受到的限制就越是强烈, 导致一个个好好的插件使用起来极度不便.
分析绑定流程后,得出了一个简单的需求: 第一步声明,和第二步建节点 是必须的操作. 不应省略. 因为属性是否需要声明是由开发者决定的. 代码中的业务才能决定是否需要声明某一属性. 唯一需要解放的操作就是烦琐的拖拽绑定.

绑定的原理分为编辑时插件自动绑定和 运行时动态查找绑定.
本文简单实现了运行时动态绑定逻辑, 只需要组件继承此Component即可实现.


export class Component extends cc.Component {
    protected onLoad(): void {
        this.autoBindNode(this);
    }

    // 自动绑定脚本节点声明.
    private autoBindNode(comp: cc.Component) {
        const attrs = comp.constructor["__attrs__"];
        const mapKeys = {};
        Object.keys(attrs).map(value => {
            return value.substring(0, value.indexOf('$_$'));
        }).forEach(value => {
            mapKeys[value] = 1;
        });
        const keys = Object.keys(mapKeys)
        keys.forEach(value => {
            let def = attrs[`${value}$_$default`];
            switch (typeof def) {
                case "string":
                case "boolean":
                case "number": {
                    // 基本类型直接返回.
                    return;
                }
            }
            // 已有绑定值,不做处理.
            if (Array.isArray(comp[value])) {
                if (comp[value].length > 0) return;
            } else if (comp[value]) return;

            let realType = attrs[`${value}$_$ctor`];

            // 未设置具体的类型.
            if (!realType) {
                cc.warn("属性未设置绑定类型:", comp.name, value);
                return;
            }
            realType = realType.name.replace('_', '.');
            comp[value] = this.findChildren(comp.node, value, realType, Array.isArray(def));
        });
        keys.forEach(value => {
            if (typeof comp[value] === "object") {
                if (!comp[value]) {
                    cc.warn('自动绑定缺失:', comp.name, value);
                }
            }
        });
    }

    private findChildren(_node: cc.Node, name: string, compName: string, array: boolean = false): any {
        if (!_node || !name || !compName) return null;
        if (!array) {
            const node = (_node.name === name) ? _node : this.getChildrenByName(_node, name, 1)[0];
            if (compName === 'cc.Node') {
                return node;
            } else {
                return node ? node.getComponent(compName) : null;
            }
        } else {
            const nodes = this.getChildrenByName(_node, name);
            if (compName === 'cc.Node') {
                return nodes;
            } else {
                return nodes.map(item => {
                    return item.getComponent(compName);
                });
            }
        }
    }

    private getChildrenByName(node: cc.Node, name: string, count: number = 0): any[] {
        if (node.childrenCount <= 0) return [];
        const nodes = [];
        for (let i = 0; i < node.childrenCount; i++) {
            if (node.children[i].name === name) {
                nodes.push(node.children[i]);
            } else {
                nodes.push(...this.getChildrenByName(node.children[i], name, count));
            }
            if (count && nodes.length >= count) break;
        }
        return nodes;
    }
}
8赞

看来是吐槽我的插件了,先给你点个赞吧

1赞

哈哈@@@@大佬经常逛论坛啊,被发现了

1赞

哈哈,表面点赞

:crazy_face:

嘘~~~~

没用过咋用

哈哈哈哈强啊

有没有使用说明书

继承这个脚本就行了 不用看属性面板 他这个是动态获取的

是不是命名要包含 $_$

:relieved:麻烦产品经理上一个产品使用说明书 :upside_down_face:

1.只能按名字找,如果有同名可能会找错
2.type应该直接使用$_$ctor,这个值是类的构造函数,更准确。用类名找有可能有同名的。

1赞

绑定后,如何使用