花7块钱下载了一个自动绑定插件, 使用之后让我十分怀疑人生,实在是无法满足自己简易的需求,于是亲自上阵手撸一个运行时绑定功能.
正常情况下:UI与脚本的绑定流程如下:
- 在脚本中声明@property 属性,
- 在编辑器中增加相应的节点,
- 拖拽节点到编辑器属性绑定.
以上流程中如果所要绑定的节点数量较少时,根本不需要考虑自动绑定这种骚操作. 但通常有很多界面需要包含上百个属性, 一一拖拽十分麻烦. 因此有了需要自动绑定的需求.
使用了几个牛人开发的自动绑定插件, 几乎都是恨不得让开发者一行代码都不用写,希望能够一键生成要绑定的属性,并且自动绑定.
又或者是想要自动绑定,需要先配置一堆的命名白名单规则, 然后在编辑器中对每个节点的命名如旅薄冰.
越是想智能化, 受到的限制就越是强烈, 导致一个个好好的插件使用起来极度不便.
分析绑定流程后,得出了一个简单的需求: 第一步声明,和第二步建节点 是必须的操作. 不应省略. 因为属性是否需要声明是由开发者决定的. 代码中的业务才能决定是否需要声明某一属性. 唯一需要解放的操作就是烦琐的拖拽绑定.
绑定的原理分为编辑时插件自动绑定和 运行时动态查找绑定.
本文简单实现了运行时动态绑定逻辑, 只需要组件继承此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;
}
}


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