diff --git a/assets/libs/behavior-tree/BTNodeFactory.ts b/assets/libs/behavior-tree/BTNodeFactory.ts new file mode 100644 index 0000000..47df509 --- /dev/null +++ b/assets/libs/behavior-tree/BTNodeFactory.ts @@ -0,0 +1,30 @@ +/** + * 内置节点工厂注册。 + * 由 index.ts 导出时执行,确保所有内置类型在 fromJSON 前已注册。 + * 自定义 Task 子类需在业务初始化时手动调用 BehaviorTree.registerFactory()。 + */ +import { BehaviorTree } from './BehaviorTree'; +import { Decorator } from './Decorator'; +import { Priority } from './Priority'; +import { Selector } from './Selector'; +import { Sequence } from './Sequence'; +import { Task } from './Task'; + +BehaviorTree.registerFactory('Sequence', (j) => + new Sequence((j.children ?? []).map(c => BehaviorTree.fromJSON(c))) +); + +BehaviorTree.registerFactory('Selector', (j) => + new Selector((j.children ?? []).map(c => BehaviorTree.fromJSON(c))) +); + +BehaviorTree.registerFactory('Priority', (j) => + new Priority((j.children ?? []).map(c => BehaviorTree.fromJSON(c))) +); + +BehaviorTree.registerFactory('Decorator', (j) => { + const child = j.children?.[0]; + return new Decorator(child ? BehaviorTree.fromJSON(child) : undefined); +}); + +BehaviorTree.registerFactory('Task', (_j) => new Task()); diff --git a/assets/libs/behavior-tree/BTNodeFactory.ts.meta b/assets/libs/behavior-tree/BTNodeFactory.ts.meta new file mode 100644 index 0000000..a83c3cc --- /dev/null +++ b/assets/libs/behavior-tree/BTNodeFactory.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "a687776b-0999-4c4b-bab4-f7507766cc3d", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/libs/behavior-tree/BTNodeJson.ts b/assets/libs/behavior-tree/BTNodeJson.ts new file mode 100644 index 0000000..9bb7f76 --- /dev/null +++ b/assets/libs/behavior-tree/BTNodeJson.ts @@ -0,0 +1,9 @@ +/** 行为树节点的 JSON 序列化格式,仅 3 个字段,结构精简 */ +export interface BTNodeJson { + /** 节点类型名,对应 registerFactory 注册的 key */ + type: string; + /** 可选唯一标识,用于可视化高亮/定位 */ + id?: string; + /** 子节点列表;叶子节点无此字段,Decorator 固定长度为 1 */ + children?: BTNodeJson[]; +} diff --git a/assets/libs/behavior-tree/BTNodeJson.ts.meta b/assets/libs/behavior-tree/BTNodeJson.ts.meta new file mode 100644 index 0000000..830d386 --- /dev/null +++ b/assets/libs/behavior-tree/BTNodeJson.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "9057271f-a13c-4288-8920-88feb5a35543", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/libs/behavior-tree/BTreeNode.ts b/assets/libs/behavior-tree/BTreeNode.ts index 3f271d4..854b716 100644 --- a/assets/libs/behavior-tree/BTreeNode.ts +++ b/assets/libs/behavior-tree/BTreeNode.ts @@ -4,6 +4,7 @@ * @LastEditors: dgflash * @LastEditTime: 2022-07-20 14:04:44 */ +import type { BTNodeJson } from './BTNodeJson'; import type { IControl } from './IControl'; /** 行为树节点 */ @@ -11,35 +12,34 @@ export abstract class BTreeNode implements IControl { protected _control: IControl | null = null; title: string; + /** 可选唯一标识,用于可视化高亮/定位 */ + id?: string; constructor() { this.title = this.constructor.name; } - start(blackboard?: any) { + start(blackboard?: object): void { } - } + end(blackboard?: object): void { } - end(blackboard?: any) { + abstract run(blackboard?: object): void; - } - - abstract run(blackboard?: any): void; - - setControl(control: IControl) { + setControl(control: IControl): void { this._control = control; } - running(blackboard?: any) { + /** 通知控制器当前节点处于 running 状态 */ + running(node: BTreeNode = this): void { if (this._control) { - this._control.running(this); + this._control.running(node); } else { console.error(`节点【${this.title}】的控制器未设置`); } } - success() { + success(): void { if (this._control) { this._control.success(); } @@ -48,7 +48,7 @@ export abstract class BTreeNode implements IControl { } } - fail() { + fail(): void { if (this._control) { this._control.fail(); } @@ -57,8 +57,17 @@ export abstract class BTreeNode implements IControl { } } + /** 序列化为 JSON 描述,叶子节点不含 children */ + toJSON(): BTNodeJson { + const json: BTNodeJson = { type: this.constructor.name }; + if (this.id !== undefined) { + json.id = this.id; + } + return json; + } + /** 清理节点资源 */ - destroy() { + destroy(): void { this._control = null; } } diff --git a/assets/libs/behavior-tree/BehaviorTree.ts b/assets/libs/behavior-tree/BehaviorTree.ts index 1417748..64c3c7a 100644 --- a/assets/libs/behavior-tree/BehaviorTree.ts +++ b/assets/libs/behavior-tree/BehaviorTree.ts @@ -1,11 +1,25 @@ +import type { BTNodeJson } from './BTNodeJson'; import { BTreeNode } from './BTreeNode'; import type { IControl } from './IControl'; -let countUnnamed = 0; +type NodeFactory = (json: BTNodeJson) => BTreeNode; + +/** 可复用 ID 池,避免 countUnnamed 单向无限增长 */ +const _idPool: number[] = []; +let _idCounter = 0; + +function allocId(): number { + return _idPool.length > 0 ? _idPool.pop()! : ++_idCounter; +} + +function freeId(id: number): void { + _idPool.push(id); +} /** 行为树 */ export class BehaviorTree implements IControl { - private readonly title: string; + private readonly _title: string; + private readonly _id: number; /** 根节点 */ private readonly _root: BTreeNode; @@ -14,91 +28,132 @@ export class BehaviorTree implements IControl { /** 是否已开始执行 */ private _started = false; /** 外部参数对象 */ - private _blackboard: any; + private _blackboard: object | undefined = undefined; - /** 是否已开始执行 */ + /** 是否正在执行中 */ get started(): boolean { return this._started; } /** - * 构造函数 * @param node 根节点 - * @param blackboard 外部参数对象 + * @param blackboard 共享数据对象 */ - constructor(node: BTreeNode, blackboard?: any) { - countUnnamed += 1; - this.title = node.constructor.name + '(btree_' + (countUnnamed) + ')'; + constructor(node: BTreeNode, blackboard?: object) { + this._id = allocId(); + this._title = node.constructor.name + '(btree_' + this._id + ')'; this._root = node; this._blackboard = blackboard; } /** 设置行为逻辑中的共享数据 */ - setObject(blackboard: any) { + setObject(blackboard: object): void { this._blackboard = blackboard; } /** 执行行为树逻辑 */ - run() { + run(blackboard?: object): void { if (this._started) { - console.error(`行为树【${this.title}】未调用步骤,在最后一次调用步骤时有一个任务未完成`); + console.error(`行为树【${this._title}】未调用步骤,在最后一次调用步骤时有一个任务未完成`); } this._started = true; - const node = BehaviorTree.getNode(this._root); + const node = this._root; this._current = node; node.setControl(this); node.start(this._blackboard); node.run(this._blackboard); } - running(node: BTreeNode) { + running(node: BTreeNode): void { this._started = false; } - success() { - if (this._current) { - this._current.end(this._blackboard); - } + success(): void { + this._current?.end(this._blackboard); this._started = false; } - fail() { - if (this._current) { - this._current.end(this._blackboard); - } + fail(): void { + this._current?.end(this._blackboard); this._started = false; } - /** 清理行为树资源 */ - destroy() { + /** 清理行为树资源,释放 ID 供后续复用 */ + destroy(): void { this._current = null; - this._blackboard = null; + this._blackboard = undefined; this._started = false; + freeId(this._id); } - /** ---------------------------------------------------------------------------------------------------- */ + /** ------------------------------------------------------------------ */ - static _registeredNodes: Map = new Map(); + /** JSON 节点工厂:type -> 构造函数 */ + private static readonly _factories: Map = new Map(); - /** 注册节点 */ - static register(name: string, node: BTreeNode) { + /** + * 注册节点工厂,供 fromJSON() 反序列化使用。 + * 内置类型由 BTNodeFactory.ts 统一注册;自定义 Task 子类手动调用此方法。 + */ + static registerFactory(type: string, factory: NodeFactory): void { + this._factories.set(type, factory); + } + + /** + * 从 JSON 描述反序列化出节点树。 + * 需先通过 registerFactory 注册对应 type 的工厂。 + */ + static fromJSON(json: BTNodeJson): BTreeNode { + const factory = this._factories.get(json.type); + if (!factory) { + throw new Error(`未注册的节点类型【${json.type}】,请先调用 BehaviorTree.registerFactory()`); + } + const node = factory(json); + if (json.id !== undefined) { + node.id = json.id; + } + return node; + } + + /** ------------------------------------------------------------------ */ + + private static readonly _registeredNodes: Map = new Map(); + + /** 注册节点,name 重复时覆盖旧值 */ + static register(name: string, node: BTreeNode): void { this._registeredNodes.set(name, node); } - /** 注销节点 */ - static unregister(name: string) { - this._registeredNodes.delete(name); + /** + * 注销节点并销毁其资源。 + * 与 destroy() 联动,防止静态 Map 长期持有已废弃节点。 + */ + static unregister(name: string): void { + const node = this._registeredNodes.get(name); + if (node) { + node.destroy(); + this._registeredNodes.delete(name); + } } - /** 清理所有注册的节点 */ - static clearAll() { + /** 清理并销毁所有已注册节点 */ + static clearAll(): void { + for (const node of this._registeredNodes.values()) { + node.destroy(); + } this._registeredNodes.clear(); } - /** 获取节点 */ + /** + * 获取节点:传入实例时直接返回,传入名称时从注册表查找。 + * 构造/配置阶段通过此方法解析,运行时持有实例引用,避免热路径 Map 查找。 + */ static getNode(name: string | BTreeNode): BTreeNode { - const node = name instanceof BTreeNode ? name : this._registeredNodes.get(name); + if (name instanceof BTreeNode) { + return name; + } + const node = this._registeredNodes.get(name); if (!node) { throw new Error(`无法找到节点【${name}】,可能它没有注册过`); } diff --git a/assets/libs/behavior-tree/BranchNode.ts b/assets/libs/behavior-tree/BranchNode.ts index 807053d..2a78a5a 100644 --- a/assets/libs/behavior-tree/BranchNode.ts +++ b/assets/libs/behavior-tree/BranchNode.ts @@ -4,94 +4,90 @@ * @LastEditors: dgflash * @LastEditTime: 2022-07-20 13:58:32 */ -import { BehaviorTree } from './BehaviorTree'; +import type { BTNodeJson } from './BTNodeJson'; import { BTreeNode } from './BTreeNode'; /** 复合节点 */ export abstract class BranchNode extends BTreeNode { /** 子节点数组 */ - children: Array; + children: BTreeNode[]; /** 当前任务索引 */ protected _actualTask: number = 0; - /** 正在运行的节点 */ - protected _runningNode: BTreeNode | null = null; - protected _nodeRunning: BTreeNode | null = null; + /** + * 当前正在执行的子节点。 + * 原 _runningNode 与 _nodeRunning 语义重叠,合并为单一引用。 + */ + protected _activeNode: BTreeNode | null = null; /** 外部参数对象 */ - protected _blackboard: any; + protected _blackboard: object | undefined = undefined; - constructor(nodes: Array) { + constructor(nodes: BTreeNode[]) { super(); - this.children = nodes || []; + this.children = nodes ?? []; } - start() { + start(blackboard?: object): void { this._actualTask = 0; - super.start(); + super.start(blackboard); } - run(blackboard?: any) { - if (this.children.length === 0) { // 没有子任务直接视为执行失败 - if (this._control) { - this._control.fail(); - } + run(blackboard?: object): void { + if (this.children.length === 0) { + this._control?.fail(); } else { this._blackboard = blackboard; - this.start(); + this.start(blackboard); if (this._actualTask < this.children.length) { this._run(); } } - this.end(); + this.end(blackboard); } - /** 执行当前节点逻辑 */ - protected _run(blackboard?: any) { - // 直接使用子节点,不需要通过 getNode 查询(性能优化) + /** 执行当前索引对应的子节点 */ + protected _run(): void { const node = this.children[this._actualTask]; if (node) { - this._runningNode = node; + this._activeNode = node; node.setControl(this); node.start(this._blackboard); node.run(this._blackboard); } } - running(node: BTreeNode) { - this._nodeRunning = node; - if (this._control) { - this._control.running(node); - } + running(node: BTreeNode): void { + this._activeNode = node; + this._control?.running(node); } - success() { - this._nodeRunning = null; - if (this._runningNode) { - this._runningNode.end(this._blackboard); - } + success(): void { + const node = this._activeNode; + this._activeNode = null; + node?.end(this._blackboard); } - fail() { - this._nodeRunning = null; - if (this._runningNode) { - this._runningNode.end(this._blackboard); - } + fail(): void { + const node = this._activeNode; + this._activeNode = null; + node?.end(this._blackboard); + } + + toJSON(): BTNodeJson { + const json = super.toJSON(); + json.children = this.children.map(c => c.toJSON()); + return json; } /** 清理节点资源 */ - destroy() { - // 清理所有子节点 - if (this.children) { - this.children.forEach(child => { - if (child && typeof child.destroy === 'function') { - child.destroy(); - } - }); + destroy(): void { + for (const child of this.children) { + child.destroy(); } - this._runningNode = null; - this._nodeRunning = null; - this._blackboard = null; + this.children.length = 0; + this._activeNode = null; + this._blackboard = undefined; super.destroy(); } } diff --git a/assets/libs/behavior-tree/Decorator.ts b/assets/libs/behavior-tree/Decorator.ts index 05a5d8b..f6f41f7 100644 --- a/assets/libs/behavior-tree/Decorator.ts +++ b/assets/libs/behavior-tree/Decorator.ts @@ -5,45 +5,43 @@ * @LastEditTime: 2022-07-20 14:05:02 */ import { BehaviorTree } from './BehaviorTree'; +import type { BTNodeJson } from './BTNodeJson'; import { BTreeNode } from './BTreeNode'; /** - * 装饰器是条件语句只能附加在其他节点上并且定义所附加的节点是否执行 - * 如果装饰器是true 它所在的子树会被执行,如果是false 所在的子树不会被执行 + * 装饰器节点:条件语句,附加在其他节点上控制其是否执行。 + * 装饰器为 true 时子树执行,为 false 时子树跳过。 */ export class Decorator extends BTreeNode { node: BTreeNode | null = null; constructor(node?: string | BTreeNode) { super(); - - if (node) { + if (node !== undefined) { this.node = BehaviorTree.getNode(node); } } - protected setNode(node: string | BTreeNode) { + protected setNode(node: string | BTreeNode): void { this.node = BehaviorTree.getNode(node); } - start() { + start(blackboard?: object): void { if (this.node) { this.node.setControl(this); - this.node.start(); + this.node.start(blackboard); } else { console.error(`装饰器节点【${this.title}】没有设置子节点`); } - super.start(); + super.start(blackboard); } - end() { - if (this.node) { - this.node.end(); - } + end(blackboard?: object): void { + this.node?.end(blackboard); } - run(blackboard: any) { + run(blackboard?: object): void { if (this.node) { this.node.run(blackboard); } @@ -53,11 +51,15 @@ export class Decorator extends BTreeNode { } } - /** 清理节点资源 */ - destroy() { - if (this.node && typeof this.node.destroy === 'function') { - this.node.destroy(); - } + toJSON(): BTNodeJson { + const json = super.toJSON(); + json.children = this.node ? [this.node.toJSON()] : []; + return json; + } + + /** 清理节点资源:先销毁子节点,再清理自身 */ + destroy(): void { + this.node?.destroy(); this.node = null; super.destroy(); } diff --git a/assets/libs/behavior-tree/IControl.ts b/assets/libs/behavior-tree/IControl.ts index 641c7e1..8621a3b 100644 --- a/assets/libs/behavior-tree/IControl.ts +++ b/assets/libs/behavior-tree/IControl.ts @@ -4,17 +4,19 @@ * @LastEditors: dgflash * @LastEditTime: 2022-07-20 14:04:27 */ +import type { BTreeNode } from './BTreeNode'; + /** 行为控制接口 */ export interface IControl { /** 行为处理成功 */ - success(blackboard?: any): void; + success(): void; /** 行为处理失败 */ - fail(blackboard?: any): void; + fail(): void; /** 处理行为逻辑 */ - run(blackboard?: any): void; + run(blackboard?: object): void; - /** 正在处理中 */ - running(blackboard?: any): void; + /** 正在处理中,传入当前正在运行的节点 */ + running(node: BTreeNode): void; } diff --git a/assets/libs/behavior-tree/Priority.ts b/assets/libs/behavior-tree/Priority.ts index 8f5dc57..69dae58 100644 --- a/assets/libs/behavior-tree/Priority.ts +++ b/assets/libs/behavior-tree/Priority.ts @@ -6,22 +6,22 @@ */ import { BranchNode } from './BranchNode'; -/** 优先 */ +/** 优先选择节点:首个成功的子节点即返回成功,全部失败则返回失败 */ export class Priority extends BranchNode { - success() { + success(): void { super.success(); - this._control.success(); + this._control!.success(); } - fail() { + fail(): void { super.fail(); this._actualTask += 1; if (this._actualTask < this.children.length) { - this._run(this._blackboard); + this._run(); } else { - this._control.fail(); + this._control!.fail(); } } } diff --git a/assets/libs/behavior-tree/Selector.ts b/assets/libs/behavior-tree/Selector.ts index 6064113..a930f44 100644 --- a/assets/libs/behavior-tree/Selector.ts +++ b/assets/libs/behavior-tree/Selector.ts @@ -8,29 +8,30 @@ import { BranchNode } from './BranchNode'; /** * 逻辑或关系 - * 只要子节点有一个返回true,则停止执行其它子节点,并且Selector返回true。如果所有子节点都返回false,则Selector返回false。 + * 只要子节点有一个返回 true,则停止执行其它子节点,Selector 返回 true。 + * 所有子节点都返回 false 时,Selector 返回 false。 */ export class Selector extends BranchNode { - success() { + success(): void { super.success(); - this._control.success(); + this._control!.success(); } - fail() { + fail(): void { super.fail(); this._actualTask += 1; if (this._actualTask < this.children.length) { - this._run(this._blackboard); + this._run(); } else { - this._control.fail(); + this._control!.fail(); } } - protected _run(blackboard?: any) { - if (this._nodeRunning) { - this._nodeRunning.run(this._blackboard); + protected _run(): void { + if (this._activeNode) { + this._activeNode.run(this._blackboard); } else { super._run(); diff --git a/assets/libs/behavior-tree/Sequence.ts b/assets/libs/behavior-tree/Sequence.ts index 61bbf63..01b2ed7 100644 --- a/assets/libs/behavior-tree/Sequence.ts +++ b/assets/libs/behavior-tree/Sequence.ts @@ -5,37 +5,33 @@ * @LastEditTime: 2022-07-20 14:05:22 */ import { BranchNode } from './BranchNode'; -import type { BTreeNode } from './BTreeNode'; /** * 逻辑与关系 - * 只要有一个子节点返回false,则停止执行其它子节点,并且Sequence返回false。如果所有子节点都返回true,则Sequence返回true。 + * 只要有一个子节点返回 false,则停止执行其它子节点,Sequence 返回 false。 + * 所有子节点都返回 true 时,Sequence 返回 true。 */ export class Sequence extends BranchNode { - constructor(nodes: Array) { - super(nodes); - } - - success() { + success(): void { super.success(); this._actualTask += 1; if (this._actualTask < this.children.length) { - this._run(this._blackboard); + this._run(); } else { - this._control.success(); + this._control!.success(); } } - fail() { + fail(): void { super.fail(); - this._control.fail(); + this._control!.fail(); } - protected _run(blackboard?: any) { - if (this._nodeRunning) { - this._nodeRunning.run(this._blackboard); + protected _run(): void { + if (this._activeNode) { + this._activeNode.run(this._blackboard); } else { super._run(); diff --git a/assets/libs/behavior-tree/Task.ts b/assets/libs/behavior-tree/Task.ts index 3dd5790..9f686a9 100644 --- a/assets/libs/behavior-tree/Task.ts +++ b/assets/libs/behavior-tree/Task.ts @@ -6,14 +6,12 @@ */ import { BTreeNode } from './BTreeNode'; -/** - * 任务行为节点 - * 这是一个基类,子类应该实现具体的 run 方法 +/** + * 叶子任务节点基类,子类重写 run() 实现具体行为逻辑。 + * 默认实现直接调用 success()。 */ export class Task extends BTreeNode { - run(blackboard?: any) { - // 默认实现:直接成功 - // 子类应该重写此方法实现具体逻辑 + run(blackboard?: object): void { this.success(); } } diff --git a/assets/libs/behavior-tree/index.ts b/assets/libs/behavior-tree/index.ts index 9f203e1..a45f4e3 100644 --- a/assets/libs/behavior-tree/index.ts +++ b/assets/libs/behavior-tree/index.ts @@ -1,3 +1,4 @@ +export * from './BTNodeJson'; export * from './BehaviorTree'; export * from './BranchNode'; export * from './Decorator'; @@ -6,3 +7,6 @@ export * from './Priority'; export * from './Sequence'; export * from './Task'; export * from './Selector'; + +// 导入此文件即完成内置节点工厂注册,fromJSON 可直接使用内置类型 +import './BTNodeFactory';