From 6d6162031a6473b7b9c8f5aebaa7c7d66987eb05 Mon Sep 17 00:00:00 2001 From: gongxh Date: Mon, 8 Sep 2025 16:10:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A3=85=E9=A5=B0=E5=99=A8?= =?UTF-8?q?=E7=BB=99=E8=93=9D=E5=9B=BE=E7=BC=96=E8=BE=91=E5=99=A8=E5=81=9A?= =?UTF-8?q?=E5=87=86=E5=A4=87;=E4=BF=AE=E6=94=B9=E9=9A=8F=E6=9C=BA?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=B7=BB=E5=8A=A0=E6=9D=83=E9=87=8D=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/behaviortree/BT.ts | 157 +++++++++++++++++++++++++++ src/behaviortree/BTNode/Action.ts | 30 ++--- src/behaviortree/BTNode/Composite.ts | 115 +++++++++++++++++--- src/behaviortree/BTNode/Condition.ts | 20 ---- src/behaviortree/BTNode/Decorator.ts | 76 +++++++++++++ src/index.ts | 1 - tsconfig.json | 2 +- 7 files changed, 349 insertions(+), 52 deletions(-) create mode 100644 src/behaviortree/BT.ts delete mode 100644 src/behaviortree/BTNode/Condition.ts diff --git a/src/behaviortree/BT.ts b/src/behaviortree/BT.ts new file mode 100644 index 0000000..019a6a6 --- /dev/null +++ b/src/behaviortree/BT.ts @@ -0,0 +1,157 @@ +/** + * 行为树装饰器和元数据管理 + * 用于编辑器显示和配置节点信息 + */ + +export namespace BT { + /** + * 参数类型枚举 + */ + export enum ParamType { + int = "number", + float = "float", + string = "string", + bool = "boolean" + } + + /** + * 节点类型枚举 + */ + export enum Type { + /** 行为节点 */ + Action = "action", + /** 条件节点 */ + Condition = "condition", + /** 组合节点 */ + Composite = "composite", + /** 装饰节点 */ + Decorator = "decorator" + } + + /** + * 参数描述接口 + */ + export interface ParameterInfo { + /** 参数类型 */ + type: ParamType; + /** 参数名称 */ + name: string; + /** 参数描述 */ + description?: string; + /** 默认值 */ + defaultValue?: any; + /** 是否必需 */ + required?: boolean; + } + + /** + * 节点元数据接口 + */ + export interface NodeMetadata { + /** 节点名称 */ + name: string; + /** 节点分组 */ + group: string; + /** 节点类型 */ + type: Type; + /** 节点描述 */ + description: string; + /** 参数列表 */ + parameters: ParameterInfo[]; + /** 最大子节点数量:0=不允许子节点,1=最多一个子节点,-1=无限制 */ + maxChildren: number; + } + + /** + * 节点元数据存储 + */ + const NODE_METADATA_MAP = new Map(); + + /** + * 行为节点装饰器 + */ + export function ActionNode(metadata: Omit) { + return function any>(constructor: T) { + const fullMetadata: NodeMetadata = { + ...metadata, + type: Type.Action, + maxChildren: 0, + }; + NODE_METADATA_MAP.set(constructor.name, fullMetadata); + return constructor; + }; + } + + /** + * 条件节点装饰器 + */ + export function ConditionNode(metadata: Omit) { + return function any>(constructor: T) { + const fullMetadata: NodeMetadata = { + ...metadata, + type: Type.Condition, + maxChildren: 0, + }; + NODE_METADATA_MAP.set(constructor.name, fullMetadata); + return constructor; + }; + } + + /** + * 组合节点装饰器 + */ + export function CompositeNode(metadata: Omit) { + return function any>(constructor: T) { + const fullMetadata: NodeMetadata = { + ...metadata, + type: Type.Composite, + maxChildren: -1 + }; + NODE_METADATA_MAP.set(constructor.name, fullMetadata); + return constructor; + }; + } + + /** + * 装饰节点装饰器 + */ + export function DecoratorNode(metadata: Omit) { + return function any>(constructor: T) { + const fullMetadata: NodeMetadata = { + ...metadata, + type: Type.Decorator, + maxChildren: 1 + }; + NODE_METADATA_MAP.set(constructor.name, fullMetadata); + return constructor; + }; + } + + /** + * 获取节点元数据 + */ + export function getNodeMetadata(nodeName: string): NodeMetadata | undefined { + return NODE_METADATA_MAP.get(nodeName); + } + + /** + * 获取所有节点元数据 + */ + 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; + } +} \ No newline at end of file diff --git a/src/behaviortree/BTNode/Action.ts b/src/behaviortree/BTNode/Action.ts index 59a0d88..045a26e 100644 --- a/src/behaviortree/BTNode/Action.ts +++ b/src/behaviortree/BTNode/Action.ts @@ -1,24 +1,20 @@ +import { BT } from "../BT"; import { Status } from "../header"; import { LeafNode } from "./AbstractNodes"; -import { IBTNode } from "./BTNode"; - -export class Action extends LeafNode { - protected _func: (node: IBTNode) => Status; - constructor(func: (node: IBTNode) => Status) { - super(); - this._func = func; - } - - public tick(): Status { - return this._func?.(this) ?? Status.SUCCESS; - } -} /** * 次数等待节点(无子节点) * 次数内,返回RUNNING * 超次,返回SUCCESS */ +@BT.ActionNode({ + name: "等待次数", + group: "等待行为", + description: "等待指定次数后返回成功", + parameters: [ + { name: "maxTicks", type: BT.ParamType.int, description: "最大等待次数", defaultValue: 0, required: true } + ] +}) export class WaitTicks extends LeafNode { private _max: number; private _value: number; @@ -46,6 +42,14 @@ export class WaitTicks extends LeafNode { * 时间等待节点 时间(秒) * 时间到后返回SUCCESS,否则返回RUNNING */ +@BT.ActionNode({ + name: "等待时间", + group: "等待行为", + description: "等待指定时间(秒)后返回成功", + parameters: [ + { name: "duration", type: BT.ParamType.float, description: "等待时间(秒)", defaultValue: 0, required: true } + ] +}) export class WaitTime extends LeafNode { private _max: number; private _value: number = 0; diff --git a/src/behaviortree/BTNode/Composite.ts b/src/behaviortree/BTNode/Composite.ts index 56a9708..0ddb8c9 100644 --- a/src/behaviortree/BTNode/Composite.ts +++ b/src/behaviortree/BTNode/Composite.ts @@ -1,5 +1,8 @@ +import { BT } from "../BT"; import { Status } from "../header"; import { Composite, MemoryComposite } from "./AbstractNodes"; +import { IBTNode } from "./BTNode"; +import { WeightDecorator } from "./Decorator"; /** * 记忆选择节点 从上到下执行 @@ -8,6 +11,12 @@ import { Composite, MemoryComposite } from "./AbstractNodes"; * * 遇到 RUNNING 返回 RUNNING 下次从该节点开始 */ +@BT.CompositeNode({ + name: "记忆选择节点", + group: "基础组合节点", + description: "记住上次运行位置的选择节点,从记忆位置开始执行", + parameters: [] +}) export class MemSelector extends MemoryComposite { public tick(): Status { let index = this.get(`__nMemoryRunningIndex`); @@ -33,6 +42,12 @@ export class MemSelector extends MemoryComposite { * * 遇到 RUNNING 返回 RUNNING 下次从该节点开始 */ +@BT.CompositeNode({ + name: "记忆顺序节点", + group: "基础组合节点", + description: "记住上次运行位置的序列节点,从记忆位置开始执行", + parameters: [] +}) export class MemSequence extends MemoryComposite { public tick(): Status { let index = this.get(`__nMemoryRunningIndex`); @@ -51,28 +66,17 @@ export class MemSequence extends MemoryComposite { } } -/** - * 随机选择节点 - * 随机选择一个子节点执行 - * 返回子节点状态 - */ -export class RandomSelector extends Composite { - public tick(): Status { - if (this.children.length === 0) { - return Status.FAILURE; - } - - const childIndex = Math.floor(Math.random() * this.children.length); - const status = this.children[childIndex]!._execute(); - return status; - } -} - /** * 选择节点 从上到下执行 * 返回第一个不为 FAILURE 的子节点状态 * 否则返回 FAILURE */ +@BT.CompositeNode({ + name: "选择节点", + group: "基础组合节点", + description: "依次执行子节点,直到找到成功或运行中的节点", + parameters: [] +}) export class Selector extends Composite { public tick(): Status { for (let i = 0; i < this.children.length; i++) { @@ -85,11 +89,76 @@ export class Selector extends Composite { } } + +/** + * 随机选择节点 + * 随机选择一个子节点执行 + * 返回子节点状态 + */ +@BT.CompositeNode({ + name: "随机选择节点", + group: "基础组合节点", + description: "随机选择一个子节点执行", + parameters: [], +}) +export class RandomSelector extends Composite { + private _totalWeight: number = 0; + private _weights: number[] = []; + + constructor(...children: IBTNode[]) { + super(...children); + this._totalWeight = 0; + this._weights = []; + + for (const child of children) { + const weight = this.getChildWeight(child); + this._totalWeight += weight; + this._weights.push(this._totalWeight); + } + } + + private getChildWeight(child: IBTNode): number { + return (child instanceof WeightDecorator) ? (child.weight) : 1; + } + + public tick(): Status { + if (this.children.length === 0) { + return Status.FAILURE; + } + + // 基于权重的随机选择 + const randomValue = Math.random() * this._totalWeight; + + // 使用二分查找找到对应的子节点索引(O(log n)复杂度) + let left = 0; + let right = this._weights.length - 1; + let childIndex = 0; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + if (this._weights[mid]! > randomValue) { + childIndex = mid; + right = mid - 1; + } else { + left = mid + 1; + } + } + const status = this.children[childIndex]!._execute(); + return status; + } +} + /** * 顺序节点 从上到下执行 * 遇到 SUCCESS 继续下一个 * 否则返回子节点状态 */ +@BT.CompositeNode({ + name: "顺序节点", + group: "基础组合节点", + description: "依次执行所有子节点,全部成功才返回成功", + parameters: [] +}) export class Sequence extends Composite { public tick(): Status { for (let i = 0; i < this.children.length; i++) { @@ -107,6 +176,12 @@ export class Sequence extends Composite { * 并行节点 从上到下执行 全部执行一遍 * 返回优先级 FAILURE > RUNNING > SUCCESS */ +@BT.CompositeNode({ + name: "并行节点", + group: "基础组合节点", + description: "同时执行所有子节点,全部成功才返回成功", + parameters: [] +}) export class Parallel extends Composite { public tick(): Status { let result = Status.SUCCESS; @@ -126,6 +201,12 @@ export class Parallel extends Composite { * 并行节点 从上到下执行 全部执行一遍 * 返回优先级 SUCCESS > RUNNING > FAILURE */ +@BT.CompositeNode({ + name: "并行任意成功", + group: "基础组合节点", + description: "同时执行所有子节点,任意一个成功即返回成功", + parameters: [] +}) export class ParallelAnySuccess extends Composite { public tick(): Status { let result = Status.FAILURE; diff --git a/src/behaviortree/BTNode/Condition.ts b/src/behaviortree/BTNode/Condition.ts deleted file mode 100644 index 773ccbd..0000000 --- a/src/behaviortree/BTNode/Condition.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Status } from "../header"; -import { LeafNode } from "./AbstractNodes"; -import { IBTNode } from "./BTNode"; - -/** - * 条件节点 - * 根据条件函数返回SUCCESS或FAILURE - */ -export class Condition extends LeafNode { - /** 执行函数 @internal */ - private readonly _func: (node: IBTNode) => boolean; - constructor(func: (node: IBTNode) => boolean) { - super(); - this._func = func; - } - - public tick(): Status { - return this._func?.(this) ? Status.SUCCESS : Status.FAILURE; - } -} \ No newline at end of file diff --git a/src/behaviortree/BTNode/Decorator.ts b/src/behaviortree/BTNode/Decorator.ts index 83ab769..a0a1ac4 100644 --- a/src/behaviortree/BTNode/Decorator.ts +++ b/src/behaviortree/BTNode/Decorator.ts @@ -4,6 +4,7 @@ * @Description: 装饰节点 装饰节点下必须包含子节点 */ +import { BT } from "../BT"; import { Status } from "../header"; import { Decorator, NumericDecorator } from "./AbstractNodes"; import { IBTNode } from "./BTNode"; @@ -14,6 +15,12 @@ import { IBTNode } from "./BTNode"; * 第一个Child Node节点, 返回 FAILURE, 本Node向自己的Parent Node也返回 SUCCESS * 第一个Child Node节点, 返回 SUCCESS, 本Node向自己的Parent Node也返回 FAILURE */ +@BT.DecoratorNode({ + name: "反转器", + group: "基础装饰节点", + description: "反转子节点的执行结果,成功变失败,失败变成功", + parameters: [] +}) export class Inverter extends Decorator { public tick(): Status { const status = this.children[0]!._execute(); @@ -34,6 +41,14 @@ export class Inverter extends Decorator { * 规定时间内, 根据Child Node的结果, 本节点向自己的父节点也返回相同的结果 * 超时后, 直接返回 FAILURE */ +@BT.DecoratorNode({ + name: "时间限制器", + group: "基础装饰节点", + description: "限制子节点执行时间,超时返回失败", + parameters: [ + { name: "max", type: BT.ParamType.float, description: "最大时间(秒)", defaultValue: 1, required: true } + ] +}) export class LimitTime extends NumericDecorator { /** * 时间限制节点 @@ -62,6 +77,14 @@ export class LimitTime extends NumericDecorator { * 必须且只能包含一个子节点 * 次数超过后, 直接返回失败; 次数未超过, 返回子节点状态 */ +@BT.DecoratorNode({ + name: "次数限制器", + group: "基础装饰节点", + description: "限制子节点执行次数,超过次数返回失败", + parameters: [ + { name: "max", type: BT.ParamType.int, description: "最大次数", defaultValue: 1, required: true } + ] +}) export class LimitTicks extends NumericDecorator { public tick(): Status { this._value++; @@ -78,6 +101,14 @@ export class LimitTicks extends NumericDecorator { * 子节点是成功或失败,累加计数 * 次数超过之后返回子节点状态,否则返回 RUNNING */ +@BT.DecoratorNode({ + name: "重复器", + group: "基础装饰节点", + description: "重复执行子节点指定次数", + parameters: [ + { name: "max", type: BT.ParamType.int, description: "重复次数", defaultValue: 1, required: true } + ] +}) export class Repeat extends NumericDecorator { public tick(): Status { // 执行子节点 @@ -101,6 +132,14 @@ export class Repeat extends NumericDecorator { * * 子节点成功 计数+1 */ +@BT.DecoratorNode({ + name: "重复直到失败", + group: "基础装饰节点", + description: "重复执行子节点直到失败或达到最大次数", + parameters: [ + { name: "max", type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, required: true } + ] +}) export class RepeatUntilFailure extends NumericDecorator { public tick(): Status { const status = this.children[0]!._execute(); @@ -125,6 +164,14 @@ export class RepeatUntilFailure extends NumericDecorator { * * 子节点失败, 计数+1 */ +@BT.DecoratorNode({ + name: "重复直到成功", + group: "基础装饰节点", + description: "重复执行子节点直到成功或达到最大次数", + parameters: [ + { name: "max", type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, required: true } + ] +}) export class RepeatUntilSuccess extends NumericDecorator { public tick(): Status { // 执行子节点 @@ -141,4 +188,33 @@ export class RepeatUntilSuccess extends NumericDecorator { } return Status.RUNNING; } +} + +/** + * 权重装饰节点 + */ +@BT.DecoratorNode({ + name: "权重装饰器", + group: "基础装饰节点", + description: "权重装饰节点", + parameters: [ + { name: "weight", type: BT.ParamType.float, description: "权重", defaultValue: 1, required: true } + ] +}) +export class WeightDecorator extends Decorator { + private _weight: number; + + constructor(child: IBTNode, weight?: number) { + super(child); + // 优先使用构造函数参数,否则使用装饰器默认参数 + this._weight = weight || 1; + } + + public tick(): Status { + return this.children[0]!._execute(); + } + + public get weight(): number { + return this._weight; + } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 2a76bc4..cae75a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,6 @@ export * from "./behaviortree/BTNode/AbstractNodes"; export * from "./behaviortree/BTNode/Action"; export { IBTNode } from "./behaviortree/BTNode/BTNode"; export * from "./behaviortree/BTNode/Composite"; -export { Condition } from "./behaviortree/BTNode/Condition"; export * from "./behaviortree/BTNode/Decorator"; export { Status } from "./behaviortree/header"; diff --git a/tsconfig.json b/tsconfig.json index 5233e30..872912d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "lib": ["es6", "dom"], "module": "commonjs", "experimentalDecorators": true, // 启用ES装饰器 + "emitDecoratorMetadata": true, // 启用装饰器元数据 "strict": true, "noImplicitAny": true, "strictNullChecks": true, @@ -20,7 +21,6 @@ }, "include": [ "./src/**/*" - // "libs" ], // 排除 "exclude": [