修改节点装饰器

# Conflicts:
#	package.json
This commit is contained in:
gongxh
2025-09-09 09:45:51 +08:00
parent 3071611cd0
commit 1c5f9de608
6 changed files with 135 additions and 115 deletions

View File

@@ -19,7 +19,7 @@
}, },
"scripts": { "scripts": {
"clean": "rm -rf dist", "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" "build": "npm run clean && rollup -c rollup.config.mjs && npm run copy"
}, },
"files": [ "files": [

View File

@@ -32,16 +32,16 @@ export namespace BT {
* 参数描述接口 * 参数描述接口
*/ */
export interface ParameterInfo { export interface ParameterInfo {
/** 参数类型 */
type: ParamType;
/** 参数名称 */ /** 参数名称 */
name: string; name: string;
/** 参数类型 */
type: ParamType;
/** 参数描述 */ /** 参数描述 */
description?: string; description?: string;
/** 默认值 */ /** 默认值 */
defaultValue?: any; defaultValue?: any;
/** 是否必需 */ /** 步进 针对数字类型的变更的最小单位 */
required?: boolean; step?: number,
} }
/** /**
@@ -50,6 +50,8 @@ export namespace BT {
export interface NodeMetadata { export interface NodeMetadata {
/** 节点名称 */ /** 节点名称 */
name: string; name: string;
/** 节点类名 */
className: string;
/** 节点分组 */ /** 节点分组 */
group: string; group: string;
/** 节点类型 */ /** 节点类型 */
@@ -65,19 +67,50 @@ export namespace BT {
/** /**
* 节点元数据存储 * 节点元数据存储
*/ */
const NODE_METADATA_MAP = new Map<string, NodeMetadata>(); const NODE_METADATA_MAP = new Map<any, NodeMetadata>();
/**
* 节点参数存储
*/
const NODE_PARAMETERS_MAP = new Map<any, ParameterInfo[]>();
/**
* 节点属性装饰器
*/
export function prop(paramInfo: Omit<ParameterInfo, "name">) {
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<NodeMetadata, "type" | "maxChildren">) { export function ActionNode(name: string, info?: { group?: string, name?: string, desc?: string }) {
return function <T extends new (...args: any[]) => any>(constructor: T) { return function <T extends new (...args: any[]) => any>(constructor: T) {
const parameters = NODE_PARAMETERS_MAP.get(constructor) || [];
const fullMetadata: NodeMetadata = { const fullMetadata: NodeMetadata = {
...metadata, className: name,
group: info?.group || '未分组',
name: info?.name || '',
description: info?.desc || '',
type: Type.Action, type: Type.Action,
maxChildren: 0, maxChildren: 0,
parameters
}; };
NODE_METADATA_MAP.set(constructor.name, fullMetadata); NODE_METADATA_MAP.set(constructor, fullMetadata);
return constructor; return constructor;
}; };
} }
@@ -85,14 +118,19 @@ export namespace BT {
/** /**
* 条件节点装饰器 * 条件节点装饰器
*/ */
export function ConditionNode(metadata: Omit<NodeMetadata, "type" | "maxChildren">) { export function ConditionNode(name: string, info?: { group?: string, name?: string, desc?: string }) {
return function <T extends new (...args: any[]) => any>(constructor: T) { return function <T extends new (...args: any[]) => any>(constructor: T) {
const parameters = NODE_PARAMETERS_MAP.get(constructor) || [];
const fullMetadata: NodeMetadata = { const fullMetadata: NodeMetadata = {
...metadata, className: name,
group: info?.group || '未分组',
name: info?.name || '',
description: info?.desc || '',
type: Type.Condition, type: Type.Condition,
maxChildren: 0, maxChildren: 0,
parameters
}; };
NODE_METADATA_MAP.set(constructor.name, fullMetadata); NODE_METADATA_MAP.set(constructor, fullMetadata);
return constructor; return constructor;
}; };
} }
@@ -100,14 +138,19 @@ export namespace BT {
/** /**
* 组合节点装饰器 * 组合节点装饰器
*/ */
export function CompositeNode(metadata: Omit<NodeMetadata, "type" | "maxChildren">) { export function CompositeNode(name: string, info?: { group?: string, name?: string, desc?: string }) {
return function <T extends new (...args: any[]) => any>(constructor: T) { return function <T extends new (...args: any[]) => any>(constructor: T) {
const parameters = NODE_PARAMETERS_MAP.get(constructor) || [];
const fullMetadata: NodeMetadata = { const fullMetadata: NodeMetadata = {
...metadata, className: name,
group: info?.group || '未分组',
name: info?.name || '',
description: info?.desc || '',
type: Type.Composite, type: Type.Composite,
maxChildren: -1 maxChildren: -1,
parameters
}; };
NODE_METADATA_MAP.set(constructor.name, fullMetadata); NODE_METADATA_MAP.set(constructor, fullMetadata);
return constructor; return constructor;
}; };
} }
@@ -115,45 +158,29 @@ export namespace BT {
/** /**
* 装饰节点装饰器 * 装饰节点装饰器
*/ */
export function DecoratorNode(metadata: Omit<NodeMetadata, "type" | "maxChildren">) { export function DecoratorNode(name: string, info?: { group?: string, name?: string, desc?: string }) {
return function <T extends new (...args: any[]) => any>(constructor: T) { return function <T extends new (...args: any[]) => any>(constructor: T) {
const parameters = NODE_PARAMETERS_MAP.get(constructor) || [];
const fullMetadata: NodeMetadata = { const fullMetadata: NodeMetadata = {
...metadata, className: name,
group: info?.group || '未分组',
name: info?.name || '',
description: info?.desc || '',
type: Type.Decorator, type: Type.Decorator,
maxChildren: 1 maxChildren: 1,
parameters
}; };
NODE_METADATA_MAP.set(constructor.name, fullMetadata); NODE_METADATA_MAP.set(constructor, fullMetadata);
return constructor; return constructor;
}; };
} }
/**
* 获取节点元数据
*/
export function getNodeMetadata(nodeName: string): NodeMetadata | undefined {
return NODE_METADATA_MAP.get(nodeName);
}
/** /**
* 获取所有节点元数据 * 获取所有节点元数据
*/ */
export function getAllNodeMetadata(): Map<string, NodeMetadata> { export function getAllNodeMetadata(): Map<any, NodeMetadata> {
return new Map(NODE_METADATA_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; let _global = globalThis || window || global;

View File

@@ -42,14 +42,7 @@ export abstract class Composite extends BTNode {
* 包含最大值和当前值的通用逻辑,适用于所有需要数值计数的修饰节点 * 包含最大值和当前值的通用逻辑,适用于所有需要数值计数的修饰节点
*/ */
export abstract class NumericDecorator extends Decorator { export abstract class NumericDecorator extends Decorator {
protected readonly _max: number;
protected _value: number = 0; protected _value: number = 0;
constructor(child: IBTNode, max: number = 1) {
super(child);
this._max = max;
}
protected override open(): void { protected override open(): void {
super.open(); super.open();
this._value = 0; this._value = 0;

View File

@@ -7,15 +7,13 @@ import { LeafNode } from "./AbstractNodes";
* 次数内返回RUNNING * 次数内返回RUNNING
* 超次返回SUCCESS * 超次返回SUCCESS
*/ */
@BT.ActionNode({ @BT.ActionNode("WaitTicks", {
name: "等待次数", name: "等待次数",
group: "等待行为", group: "等待行为",
description: "等待指定次数后返回成功", desc: "等待指定次数后返回成功",
parameters: [
{ name: "maxTicks", type: BT.ParamType.int, description: "最大等待次数", defaultValue: 0, required: true }
]
}) })
export class WaitTicks extends LeafNode { export class WaitTicks extends LeafNode {
@BT.prop({ type: BT.ParamType.int, description: "最大等待次数", defaultValue: 0, step: 1 })
private _max: number; private _max: number;
private _value: number; private _value: number;
@@ -42,15 +40,13 @@ export class WaitTicks extends LeafNode {
* 时间等待节点 时间(秒) * 时间等待节点 时间(秒)
* 时间到后返回SUCCESS否则返回RUNNING * 时间到后返回SUCCESS否则返回RUNNING
*/ */
@BT.ActionNode({ @BT.ActionNode("WaitTime", {
name: "等待时间", name: "等待时间",
group: "等待行为", group: "等待行为",
description: "等待指定时间(秒)后返回成功", desc: "等待指定时间(秒)后返回成功",
parameters: [
{ name: "duration", type: BT.ParamType.float, description: "等待时间(秒)", defaultValue: 0, required: true }
]
}) })
export class WaitTime extends LeafNode { export class WaitTime extends LeafNode {
@BT.prop({ type: BT.ParamType.float, description: "等待时间(秒)", defaultValue: 0, step: 0.01 })
private _max: number; private _max: number;
private _value: number = 0; private _value: number = 0;
constructor(duration: number = 0) { constructor(duration: number = 0) {

View File

@@ -11,11 +11,10 @@ import { WeightDecorator } from "./Decorator";
* *
* 遇到 RUNNING 返回 RUNNING 下次从该节点开始 * 遇到 RUNNING 返回 RUNNING 下次从该节点开始
*/ */
@BT.CompositeNode({ @BT.CompositeNode("MemSelector", {
name: "记忆选择节点", name: "记忆选择节点",
group: "基础组合节点", group: "基础组合节点",
description: "记住上次运行位置的选择节点,从记忆位置开始执行", desc: "记住上次运行位置的选择节点,从记忆位置开始执行",
parameters: []
}) })
export class MemSelector extends MemoryComposite { export class MemSelector extends MemoryComposite {
public tick(): Status { public tick(): Status {
@@ -42,11 +41,10 @@ export class MemSelector extends MemoryComposite {
* *
* 遇到 RUNNING 返回 RUNNING 下次从该节点开始 * 遇到 RUNNING 返回 RUNNING 下次从该节点开始
*/ */
@BT.CompositeNode({ @BT.CompositeNode("MemSequence", {
name: "记忆顺序节点", name: "记忆顺序节点",
group: "基础组合节点", group: "基础组合节点",
description: "记住上次运行位置的序列节点,从记忆位置开始执行", desc: "记住上次运行位置的序列节点,从记忆位置开始执行",
parameters: []
}) })
export class MemSequence extends MemoryComposite { export class MemSequence extends MemoryComposite {
public tick(): Status { public tick(): Status {
@@ -71,11 +69,10 @@ export class MemSequence extends MemoryComposite {
* 返回第一个不为 FAILURE 的子节点状态 * 返回第一个不为 FAILURE 的子节点状态
* 否则返回 FAILURE * 否则返回 FAILURE
*/ */
@BT.CompositeNode({ @BT.CompositeNode("Selector", {
name: "选择节点", name: "选择节点",
group: "基础组合节点", group: "基础组合节点",
description: "依次执行子节点,直到找到成功或运行中的节点", desc: "依次执行子节点,直到找到成功或运行中的节点",
parameters: []
}) })
export class Selector extends Composite { export class Selector extends Composite {
public tick(): Status { public tick(): Status {
@@ -95,11 +92,10 @@ export class Selector extends Composite {
* 随机选择一个子节点执行 * 随机选择一个子节点执行
* 返回子节点状态 * 返回子节点状态
*/ */
@BT.CompositeNode({ @BT.CompositeNode("RandomSelector", {
name: "随机选择节点", name: "随机选择节点",
group: "基础组合节点", group: "基础组合节点",
description: "随机选择一个子节点执行", desc: "随机选择一个子节点执行",
parameters: [],
}) })
export class RandomSelector extends Composite { export class RandomSelector extends Composite {
private _totalWeight: number = 0; private _totalWeight: number = 0;
@@ -153,11 +149,10 @@ export class RandomSelector extends Composite {
* 遇到 SUCCESS 继续下一个 * 遇到 SUCCESS 继续下一个
* 否则返回子节点状态 * 否则返回子节点状态
*/ */
@BT.CompositeNode({ @BT.CompositeNode("Sequence", {
name: "顺序节点", name: "顺序节点",
group: "基础组合节点", group: "基础组合节点",
description: "依次执行所有子节点,全部成功才返回成功", desc: "依次执行所有子节点,全部成功才返回成功",
parameters: []
}) })
export class Sequence extends Composite { export class Sequence extends Composite {
public tick(): Status { public tick(): Status {
@@ -176,11 +171,10 @@ export class Sequence extends Composite {
* 并行节点 从上到下执行 全部执行一遍 * 并行节点 从上到下执行 全部执行一遍
* 返回优先级 FAILURE > RUNNING > SUCCESS * 返回优先级 FAILURE > RUNNING > SUCCESS
*/ */
@BT.CompositeNode({ @BT.CompositeNode("Parallel", {
name: "并行节点", name: "并行节点",
group: "基础组合节点", group: "基础组合节点",
description: "同时执行所有子节点,全部成功才返回成功", desc: "同时执行所有子节点,全部成功才返回成功",
parameters: []
}) })
export class Parallel extends Composite { export class Parallel extends Composite {
public tick(): Status { public tick(): Status {
@@ -201,11 +195,10 @@ export class Parallel extends Composite {
* 并行节点 从上到下执行 全部执行一遍 * 并行节点 从上到下执行 全部执行一遍
* 返回优先级 SUCCESS > RUNNING > FAILURE * 返回优先级 SUCCESS > RUNNING > FAILURE
*/ */
@BT.CompositeNode({ @BT.CompositeNode("ParallelAnySuccess", {
name: "并行任意成功", name: "并行任意成功",
group: "基础组合节点", group: "基础组合节点",
description: "同时执行所有子节点,任意一个成功即返回成功", desc: "同时执行所有子节点,任意一个成功即返回成功",
parameters: []
}) })
export class ParallelAnySuccess extends Composite { export class ParallelAnySuccess extends Composite {
public tick(): Status { public tick(): Status {

View File

@@ -15,11 +15,10 @@ import { IBTNode } from "./BTNode";
* 第一个Child Node节点, 返回 FAILURE, 本Node向自己的Parent Node也返回 SUCCESS * 第一个Child Node节点, 返回 FAILURE, 本Node向自己的Parent Node也返回 SUCCESS
* 第一个Child Node节点, 返回 SUCCESS, 本Node向自己的Parent Node也返回 FAILURE * 第一个Child Node节点, 返回 SUCCESS, 本Node向自己的Parent Node也返回 FAILURE
*/ */
@BT.DecoratorNode({ @BT.DecoratorNode("Inverter", {
name: "反转器", name: "反转器",
group: "基础装饰节点", group: "基础装饰节点",
description: "反转子节点的执行结果,成功变失败,失败变成功", desc: "反转子节点的执行结果,成功变失败,失败变成功",
parameters: []
}) })
export class Inverter extends Decorator { export class Inverter extends Decorator {
public tick(): Status { public tick(): Status {
@@ -41,22 +40,23 @@ export class Inverter extends Decorator {
* 规定时间内, 根据Child Node的结果, 本节点向自己的父节点也返回相同的结果 * 规定时间内, 根据Child Node的结果, 本节点向自己的父节点也返回相同的结果
* 超时后, 直接返回 FAILURE * 超时后, 直接返回 FAILURE
*/ */
@BT.DecoratorNode({ @BT.DecoratorNode("LimitTime", {
name: "时间限制器", name: "时间限制器",
group: "基础装饰节点", group: "基础装饰节点",
description: "限制子节点执行时间,超时返回失败", desc: "限制子节点执行时间,超时返回失败",
parameters: [
{ name: "max", type: BT.ParamType.float, description: "最大时间(秒)", defaultValue: 1, required: true }
]
}) })
export class LimitTime extends NumericDecorator { export class LimitTime extends NumericDecorator {
@BT.prop({ type: BT.ParamType.float, description: "最大时间(秒)", defaultValue: 1 })
protected _max: number = 1;
/** /**
* 时间限制节点 * 时间限制节点
* @param child 子节点 * @param child 子节点
* @param max 最大时间 (秒) 默认1秒 * @param max 最大时间 (秒) 默认1秒
*/ */
constructor(child: IBTNode, max: number = 1) { constructor(child: IBTNode, max: number = 1) {
super(child, max * 1000); super(child);
this._max = max * 1000;
} }
protected override open(): void { protected override open(): void {
@@ -77,15 +77,19 @@ export class LimitTime extends NumericDecorator {
* 必须且只能包含一个子节点 * 必须且只能包含一个子节点
* 次数超过后, 直接返回失败; 次数未超过, 返回子节点状态 * 次数超过后, 直接返回失败; 次数未超过, 返回子节点状态
*/ */
@BT.DecoratorNode({ @BT.DecoratorNode("LimitTicks", {
name: "次数限制器", name: "次数限制器",
group: "基础装饰节点", group: "基础装饰节点",
description: "限制子节点执行次数,超过次数返回失败", desc: "限制子节点执行次数,超过次数返回失败",
parameters: [
{ name: "max", type: BT.ParamType.int, description: "最大次数", defaultValue: 1, required: true }
]
}) })
export class LimitTicks extends NumericDecorator { 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 { public tick(): Status {
this._value++; this._value++;
if (this._value > this._max) { if (this._value > this._max) {
@@ -101,15 +105,18 @@ export class LimitTicks extends NumericDecorator {
* 子节点是成功或失败,累加计数 * 子节点是成功或失败,累加计数
* 次数超过之后返回子节点状态,否则返回 RUNNING * 次数超过之后返回子节点状态,否则返回 RUNNING
*/ */
@BT.DecoratorNode({ @BT.DecoratorNode("Repeat", {
name: "重复", name: "重复节点",
group: "基础装饰节点", group: "基础装饰节点",
description: "重复执行子节点指定次数", desc: "重复执行子节点指定次数",
parameters: [
{ name: "max", type: BT.ParamType.int, description: "重复次数", defaultValue: 1, required: true }
]
}) })
export class Repeat extends NumericDecorator { 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 { public tick(): Status {
// 执行子节点 // 执行子节点
const status = this.children[0]!._execute(); const status = this.children[0]!._execute();
@@ -132,15 +139,18 @@ export class Repeat extends NumericDecorator {
* *
* 子节点成功 计数+1 * 子节点成功 计数+1
*/ */
@BT.DecoratorNode({ @BT.DecoratorNode("RepeatUntilFailure", {
name: "重复直到失败", name: "重复直到失败",
group: "基础装饰节点", group: "基础装饰节点",
description: "重复执行子节点直到失败或达到最大次数", desc: "重复执行子节点直到失败或达到最大次数",
parameters: [
{ name: "max", type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, required: true }
]
}) })
export class RepeatUntilFailure extends NumericDecorator { 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 { public tick(): Status {
const status = this.children[0]!._execute(); const status = this.children[0]!._execute();
if (status === Status.FAILURE) { if (status === Status.FAILURE) {
@@ -164,15 +174,18 @@ export class RepeatUntilFailure extends NumericDecorator {
* *
* 子节点失败, 计数+1 * 子节点失败, 计数+1
*/ */
@BT.DecoratorNode({ @BT.DecoratorNode("RepeatUntilSuccess", {
name: "重复直到成功", name: "重复直到成功",
group: "基础装饰节点", group: "基础装饰节点",
description: "重复执行子节点直到成功或达到最大次数", desc: "重复执行子节点直到成功或达到最大次数",
parameters: [
{ name: "max", type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, required: true }
]
}) })
export class RepeatUntilSuccess extends NumericDecorator { 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 { public tick(): Status {
// 执行子节点 // 执行子节点
const status = this.children[0]!._execute(); const status = this.children[0]!._execute();
@@ -193,15 +206,13 @@ export class RepeatUntilSuccess extends NumericDecorator {
/** /**
* 权重装饰节点 * 权重装饰节点
*/ */
@BT.DecoratorNode({ @BT.DecoratorNode("WeightDecorator", {
name: "权重装饰器", name: "权重装饰器",
group: "基础装饰节点", group: "基础装饰节点",
description: "权重装饰节点", desc: "权重装饰节点",
parameters: [
{ name: "weight", type: BT.ParamType.float, description: "权重", defaultValue: 1, required: true }
]
}) })
export class WeightDecorator extends Decorator { export class WeightDecorator extends Decorator {
@BT.prop({ type: BT.ParamType.int, description: "权重", defaultValue: 1, step: 1 })
private _weight: number; private _weight: number;
constructor(child: IBTNode, weight?: number) { constructor(child: IBTNode, weight?: number) {