diff --git a/package.json b/package.json index 69a86ec..6f12407 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "scripts": { "clean": "rm -rf dist", - "copy": "cp -r dist/* ./../kunpo-esc/demo/node_modules/kunpocc-behaviortree/dist/", + "copy": "cp -r dist/* ./../kunpo-ecs/demo/node_modules/kunpocc-behaviortree/dist/", "build": "npm run clean && rollup -c rollup.config.mjs && npm run copy" }, "files": [ diff --git a/src/behaviortree/BT.ts b/src/behaviortree/BT.ts index 215b7ac..61cc909 100644 --- a/src/behaviortree/BT.ts +++ b/src/behaviortree/BT.ts @@ -32,16 +32,16 @@ export namespace BT { * 参数描述接口 */ export interface ParameterInfo { - /** 参数类型 */ - type: ParamType; /** 参数名称 */ name: string; + /** 参数类型 */ + type: ParamType; /** 参数描述 */ description?: string; /** 默认值 */ defaultValue?: any; - /** 是否必需 */ - required?: boolean; + /** 步进 针对数字类型的变更的最小单位 */ + step?: number, } /** @@ -50,6 +50,8 @@ export namespace BT { export interface NodeMetadata { /** 节点名称 */ name: string; + /** 节点类名 */ + className: string; /** 节点分组 */ group: string; /** 节点类型 */ @@ -65,19 +67,50 @@ export namespace BT { /** * 节点元数据存储 */ - const NODE_METADATA_MAP = new Map(); + const NODE_METADATA_MAP = new Map(); + + /** + * 节点参数存储 + */ + const NODE_PARAMETERS_MAP = new Map(); + + /** + * 节点属性装饰器 + */ + export function prop(paramInfo: Omit) { + return function (target: any, propertyKey: string) { + const ctor = target.constructor; + if (!NODE_PARAMETERS_MAP.has(ctor)) { + NODE_PARAMETERS_MAP.set(ctor, []); + } + const parameters = NODE_PARAMETERS_MAP.get(ctor)!; + parameters.push({ + ...paramInfo, + name: propertyKey + }); + }; + } /** * 行为节点装饰器 + * @param name 节点的类名 编辑器导出数据中的节点名字 + * @param info.group 节点在编辑器中的分组 + * @param info.name 节点在编辑器中的中文名 + * @param info.desc 节点描述信息 */ - export function ActionNode(metadata: Omit) { + export function ActionNode(name: string, info?: { group?: string, name?: string, desc?: string }) { return function any>(constructor: T) { + const parameters = NODE_PARAMETERS_MAP.get(constructor) || []; const fullMetadata: NodeMetadata = { - ...metadata, + className: name, + group: info?.group || '未分组', + name: info?.name || '', + description: info?.desc || '', type: Type.Action, maxChildren: 0, + parameters }; - NODE_METADATA_MAP.set(constructor.name, fullMetadata); + NODE_METADATA_MAP.set(constructor, fullMetadata); return constructor; }; } @@ -85,14 +118,19 @@ export namespace BT { /** * 条件节点装饰器 */ - export function ConditionNode(metadata: Omit) { + export function ConditionNode(name: string, info?: { group?: string, name?: string, desc?: string }) { return function any>(constructor: T) { + const parameters = NODE_PARAMETERS_MAP.get(constructor) || []; const fullMetadata: NodeMetadata = { - ...metadata, + className: name, + group: info?.group || '未分组', + name: info?.name || '', + description: info?.desc || '', type: Type.Condition, maxChildren: 0, + parameters }; - NODE_METADATA_MAP.set(constructor.name, fullMetadata); + NODE_METADATA_MAP.set(constructor, fullMetadata); return constructor; }; } @@ -100,14 +138,19 @@ export namespace BT { /** * 组合节点装饰器 */ - export function CompositeNode(metadata: Omit) { + export function CompositeNode(name: string, info?: { group?: string, name?: string, desc?: string }) { return function any>(constructor: T) { + const parameters = NODE_PARAMETERS_MAP.get(constructor) || []; const fullMetadata: NodeMetadata = { - ...metadata, + className: name, + group: info?.group || '未分组', + name: info?.name || '', + description: info?.desc || '', type: Type.Composite, - maxChildren: -1 + maxChildren: -1, + parameters }; - NODE_METADATA_MAP.set(constructor.name, fullMetadata); + NODE_METADATA_MAP.set(constructor, fullMetadata); return constructor; }; } @@ -115,45 +158,29 @@ export namespace BT { /** * 装饰节点装饰器 */ - export function DecoratorNode(metadata: Omit) { + export function DecoratorNode(name: string, info?: { group?: string, name?: string, desc?: string }) { return function any>(constructor: T) { + const parameters = NODE_PARAMETERS_MAP.get(constructor) || []; const fullMetadata: NodeMetadata = { - ...metadata, + className: name, + group: info?.group || '未分组', + name: info?.name || '', + description: info?.desc || '', type: Type.Decorator, - maxChildren: 1 + maxChildren: 1, + parameters }; - NODE_METADATA_MAP.set(constructor.name, fullMetadata); + NODE_METADATA_MAP.set(constructor, fullMetadata); return constructor; }; } - /** - * 获取节点元数据 - */ - export function getNodeMetadata(nodeName: string): NodeMetadata | undefined { - return NODE_METADATA_MAP.get(nodeName); - } - /** * 获取所有节点元数据 */ - export function getAllNodeMetadata(): Map { + export function getAllNodeMetadata(): Map { return new Map(NODE_METADATA_MAP); } - - /** - * 判断节点是否允许子节点 - */ - export function canHaveChildren(metadata: NodeMetadata): boolean { - return metadata.maxChildren !== 0; - } - - /** - * 判断节点是否可以添加更多子节点 - */ - export function canAddMoreChildren(metadata: NodeMetadata, currentChildCount: number): boolean { - return metadata.maxChildren === -1 || currentChildCount < metadata.maxChildren; - } } let _global = globalThis || window || global; diff --git a/src/behaviortree/BTNode/AbstractNodes.ts b/src/behaviortree/BTNode/AbstractNodes.ts index 66eea5f..de87719 100644 --- a/src/behaviortree/BTNode/AbstractNodes.ts +++ b/src/behaviortree/BTNode/AbstractNodes.ts @@ -42,14 +42,7 @@ export abstract class Composite extends BTNode { * 包含最大值和当前值的通用逻辑,适用于所有需要数值计数的修饰节点 */ export abstract class NumericDecorator extends Decorator { - protected readonly _max: number; protected _value: number = 0; - - constructor(child: IBTNode, max: number = 1) { - super(child); - this._max = max; - } - protected override open(): void { super.open(); this._value = 0; diff --git a/src/behaviortree/BTNode/Action.ts b/src/behaviortree/BTNode/Action.ts index 045a26e..1ef297c 100644 --- a/src/behaviortree/BTNode/Action.ts +++ b/src/behaviortree/BTNode/Action.ts @@ -7,15 +7,13 @@ import { LeafNode } from "./AbstractNodes"; * 次数内,返回RUNNING * 超次,返回SUCCESS */ -@BT.ActionNode({ +@BT.ActionNode("WaitTicks", { name: "等待次数", group: "等待行为", - description: "等待指定次数后返回成功", - parameters: [ - { name: "maxTicks", type: BT.ParamType.int, description: "最大等待次数", defaultValue: 0, required: true } - ] + desc: "等待指定次数后返回成功", }) export class WaitTicks extends LeafNode { + @BT.prop({ type: BT.ParamType.int, description: "最大等待次数", defaultValue: 0, step: 1 }) private _max: number; private _value: number; @@ -42,15 +40,13 @@ export class WaitTicks extends LeafNode { * 时间等待节点 时间(秒) * 时间到后返回SUCCESS,否则返回RUNNING */ -@BT.ActionNode({ +@BT.ActionNode("WaitTime", { name: "等待时间", group: "等待行为", - description: "等待指定时间(秒)后返回成功", - parameters: [ - { name: "duration", type: BT.ParamType.float, description: "等待时间(秒)", defaultValue: 0, required: true } - ] + desc: "等待指定时间(秒)后返回成功", }) export class WaitTime extends LeafNode { + @BT.prop({ type: BT.ParamType.float, description: "等待时间(秒)", defaultValue: 0, step: 0.01 }) private _max: number; private _value: number = 0; constructor(duration: number = 0) { diff --git a/src/behaviortree/BTNode/Composite.ts b/src/behaviortree/BTNode/Composite.ts index 0ddb8c9..6e58370 100644 --- a/src/behaviortree/BTNode/Composite.ts +++ b/src/behaviortree/BTNode/Composite.ts @@ -11,11 +11,10 @@ import { WeightDecorator } from "./Decorator"; * * 遇到 RUNNING 返回 RUNNING 下次从该节点开始 */ -@BT.CompositeNode({ +@BT.CompositeNode("MemSelector", { name: "记忆选择节点", group: "基础组合节点", - description: "记住上次运行位置的选择节点,从记忆位置开始执行", - parameters: [] + desc: "记住上次运行位置的选择节点,从记忆位置开始执行", }) export class MemSelector extends MemoryComposite { public tick(): Status { @@ -42,11 +41,10 @@ export class MemSelector extends MemoryComposite { * * 遇到 RUNNING 返回 RUNNING 下次从该节点开始 */ -@BT.CompositeNode({ +@BT.CompositeNode("MemSequence", { name: "记忆顺序节点", group: "基础组合节点", - description: "记住上次运行位置的序列节点,从记忆位置开始执行", - parameters: [] + desc: "记住上次运行位置的序列节点,从记忆位置开始执行", }) export class MemSequence extends MemoryComposite { public tick(): Status { @@ -71,11 +69,10 @@ export class MemSequence extends MemoryComposite { * 返回第一个不为 FAILURE 的子节点状态 * 否则返回 FAILURE */ -@BT.CompositeNode({ +@BT.CompositeNode("Selector", { name: "选择节点", group: "基础组合节点", - description: "依次执行子节点,直到找到成功或运行中的节点", - parameters: [] + desc: "依次执行子节点,直到找到成功或运行中的节点", }) export class Selector extends Composite { public tick(): Status { @@ -95,11 +92,10 @@ export class Selector extends Composite { * 随机选择一个子节点执行 * 返回子节点状态 */ -@BT.CompositeNode({ +@BT.CompositeNode("RandomSelector", { name: "随机选择节点", group: "基础组合节点", - description: "随机选择一个子节点执行", - parameters: [], + desc: "随机选择一个子节点执行", }) export class RandomSelector extends Composite { private _totalWeight: number = 0; @@ -153,11 +149,10 @@ export class RandomSelector extends Composite { * 遇到 SUCCESS 继续下一个 * 否则返回子节点状态 */ -@BT.CompositeNode({ +@BT.CompositeNode("Sequence", { name: "顺序节点", group: "基础组合节点", - description: "依次执行所有子节点,全部成功才返回成功", - parameters: [] + desc: "依次执行所有子节点,全部成功才返回成功", }) export class Sequence extends Composite { public tick(): Status { @@ -176,11 +171,10 @@ export class Sequence extends Composite { * 并行节点 从上到下执行 全部执行一遍 * 返回优先级 FAILURE > RUNNING > SUCCESS */ -@BT.CompositeNode({ +@BT.CompositeNode("Parallel", { name: "并行节点", group: "基础组合节点", - description: "同时执行所有子节点,全部成功才返回成功", - parameters: [] + desc: "同时执行所有子节点,全部成功才返回成功", }) export class Parallel extends Composite { public tick(): Status { @@ -201,11 +195,10 @@ export class Parallel extends Composite { * 并行节点 从上到下执行 全部执行一遍 * 返回优先级 SUCCESS > RUNNING > FAILURE */ -@BT.CompositeNode({ +@BT.CompositeNode("ParallelAnySuccess", { name: "并行任意成功", group: "基础组合节点", - description: "同时执行所有子节点,任意一个成功即返回成功", - parameters: [] + desc: "同时执行所有子节点,任意一个成功即返回成功", }) export class ParallelAnySuccess extends Composite { public tick(): Status { diff --git a/src/behaviortree/BTNode/Decorator.ts b/src/behaviortree/BTNode/Decorator.ts index a0a1ac4..6ebbbc1 100644 --- a/src/behaviortree/BTNode/Decorator.ts +++ b/src/behaviortree/BTNode/Decorator.ts @@ -15,11 +15,10 @@ import { IBTNode } from "./BTNode"; * 第一个Child Node节点, 返回 FAILURE, 本Node向自己的Parent Node也返回 SUCCESS * 第一个Child Node节点, 返回 SUCCESS, 本Node向自己的Parent Node也返回 FAILURE */ -@BT.DecoratorNode({ +@BT.DecoratorNode("Inverter", { name: "反转器", group: "基础装饰节点", - description: "反转子节点的执行结果,成功变失败,失败变成功", - parameters: [] + desc: "反转子节点的执行结果,成功变失败,失败变成功", }) export class Inverter extends Decorator { public tick(): Status { @@ -41,22 +40,23 @@ export class Inverter extends Decorator { * 规定时间内, 根据Child Node的结果, 本节点向自己的父节点也返回相同的结果 * 超时后, 直接返回 FAILURE */ -@BT.DecoratorNode({ +@BT.DecoratorNode("LimitTime", { name: "时间限制器", group: "基础装饰节点", - description: "限制子节点执行时间,超时返回失败", - parameters: [ - { name: "max", type: BT.ParamType.float, description: "最大时间(秒)", defaultValue: 1, required: true } - ] + desc: "限制子节点执行时间,超时返回失败", }) export class LimitTime extends NumericDecorator { + @BT.prop({ type: BT.ParamType.float, description: "最大时间(秒)", defaultValue: 1 }) + protected _max: number = 1; + /** * 时间限制节点 * @param child 子节点 * @param max 最大时间 (秒) 默认1秒 */ constructor(child: IBTNode, max: number = 1) { - super(child, max * 1000); + super(child); + this._max = max * 1000; } protected override open(): void { @@ -77,15 +77,19 @@ export class LimitTime extends NumericDecorator { * 必须且只能包含一个子节点 * 次数超过后, 直接返回失败; 次数未超过, 返回子节点状态 */ -@BT.DecoratorNode({ +@BT.DecoratorNode("LimitTicks", { name: "次数限制器", group: "基础装饰节点", - description: "限制子节点执行次数,超过次数返回失败", - parameters: [ - { name: "max", type: BT.ParamType.int, description: "最大次数", defaultValue: 1, required: true } - ] + desc: "限制子节点执行次数,超过次数返回失败", }) export class LimitTicks extends NumericDecorator { + @BT.prop({ type: BT.ParamType.int, description: "最大次数", defaultValue: 1 }) + protected _max: number = 1; + constructor(child: IBTNode, max: number = 1) { + super(child); + this._max = max; + } + public tick(): Status { this._value++; if (this._value > this._max) { @@ -101,15 +105,18 @@ export class LimitTicks extends NumericDecorator { * 子节点是成功或失败,累加计数 * 次数超过之后返回子节点状态,否则返回 RUNNING */ -@BT.DecoratorNode({ - name: "重复器", +@BT.DecoratorNode("Repeat", { + name: "重复节点", group: "基础装饰节点", - description: "重复执行子节点指定次数", - parameters: [ - { name: "max", type: BT.ParamType.int, description: "重复次数", defaultValue: 1, required: true } - ] + desc: "重复执行子节点指定次数", }) export class Repeat extends NumericDecorator { + @BT.prop({ type: BT.ParamType.int, description: "重复次数", defaultValue: 1 }) + protected _max: number = 1; + constructor(child: IBTNode, max: number = 1) { + super(child); + this._max = max; + } public tick(): Status { // 执行子节点 const status = this.children[0]!._execute(); @@ -132,15 +139,18 @@ export class Repeat extends NumericDecorator { * * 子节点成功 计数+1 */ -@BT.DecoratorNode({ +@BT.DecoratorNode("RepeatUntilFailure", { name: "重复直到失败", group: "基础装饰节点", - description: "重复执行子节点直到失败或达到最大次数", - parameters: [ - { name: "max", type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, required: true } - ] + desc: "重复执行子节点直到失败或达到最大次数", }) export class RepeatUntilFailure extends NumericDecorator { + @BT.prop({ type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1 }) + protected _max: number = 1; + constructor(child: IBTNode, max: number = 1) { + super(child); + this._max = max; + } public tick(): Status { const status = this.children[0]!._execute(); if (status === Status.FAILURE) { @@ -164,15 +174,18 @@ export class RepeatUntilFailure extends NumericDecorator { * * 子节点失败, 计数+1 */ -@BT.DecoratorNode({ +@BT.DecoratorNode("RepeatUntilSuccess", { name: "重复直到成功", group: "基础装饰节点", - description: "重复执行子节点直到成功或达到最大次数", - parameters: [ - { name: "max", type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, required: true } - ] + desc: "重复执行子节点直到成功或达到最大次数", }) export class RepeatUntilSuccess extends NumericDecorator { + @BT.prop({ type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, step: 1 }) + protected _max: number = 1; + constructor(child: IBTNode, max: number = 1) { + super(child); + this._max = max; + } public tick(): Status { // 执行子节点 const status = this.children[0]!._execute(); @@ -193,15 +206,13 @@ export class RepeatUntilSuccess extends NumericDecorator { /** * 权重装饰节点 */ -@BT.DecoratorNode({ +@BT.DecoratorNode("WeightDecorator", { name: "权重装饰器", group: "基础装饰节点", - description: "权重装饰节点", - parameters: [ - { name: "weight", type: BT.ParamType.float, description: "权重", defaultValue: 1, required: true } - ] + desc: "权重装饰节点", }) export class WeightDecorator extends Decorator { + @BT.prop({ type: BT.ParamType.int, description: "权重", defaultValue: 1, step: 1 }) private _weight: number; constructor(child: IBTNode, weight?: number) {