From 9a3e7028d293792d7e850fc67f2b3032c453682d Mon Sep 17 00:00:00 2001 From: gongxh Date: Fri, 3 Oct 2025 18:09:47 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E6=A0=91=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E8=B0=83=E6=95=B4=EF=BC=8C=E5=88=A0=E9=99=A4=E9=9D=9E=E8=AE=B0?= =?UTF-8?q?=E5=BF=86=E7=9A=84=E9=80=89=E6=8B=A9=E5=92=8C=E9=A1=BA=E5=BA=8F?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/behaviortree/BTNode/AbstractNodes.ts | 66 ---------- src/behaviortree/BTNode/Action.ts | 20 ++- src/behaviortree/BTNode/BTNode.ts | 10 +- src/behaviortree/BTNode/Composite.ts | 149 ++++++++++------------- src/behaviortree/BTNode/Condition.ts | 15 +-- src/behaviortree/BTNode/Decorator.ts | 140 ++++++++++++--------- src/behaviortree/BehaviorTree.ts | 4 +- src/behaviortree/Factory.ts | 2 +- src/index.ts | 4 +- 9 files changed, 167 insertions(+), 243 deletions(-) delete mode 100644 src/behaviortree/BTNode/AbstractNodes.ts diff --git a/src/behaviortree/BTNode/AbstractNodes.ts b/src/behaviortree/BTNode/AbstractNodes.ts deleted file mode 100644 index de87719..0000000 --- a/src/behaviortree/BTNode/AbstractNodes.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @Author: Gongxh - * @Date: 2025-09-01 - * @Description: 抽象节点基类 - */ - -import { IBlackboard } from "../Blackboard"; -import { BTNode, IBTNode } from "./BTNode"; - -/** - * 叶子节点 基类 - * 没有子节点 - */ -export abstract class LeafNode extends BTNode { - constructor() { - super([]); - } -} - -/** - * 修饰节点 基类 - * 有且仅有一个子节点 - */ -export abstract class Decorator extends BTNode { - constructor(child: IBTNode) { - super([child]); - } -} - -/** - * 组合节点 基类 - * 多个子节点 - */ -export abstract class Composite extends BTNode { - constructor(...children: IBTNode[]) { - super(children); - } -} - -/** - * 数值型修饰节点 基类 - * 包含最大值和当前值的通用逻辑,适用于所有需要数值计数的修饰节点 - */ -export abstract class NumericDecorator extends Decorator { - protected _value: number = 0; - protected override open(): void { - super.open(); - this._value = 0; - } -} - -/** - * 记忆修饰节点基类 - * 只有记忆节点才需要设置局部数据 - */ -export abstract class MemoryComposite extends Composite { - public override _initialize(global: IBlackboard, branch: IBlackboard): void { - super._initialize(global, branch); - this._local = branch.createChild(); - } - - protected override open(): void { - super.open(); - this.set(`__nMemoryRunningIndex`, 0); - } -} \ No newline at end of file diff --git a/src/behaviortree/BTNode/Action.ts b/src/behaviortree/BTNode/Action.ts index 9c29263..5fc174a 100644 --- a/src/behaviortree/BTNode/Action.ts +++ b/src/behaviortree/BTNode/Action.ts @@ -1,6 +1,16 @@ import { BT } from "../BT"; import { Status } from "../header"; -import { LeafNode } from "./AbstractNodes"; +import { BTNode } from "./BTNode"; + +/** + * 叶子节点 基类 + * 没有子节点 + */ +export abstract class LeafNode extends BTNode { + constructor() { + super([]); + } +} /** * 次数等待节点(无子节点) @@ -56,12 +66,12 @@ export class WaitTime extends LeafNode { protected override open(): void { super.open(); - this._value = new Date().getTime(); + this._value = 0; } - public tick(): Status { - const currTime = new Date().getTime(); - if (currTime - this._value >= this._max * 1000) { + public tick(dt: number): Status { + this._value += dt; + if (this._value >= this._max) { return Status.SUCCESS; } return Status.RUNNING; diff --git a/src/behaviortree/BTNode/BTNode.ts b/src/behaviortree/BTNode/BTNode.ts index a77bb78..7d022d6 100644 --- a/src/behaviortree/BTNode/BTNode.ts +++ b/src/behaviortree/BTNode/BTNode.ts @@ -15,8 +15,8 @@ export interface IBTNode { /** * @internal */ - _execute(): Status; - tick(): Status; + _execute(dt: number): Status; + tick(dt: number): Status; /** * 优先写入自己的黑板数据, 如果没有则写入父节点的黑板数据 @@ -67,7 +67,7 @@ export abstract class BTNode implements IBTNode { /** * @internal */ - public _execute(): Status { + public _execute(dt: number): Status { // 首次执行时初始化 const isRunning = this._local.openNodes.get(this) || false; if (!isRunning) { @@ -76,7 +76,7 @@ export abstract class BTNode implements IBTNode { } // 执行核心逻辑 - const status = this.tick(); + const status = this.tick(dt); // 执行完成时清理 if (status !== Status.RUNNING) { @@ -98,7 +98,7 @@ export abstract class BTNode implements IBTNode { * 子类必须实现此方法 * @returns 执行状态 */ - public abstract tick(): Status; + public abstract tick(dt: number): Status; /** * 清理节点(执行完成时调用) diff --git a/src/behaviortree/BTNode/Composite.ts b/src/behaviortree/BTNode/Composite.ts index 6e58370..f245002 100644 --- a/src/behaviortree/BTNode/Composite.ts +++ b/src/behaviortree/BTNode/Composite.ts @@ -1,9 +1,19 @@ +import { IBlackboard } from "../Blackboard"; import { BT } from "../BT"; import { Status } from "../header"; -import { Composite, MemoryComposite } from "./AbstractNodes"; -import { IBTNode } from "./BTNode"; +import { BTNode, IBTNode } from "./BTNode"; import { WeightDecorator } from "./Decorator"; +/** + * 组合节点基类 + * 有多个子节点 + */ +export abstract class Composite extends BTNode { + constructor(...children: IBTNode[]) { + super(children); + } +} + /** * 记忆选择节点 从上到下执行 * 遇到 FAILURE 继续下一个 @@ -11,23 +21,29 @@ import { WeightDecorator } from "./Decorator"; * * 遇到 RUNNING 返回 RUNNING 下次从该节点开始 */ -@BT.CompositeNode("MemSelector", { - name: "记忆选择节点", - group: "基础组合节点", - desc: "记住上次运行位置的选择节点,从记忆位置开始执行", -}) -export class MemSelector extends MemoryComposite { - public tick(): Status { - let index = this.get(`__nMemoryRunningIndex`); +@BT.CompositeNode("Selector", { name: "选择节点", group: "基础组合节点", desc: "选择节点" }) +export class Selector extends Composite { + public override _initialize(global: IBlackboard, branch: IBlackboard): void { + super._initialize(global, branch); + this._local = branch.createChild(); + } + + protected override open(): void { + super.open(); + this.set(`__nRunningIndex`, 0); + } + + public tick(dt: number): Status { + let index = this.get(`__nRunningIndex`); for (let i = index; i < this.children.length; i++) { - let status = this.children[i]!._execute(); + let status = this.children[i]!._execute(dt); if (status === Status.FAILURE) { continue; } if (status === Status.SUCCESS) { return status; } - this.set(`__nMemoryRunningIndex`, i); + this.set(`__nRunningIndex`, i); return Status.RUNNING; } return Status.FAILURE; @@ -35,29 +51,35 @@ export class MemSelector extends MemoryComposite { } /** - * 记忆顺序节点 从上到下执行 + * 顺序节点 从上到下执行 * 遇到 SUCCESS 继续下一个 * 遇到 FAILURE 停止迭代 返回 FAILURE 下次重新开始 * * 遇到 RUNNING 返回 RUNNING 下次从该节点开始 */ -@BT.CompositeNode("MemSequence", { - name: "记忆顺序节点", - group: "基础组合节点", - desc: "记住上次运行位置的序列节点,从记忆位置开始执行", -}) -export class MemSequence extends MemoryComposite { - public tick(): Status { - let index = this.get(`__nMemoryRunningIndex`); +@BT.CompositeNode("Sequence", { name: "顺序节点", group: "基础组合节点", desc: "顺序节点" }) +export class Sequence extends Composite { + public override _initialize(global: IBlackboard, branch: IBlackboard): void { + super._initialize(global, branch); + this._local = branch.createChild(); + } + + protected override open(): void { + super.open(); + this.set(`__nRunningIndex`, 0); + } + + public tick(dt: number): Status { + let index = this.get(`__nRunningIndex`); for (let i = index; i < this.children.length; i++) { - let status = this.children[i]!._execute(); + let status = this.children[i]!._execute(dt); if (status === Status.SUCCESS) { continue; } if (status === Status.FAILURE) { return Status.FAILURE; } - this.set(`__nMemoryRunningIndex`, i); + this.set(`__nRunningIndex`, i); return Status.RUNNING; } return Status.SUCCESS; @@ -65,24 +87,22 @@ export class MemSequence extends MemoryComposite { } /** - * 选择节点 从上到下执行 - * 返回第一个不为 FAILURE 的子节点状态 - * 否则返回 FAILURE + * 并行节点 从上到下执行 全部执行一遍 + * 返回优先级 FAILURE > RUNNING > SUCCESS */ -@BT.CompositeNode("Selector", { - name: "选择节点", - group: "基础组合节点", - desc: "依次执行子节点,直到找到成功或运行中的节点", -}) -export class Selector extends Composite { - public tick(): Status { +@BT.CompositeNode("Parallel", { name: "并行节点", group: "基础组合节点", desc: "同时执行所有子节点,全部成功才返回成功" }) +export class Parallel extends Composite { + public tick(dt: number): Status { + let result = Status.SUCCESS; for (let i = 0; i < this.children.length; i++) { - let status = this.children[i]!._execute(); - if (status !== Status.FAILURE) { - return status; + let status = this.children[i]!._execute(dt); + if (result === Status.FAILURE || status === Status.FAILURE) { + result = Status.FAILURE; + } else if (status === Status.RUNNING) { + result = Status.RUNNING; } } - return Status.FAILURE; + return result; } } @@ -117,7 +137,7 @@ export class RandomSelector extends Composite { return (child instanceof WeightDecorator) ? (child.weight) : 1; } - public tick(): Status { + public tick(dt: number): Status { if (this.children.length === 0) { return Status.FAILURE; } @@ -139,58 +159,11 @@ export class RandomSelector extends Composite { left = mid + 1; } } - const status = this.children[childIndex]!._execute(); + const status = this.children[childIndex]!._execute(dt); return status; } } -/** - * 顺序节点 从上到下执行 - * 遇到 SUCCESS 继续下一个 - * 否则返回子节点状态 - */ -@BT.CompositeNode("Sequence", { - name: "顺序节点", - group: "基础组合节点", - desc: "依次执行所有子节点,全部成功才返回成功", -}) -export class Sequence extends Composite { - public tick(): Status { - for (let i = 0; i < this.children.length; i++) { - let status = this.children[i]!._execute(); - if (status === Status.SUCCESS) { - continue; - } - return status; - } - return Status.SUCCESS; - } -} - -/** - * 并行节点 从上到下执行 全部执行一遍 - * 返回优先级 FAILURE > RUNNING > SUCCESS - */ -@BT.CompositeNode("Parallel", { - name: "并行节点", - group: "基础组合节点", - desc: "同时执行所有子节点,全部成功才返回成功", -}) -export class Parallel extends Composite { - public tick(): Status { - let result = Status.SUCCESS; - for (let i = 0; i < this.children.length; i++) { - let status = this.children[i]!._execute(); - if (result === Status.FAILURE || status === Status.FAILURE) { - result = Status.FAILURE; - } else if (status === Status.RUNNING) { - result = Status.RUNNING; - } - } - return result; - } -} - /** * 并行节点 从上到下执行 全部执行一遍 * 返回优先级 SUCCESS > RUNNING > FAILURE @@ -201,10 +174,10 @@ export class Parallel extends Composite { desc: "同时执行所有子节点,任意一个成功即返回成功", }) export class ParallelAnySuccess extends Composite { - public tick(): Status { + public tick(dt: number): Status { let result = Status.FAILURE; for (let i = 0; i < this.children.length; i++) { - let status = this.children[i]!._execute(); + let status = this.children[i]!._execute(dt); if (result === Status.SUCCESS || status === Status.SUCCESS) { result = Status.SUCCESS; } else if (status === Status.RUNNING) { diff --git a/src/behaviortree/BTNode/Condition.ts b/src/behaviortree/BTNode/Condition.ts index ced9b5a..0fdee68 100644 --- a/src/behaviortree/BTNode/Condition.ts +++ b/src/behaviortree/BTNode/Condition.ts @@ -5,7 +5,7 @@ */ import { Status } from "../header"; -import { Decorator, LeafNode } from "./AbstractNodes"; +import { LeafNode } from "./Action"; /** 条件叶子节点 */ export abstract class Condition extends LeafNode { @@ -18,17 +18,4 @@ export abstract class Condition extends LeafNode { public tick(): Status { return this.isEligible() ? Status.SUCCESS : Status.FAILURE; } -} - -/** 条件装饰节点 */ -export abstract class ConditionDecorator extends Decorator { - /** - * 判断是否满足条件 - * @returns 是否满足条件 - */ - protected abstract isEligible(): boolean; - - public tick(): Status { - return this.isEligible() ? this.children[0]!._execute() : Status.FAILURE; - } } \ No newline at end of file diff --git a/src/behaviortree/BTNode/Decorator.ts b/src/behaviortree/BTNode/Decorator.ts index 36dec5b..28d09a2 100644 --- a/src/behaviortree/BTNode/Decorator.ts +++ b/src/behaviortree/BTNode/Decorator.ts @@ -6,8 +6,30 @@ import { BT } from "../BT"; import { Status } from "../header"; -import { Decorator, NumericDecorator } from "./AbstractNodes"; -import { IBTNode } from "./BTNode"; +import { BTNode, IBTNode } from "./BTNode"; + +/** + * 修饰节点 基类 + * 有且仅有一个子节点 + */ +export abstract class Decorator extends BTNode { + constructor(child: IBTNode) { + super([child]); + } +} + +/** 条件装饰节点基类 */ +export abstract class ConditionDecorator extends Decorator { + /** + * 判断是否满足条件 + * @returns 是否满足条件 + */ + protected abstract isEligible(): boolean; + + public tick(dt: number): Status { + return this.isEligible() ? this.children[0]!._execute(dt) : Status.FAILURE; + } +} /** * 结果反转节点 @@ -15,14 +37,10 @@ import { IBTNode } from "./BTNode"; * 第一个Child Node节点, 返回 FAILURE, 本Node向自己的Parent Node也返回 SUCCESS * 第一个Child Node节点, 返回 SUCCESS, 本Node向自己的Parent Node也返回 FAILURE */ -@BT.DecoratorNode("Inverter", { - name: "反转器", - group: "基础装饰节点", - desc: "反转子节点的执行结果,成功变失败,失败变成功", -}) +@BT.DecoratorNode("Inverter", { name: "反转器", group: "基础装饰节点", desc: "反转子节点的执行结果,成功变失败,失败变成功" }) export class Inverter extends Decorator { - public tick(): Status { - const status = this.children[0]!._execute(); + public tick(dt: number): Status { + const status = this.children[0]!._execute(dt); if (status === Status.SUCCESS) { return Status.FAILURE; @@ -40,15 +58,12 @@ export class Inverter extends Decorator { * 规定时间内, 根据Child Node的结果, 本节点向自己的父节点也返回相同的结果 * 超时后, 直接返回 FAILURE */ -@BT.DecoratorNode("LimitTime", { - name: "时间限制器", - group: "基础装饰节点", - desc: "限制子节点执行时间,超时返回失败", -}) -export class LimitTime extends NumericDecorator { +@BT.DecoratorNode("LimitTime", { name: "时间限制器", group: "基础装饰节点", desc: "限制子节点执行时间,超时返回失败" }) +export class LimitTime extends Decorator { @BT.prop({ type: BT.ParamType.float, description: "最大时间(秒)", defaultValue: 1 }) protected _max: number = 1; + private _value: number = 0; /** * 时间限制节点 * @param child 子节点 @@ -60,15 +75,15 @@ export class LimitTime extends NumericDecorator { } protected override open(): void { - this._value = Date.now(); + this._value = 0; } - public tick(): Status { - const currentTime = Date.now(); - if (currentTime - this._value > this._max * 1000) { + public tick(dt: number): Status { + this._value += dt; + if (this._value > this._max) { return Status.FAILURE; } - return this.children[0]!._execute(); + return this.children[0]!._execute(dt); } } @@ -77,25 +92,27 @@ export class LimitTime extends NumericDecorator { * 必须且只能包含一个子节点 * 次数超过后, 直接返回失败; 次数未超过, 返回子节点状态 */ -@BT.DecoratorNode("LimitTicks", { - name: "次数限制器", - group: "基础装饰节点", - desc: "限制子节点执行次数,超过次数返回失败", -}) -export class LimitTicks extends NumericDecorator { +@BT.DecoratorNode("LimitTicks", { name: "次数限制器", group: "基础装饰节点", desc: "限制子节点执行次数,超过次数返回失败" }) +export class LimitTicks extends Decorator { @BT.prop({ type: BT.ParamType.int, description: "最大次数", defaultValue: 1 }) protected _max: number = 1; + + private _value: number = 0; constructor(child: IBTNode, max: number = 1) { super(child); this._max = max; } - public tick(): Status { + protected override open(): void { + this._value = 0; + } + + public tick(dt: number): Status { this._value++; if (this._value > this._max) { return Status.FAILURE; } - return this.children[0]!._execute(); + return this.children[0]!._execute(dt); } } @@ -105,21 +122,24 @@ export class LimitTicks extends NumericDecorator { * 子节点是成功或失败,累加计数 * 次数超过之后返回子节点状态,否则返回 RUNNING */ -@BT.DecoratorNode("Repeat", { - name: "重复节点", - group: "基础装饰节点", - desc: "重复执行子节点指定次数", -}) -export class Repeat extends NumericDecorator { +@BT.DecoratorNode("Repeat", { name: "重复节点", group: "基础装饰节点", desc: "重复执行子节点指定次数" }) +export class Repeat extends Decorator { @BT.prop({ type: BT.ParamType.int, description: "重复次数", defaultValue: 1 }) protected _max: number = 1; + + private _value: number = 0; constructor(child: IBTNode, max: number = 1) { super(child); this._max = max; } - public tick(): Status { + + protected override open(): void { + this._value = 0; + } + + public tick(dt: number): Status { // 执行子节点 - const status = this.children[0]!._execute(); + const status = this.children[0]!._execute(dt); // 如果子节点完成(成功或失败),增加计数 if (status === Status.SUCCESS || status === Status.FAILURE) { this._value++; @@ -139,20 +159,23 @@ export class Repeat extends NumericDecorator { * * 子节点成功 计数+1 */ -@BT.DecoratorNode("RepeatUntilFailure", { - name: "重复直到失败", - group: "基础装饰节点", - desc: "重复执行子节点直到失败或达到最大次数", -}) -export class RepeatUntilFailure extends NumericDecorator { +@BT.DecoratorNode("RepeatUntilFailure", { name: "重复直到失败", group: "基础装饰节点", desc: "重复执行子节点直到失败或达到最大次数" }) +export class RepeatUntilFailure extends Decorator { @BT.prop({ type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1 }) protected _max: number = 1; + + private _value: number = 0; constructor(child: IBTNode, max: number = 1) { super(child); this._max = max; } - public tick(): Status { - const status = this.children[0]!._execute(); + + protected override open(): void { + this._value = 0; + } + + public tick(dt: number): Status { + const status = this.children[0]!._execute(dt); if (status === Status.FAILURE) { return Status.FAILURE; } @@ -174,21 +197,24 @@ export class RepeatUntilFailure extends NumericDecorator { * * 子节点失败, 计数+1 */ -@BT.DecoratorNode("RepeatUntilSuccess", { - name: "重复直到成功", - group: "基础装饰节点", - desc: "重复执行子节点直到成功或达到最大次数", -}) -export class RepeatUntilSuccess extends NumericDecorator { +@BT.DecoratorNode("RepeatUntilSuccess", { name: "重复直到成功", group: "基础装饰节点", desc: "重复执行子节点直到成功或达到最大次数" }) +export class RepeatUntilSuccess extends Decorator { @BT.prop({ type: BT.ParamType.int, description: "最大重试次数", defaultValue: 1, step: 1 }) protected _max: number = 1; + + private _value: number = 0; constructor(child: IBTNode, max: number = 1) { super(child); this._max = max; } - public tick(): Status { + + protected override open(): void { + this._value = 0; + } + + public tick(dt: number): Status { // 执行子节点 - const status = this.children[0]!._execute(); + const status = this.children[0]!._execute(dt); if (status === Status.SUCCESS) { return Status.SUCCESS; } @@ -206,11 +232,7 @@ export class RepeatUntilSuccess extends NumericDecorator { /** * 权重装饰节点 */ -@BT.DecoratorNode("WeightDecorator", { - name: "权重装饰器", - group: "基础装饰节点", - desc: "权重装饰节点", -}) +@BT.DecoratorNode("WeightDecorator", { name: "权重装饰器", group: "基础装饰节点", desc: "权重装饰节点" }) export class WeightDecorator extends Decorator { @BT.prop({ type: BT.ParamType.int, description: "权重", defaultValue: 1, step: 1 }) private _weight: number; @@ -221,8 +243,8 @@ export class WeightDecorator extends Decorator { this._weight = weight || 1; } - public tick(): Status { - return this.children[0]!._execute(); + public tick(dt: number): Status { + return this.children[0]!._execute(dt); } public get weight(): number { diff --git a/src/behaviortree/BehaviorTree.ts b/src/behaviortree/BehaviorTree.ts index 3598a35..234c548 100644 --- a/src/behaviortree/BehaviorTree.ts +++ b/src/behaviortree/BehaviorTree.ts @@ -34,8 +34,8 @@ export class BehaviorTree { /** * 执行行为树 */ - public tick(): Status { - return this._root._execute(); + public tick(dt: number): Status { + return this._root._execute(dt); } /** diff --git a/src/behaviortree/Factory.ts b/src/behaviortree/Factory.ts index c823181..35f2648 100644 --- a/src/behaviortree/Factory.ts +++ b/src/behaviortree/Factory.ts @@ -8,7 +8,7 @@ import { BehaviorTree } from "./BehaviorTree"; import { BT } from "./BT"; import { IBTNode } from "./BTNode/BTNode"; -interface INodeConfig { +export interface INodeConfig { id: string, className: string, parameters: Record, diff --git a/src/index.ts b/src/index.ts index 9127dda..f11b7d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,14 +2,12 @@ /** 行为树 */ export { BehaviorTree } from "./behaviortree/BehaviorTree"; export { Blackboard } from "./behaviortree/Blackboard"; -export { } from "./behaviortree/BT"; -export * from "./behaviortree/BTNode/AbstractNodes"; export * from "./behaviortree/BTNode/Action"; export { IBTNode } from "./behaviortree/BTNode/BTNode"; export * from "./behaviortree/BTNode/Composite"; export * from "./behaviortree/BTNode/Condition"; export * from "./behaviortree/BTNode/Decorator"; -export { createBehaviorTree } from "./behaviortree/Factory"; +export { createBehaviorTree, INodeConfig } from "./behaviortree/Factory"; export { Status } from "./behaviortree/header"; // 导出装饰器内容