From d7fc1e49ae9f779f4d10cd36d5ff48f2d87023df Mon Sep 17 00:00:00 2001 From: gongxh Date: Tue, 16 Sep 2025 18:12:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A0=B9=E6=8D=AE=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=88=9B=E5=BB=BA=E8=A1=8C=E4=B8=BA=E6=A0=91=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/behaviortree/BT.ts | 23 ++ src/behaviortree/BTNode/Action.ts | 4 +- src/behaviortree/BTNode/Decorator.ts | 4 +- src/behaviortree/Factory.ts | 71 ++++ src/behaviortree/header.ts | 2 +- src/index.ts | 7 +- test/simple-runner.ts | 581 --------------------------- 7 files changed, 104 insertions(+), 588 deletions(-) create mode 100644 src/behaviortree/Factory.ts delete mode 100644 test/simple-runner.ts diff --git a/src/behaviortree/BT.ts b/src/behaviortree/BT.ts index 61cc909..1919ff1 100644 --- a/src/behaviortree/BT.ts +++ b/src/behaviortree/BT.ts @@ -64,6 +64,11 @@ export namespace BT { maxChildren: number; } + /** + * 注册节点名 到 节点构造函数的映射 + */ + const NODE_NAME_TO_CONSTRUCTOR_MAP = new Map(); + /** * 节点元数据存储 */ @@ -111,6 +116,7 @@ export namespace BT { parameters }; NODE_METADATA_MAP.set(constructor, fullMetadata); + NODE_NAME_TO_CONSTRUCTOR_MAP.set(name, constructor); return constructor; }; } @@ -131,6 +137,7 @@ export namespace BT { parameters }; NODE_METADATA_MAP.set(constructor, fullMetadata); + NODE_NAME_TO_CONSTRUCTOR_MAP.set(name, constructor); return constructor; }; } @@ -151,6 +158,7 @@ export namespace BT { parameters }; NODE_METADATA_MAP.set(constructor, fullMetadata); + NODE_NAME_TO_CONSTRUCTOR_MAP.set(name, constructor); return constructor; }; } @@ -171,6 +179,7 @@ export namespace BT { parameters }; NODE_METADATA_MAP.set(constructor, fullMetadata); + NODE_NAME_TO_CONSTRUCTOR_MAP.set(name, constructor); return constructor; }; } @@ -181,6 +190,20 @@ export namespace BT { export function getAllNodeMetadata(): Map { return new Map(NODE_METADATA_MAP); } + + /** + * 通过节点名 获取 节点构造函数 + */ + export function getNodeConstructor(name: string): any { + return NODE_NAME_TO_CONSTRUCTOR_MAP.get(name); + } + + /** + * 通过节点构造函数 找到节点元数据 + */ + export function getNodeMetadata(ctor: any): NodeMetadata { + return NODE_METADATA_MAP.get(ctor)!; + } } let _global = globalThis || window || global; diff --git a/src/behaviortree/BTNode/Action.ts b/src/behaviortree/BTNode/Action.ts index 1ef297c..b75df39 100644 --- a/src/behaviortree/BTNode/Action.ts +++ b/src/behaviortree/BTNode/Action.ts @@ -51,7 +51,7 @@ export class WaitTime extends LeafNode { private _value: number = 0; constructor(duration: number = 0) { super(); - this._max = duration * 1000; + this._max = duration; } protected override open(): void { @@ -61,7 +61,7 @@ export class WaitTime extends LeafNode { public tick(): Status { const currTime = new Date().getTime(); - if (currTime - this._value >= this._max) { + if (currTime - this._value >= this._max * 1000) { return Status.SUCCESS; } return Status.RUNNING; diff --git a/src/behaviortree/BTNode/Decorator.ts b/src/behaviortree/BTNode/Decorator.ts index 6ebbbc1..36dec5b 100644 --- a/src/behaviortree/BTNode/Decorator.ts +++ b/src/behaviortree/BTNode/Decorator.ts @@ -56,7 +56,7 @@ export class LimitTime extends NumericDecorator { */ constructor(child: IBTNode, max: number = 1) { super(child); - this._max = max * 1000; + this._max = max; } protected override open(): void { @@ -65,7 +65,7 @@ export class LimitTime extends NumericDecorator { public tick(): Status { const currentTime = Date.now(); - if (currentTime - this._value > this._max) { + if (currentTime - this._value > this._max * 1000) { return Status.FAILURE; } return this.children[0]!._execute(); diff --git a/src/behaviortree/Factory.ts b/src/behaviortree/Factory.ts new file mode 100644 index 0000000..c823181 --- /dev/null +++ b/src/behaviortree/Factory.ts @@ -0,0 +1,71 @@ +/** + * @Author: Gongxh + * @Date: 2025-09-16 + * @Description: 根据数据创建一颗行为树 + */ + +import { BehaviorTree } from "./BehaviorTree"; +import { BT } from "./BT"; +import { IBTNode } from "./BTNode/BTNode"; + +interface INodeConfig { + id: string, + className: string, + parameters: Record, + children: string[] +} + +/** + * 根据节点配置递归创建节点 + * @param info 节点配置 + * @param nodeMap 所有节点配置的映射表 + * @returns 创建的节点实例 + */ +function createNodeRecursively(info: INodeConfig, nodeMap: Map): IBTNode { + // 获取节点构造函数 + const ctor = BT.getNodeConstructor(info.className); + if (!ctor) { + throw new Error(`未找到节点【${info.className}】的构造函数`); + } + + // 递归创建子节点 + const childNodes: IBTNode[] = []; + for (const childId of info.children || []) { + const childInfo = nodeMap.get(childId); + if (!childInfo) { + throw new Error(`未找到子节点【${childId}】,行为树配置导出信息错误`); + } + const childNode = createNodeRecursively(childInfo, nodeMap); + childNodes.push(childNode); + } + + // 创建节点实例 + let btnode: IBTNode; + const metadata = BT.getNodeMetadata(ctor); + if (metadata.type === BT.Type.Action || metadata.type === BT.Type.Condition) { + btnode = new ctor(); + } else if (metadata.type === BT.Type.Decorator) { + btnode = new ctor(childNodes[0]!); + } else { + btnode = new ctor(...childNodes); + } + // 设置节点参数 + for (const key in info.parameters) { + (btnode as any)[key] = info.parameters[key]; + } + return btnode; +} + +export function createBehaviorTree(config: INodeConfig[], entity: T): BehaviorTree { + // 验证配置 + if (!config || !Array.isArray(config) || config.length === 0) { + throw new Error("Config is empty or invalid"); + } + + // 创建配置映射表 + const nodeMap = new Map(); + for (const info of config) { + nodeMap.set(info.id, info); + } + return new BehaviorTree(entity, createNodeRecursively(config[0]!, nodeMap)); +} \ No newline at end of file diff --git a/src/behaviortree/header.ts b/src/behaviortree/header.ts index e85479a..d17cc92 100644 --- a/src/behaviortree/header.ts +++ b/src/behaviortree/header.ts @@ -2,4 +2,4 @@ export enum Status { FAILURE, SUCCESS, RUNNING, -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index cae75a6..67c8290 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,11 +2,14 @@ /** 行为树 */ 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/Decorator"; -export { Status } from "./behaviortree/header"; - +export { createBehaviorTree } from "./behaviortree/Factory"; +// 导出装饰器内容 +import { BT } from "./behaviortree/BT"; +export const { ActionNode, ConditionNode, CompositeNode, DecoratorNode, prop } = BT; \ No newline at end of file diff --git a/test/simple-runner.ts b/test/simple-runner.ts deleted file mode 100644 index a2296f1..0000000 --- a/test/simple-runner.ts +++ /dev/null @@ -1,581 +0,0 @@ -/** - * 简单的测试运行器 - 无需外部依赖 - * 验证所有行为树功能 - */ - -import { BehaviorTree } from '../src/behaviortree/BehaviorTree'; -import { Blackboard, globalBlackboard } from '../src/behaviortree/Blackboard'; -import { Status } from '../src/behaviortree/header'; - -// 导入所有节点类型 -import { Action, WaitTicks, WaitTime } from '../src/behaviortree/BTNode/Action'; -import { Condition } from '../src/behaviortree/BTNode/Condition'; -import { - Selector, Sequence, Parallel, ParallelAnySuccess, - MemSelector, MemSequence, RandomSelector -} from '../src/behaviortree/BTNode/Composite'; -import { - Inverter, LimitTime, LimitTicks, Repeat, - RepeatUntilFailure, RepeatUntilSuccess -} from '../src/behaviortree/BTNode/Decorator'; - -interface TestEntity { - name: string; - value: number; -} - -// 简单断言函数 -function assert(condition: boolean, message: string) { - if (!condition) { - console.error(`❌ FAIL: ${message}`); - process.exit(1); - } -} - -function assertEqual(actual: T, expected: T, message: string) { - if (actual !== expected) { - console.error(`❌ FAIL: ${message}`); - console.error(` Expected: ${expected}`); - console.error(` Actual: ${actual}`); - process.exit(1); - } -} - -function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -// 测试计数器 -let totalTests = 0; -let passedTests = 0; - -function runTest(testName: string, testFn: () => void | Promise) { - totalTests++; - try { - const result = testFn(); - if (result instanceof Promise) { - return result.then(() => { - console.log(`✅ ${testName}`); - passedTests++; - }).catch(error => { - console.error(`❌ FAIL: ${testName} - ${error.message}`); - process.exit(1); - }); - } else { - console.log(`✅ ${testName}`); - passedTests++; - return Promise.resolve(); - } - } catch (error: any) { - console.error(`❌ FAIL: ${testName} - ${error.message}`); - process.exit(1); - } -} - -async function main() { - console.log('🚀 开始行为树全面功能测试...\n'); - - const testEntity: TestEntity = { name: 'test', value: 0 }; - - // 重置函数 - function reset() { - testEntity.name = 'test'; - testEntity.value = 0; - globalBlackboard.clean(); - } - - console.log('📋 1. Action节点测试'); - - runTest('Action节点 - 成功执行', () => { - reset(); - let executed = false; - const action = new Action(() => { - executed = true; - return Status.SUCCESS; - }); - const tree = new BehaviorTree(testEntity, action); - const result = tree.tick(); - assertEqual(result, Status.SUCCESS, 'Action应该返回SUCCESS'); - assert(executed, 'Action函数应该被执行'); - }); - - runTest('Action节点 - 失败执行', () => { - reset(); - let executed = false; - const action = new Action(() => { - executed = true; - return Status.FAILURE; - }); - const tree = new BehaviorTree(testEntity, action); - const result = tree.tick(); - assertEqual(result, Status.FAILURE, 'Action应该返回FAILURE'); - assert(executed, 'Action函数应该被执行'); - }); - - runTest('WaitTicks节点 - 等待指定次数', () => { - reset(); - const waitNode = new WaitTicks(3); - const tree = new BehaviorTree(testEntity, waitNode); - - assertEqual(tree.tick(), Status.RUNNING, '第1次tick应该返回RUNNING'); - assertEqual(tree.tick(), Status.RUNNING, '第2次tick应该返回RUNNING'); - assertEqual(tree.tick(), Status.SUCCESS, '第3次tick应该返回SUCCESS'); - }); - - await runTest('WaitTime节点 - 时间等待', async () => { - reset(); - const waitNode = new WaitTime(0.1); // 100ms - const tree = new BehaviorTree(testEntity, waitNode); - - assertEqual(tree.tick(), Status.RUNNING, '时间未到应该返回RUNNING'); - - await sleep(150); - assertEqual(tree.tick(), Status.SUCCESS, '时间到了应该返回SUCCESS'); - }); - - console.log('\n📋 2. Condition节点测试'); - - runTest('Condition节点 - 条件为真', () => { - reset(); - const condition = new Condition(() => true); - const tree = new BehaviorTree(testEntity, condition); - assertEqual(tree.tick(), Status.SUCCESS, 'true条件应该返回SUCCESS'); - }); - - runTest('Condition节点 - 条件为假', () => { - reset(); - const condition = new Condition(() => false); - const tree = new BehaviorTree(testEntity, condition); - assertEqual(tree.tick(), Status.FAILURE, 'false条件应该返回FAILURE'); - }); - - console.log('\n📋 3. Composite节点测试'); - - runTest('Selector节点 - 第一个成功', () => { - reset(); - const selector = new Selector( - new Action(() => Status.SUCCESS), - new Action(() => Status.FAILURE) - ); - const tree = new BehaviorTree(testEntity, selector); - assertEqual(tree.tick(), Status.SUCCESS, 'Selector第一个成功应该返回SUCCESS'); - }); - - runTest('Selector节点 - 全部失败', () => { - reset(); - const selector = new Selector( - new Action(() => Status.FAILURE), - new Action(() => Status.FAILURE) - ); - const tree = new BehaviorTree(testEntity, selector); - assertEqual(tree.tick(), Status.FAILURE, 'Selector全部失败应该返回FAILURE'); - }); - - runTest('Sequence节点 - 全部成功', () => { - reset(); - const sequence = new Sequence( - new Action(() => Status.SUCCESS), - new Action(() => Status.SUCCESS) - ); - const tree = new BehaviorTree(testEntity, sequence); - assertEqual(tree.tick(), Status.SUCCESS, 'Sequence全部成功应该返回SUCCESS'); - }); - - runTest('Sequence节点 - 中途失败', () => { - reset(); - let firstExecuted = false; - let secondExecuted = false; - - const sequence = new Sequence( - new Action(() => { - firstExecuted = true; - return Status.SUCCESS; - }), - new Action(() => { - secondExecuted = true; - return Status.FAILURE; - }) - ); - const tree = new BehaviorTree(testEntity, sequence); - - assertEqual(tree.tick(), Status.FAILURE, 'Sequence中途失败应该返回FAILURE'); - assert(firstExecuted, '第一个Action应该被执行'); - assert(secondExecuted, '第二个Action也应该被执行'); - }); - - runTest('MemSelector节点 - 记忆运行状态', () => { - reset(); - let firstCallCount = 0; - let secondCallCount = 0; - - const memSelector = new MemSelector( - new Action(() => { - firstCallCount++; - return Status.RUNNING; - }), - new Action(() => { - secondCallCount++; - return Status.SUCCESS; - }) - ); - const tree = new BehaviorTree(testEntity, memSelector); - - assertEqual(tree.tick(), Status.RUNNING, '第一次tick应该返回RUNNING'); - assertEqual(firstCallCount, 1, '第一个Action应该执行1次'); - assertEqual(secondCallCount, 0, '第二个Action不应该执行'); - - assertEqual(tree.tick(), Status.RUNNING, '第二次tick应该从第一个节点继续'); - assertEqual(firstCallCount, 2, '第一个Action应该执行2次'); - assertEqual(secondCallCount, 0, '第二个Action仍不应该执行'); - }); - - runTest('Parallel节点 - FAILURE优先级最高', () => { - reset(); - const parallel = new Parallel( - new Action(() => Status.SUCCESS), - new Action(() => Status.FAILURE), - new Action(() => Status.RUNNING) - ); - const tree = new BehaviorTree(testEntity, parallel); - assertEqual(tree.tick(), Status.FAILURE, 'Parallel有FAILURE应该返回FAILURE'); - }); - - runTest('Parallel节点 - RUNNING次优先级', () => { - reset(); - const parallel = new Parallel( - new Action(() => Status.SUCCESS), - new Action(() => Status.RUNNING), - new Action(() => Status.SUCCESS) - ); - const tree = new BehaviorTree(testEntity, parallel); - assertEqual(tree.tick(), Status.RUNNING, 'Parallel有RUNNING无FAILURE应该返回RUNNING'); - }); - - runTest('ParallelAnySuccess节点 - SUCCESS优先级最高', () => { - reset(); - const parallel = new ParallelAnySuccess( - new Action(() => Status.SUCCESS), - new Action(() => Status.FAILURE), - new Action(() => Status.RUNNING) - ); - const tree = new BehaviorTree(testEntity, parallel); - assertEqual(tree.tick(), Status.SUCCESS, 'ParallelAnySuccess有SUCCESS应该返回SUCCESS'); - }); - - console.log('\n📋 4. Decorator节点测试'); - - runTest('Inverter节点 - 反转SUCCESS', () => { - reset(); - const inverter = new Inverter(new Action(() => Status.SUCCESS)); - const tree = new BehaviorTree(testEntity, inverter); - assertEqual(tree.tick(), Status.FAILURE, 'Inverter应该将SUCCESS反转为FAILURE'); - }); - - runTest('Inverter节点 - 反转FAILURE', () => { - reset(); - const inverter = new Inverter(new Action(() => Status.FAILURE)); - const tree = new BehaviorTree(testEntity, inverter); - assertEqual(tree.tick(), Status.SUCCESS, 'Inverter应该将FAILURE反转为SUCCESS'); - }); - - runTest('LimitTicks节点 - 次数限制内成功', () => { - reset(); - let executeCount = 0; - const limitTicks = new LimitTicks( - new Action(() => { - executeCount++; - return Status.SUCCESS; - }), - 3 - ); - const tree = new BehaviorTree(testEntity, limitTicks); - - assertEqual(tree.tick(), Status.SUCCESS, '限制内应该返回SUCCESS'); - assertEqual(executeCount, 1, '应该执行1次'); - }); - - runTest('LimitTicks节点 - 超过次数限制', () => { - reset(); - let executeCount = 0; - const limitTicks = new LimitTicks( - new Action(() => { - executeCount++; - return Status.RUNNING; - }), - 2 - ); - const tree = new BehaviorTree(testEntity, limitTicks); - - assertEqual(tree.tick(), Status.RUNNING, '第1次应该返回RUNNING'); - assertEqual(tree.tick(), Status.RUNNING, '第2次应该返回RUNNING'); - assertEqual(tree.tick(), Status.FAILURE, '第3次超限应该返回FAILURE'); - assertEqual(executeCount, 2, '应该只执行2次'); - }); - - runTest('Repeat节点 - 指定次数重复', () => { - reset(); - let executeCount = 0; - const repeat = new Repeat( - new Action(() => { - executeCount++; - return Status.SUCCESS; - }), - 3 - ); - const tree = new BehaviorTree(testEntity, repeat); - - assertEqual(tree.tick(), Status.RUNNING, '第1次应该返回RUNNING'); - assertEqual(tree.tick(), Status.RUNNING, '第2次应该返回RUNNING'); - assertEqual(tree.tick(), Status.SUCCESS, '第3次应该完成返回SUCCESS'); - assertEqual(executeCount, 3, '应该执行3次'); - }); - - runTest('RepeatUntilSuccess节点 - 直到成功', () => { - reset(); - let attempts = 0; - const repeatUntilSuccess = new RepeatUntilSuccess( - new Action(() => { - attempts++; - return attempts >= 3 ? Status.SUCCESS : Status.FAILURE; - }), - 5 - ); - const tree = new BehaviorTree(testEntity, repeatUntilSuccess); - - assertEqual(tree.tick(), Status.RUNNING, '第1次失败应该返回RUNNING'); - assertEqual(tree.tick(), Status.RUNNING, '第2次失败应该返回RUNNING'); - assertEqual(tree.tick(), Status.SUCCESS, '第3次成功应该返回SUCCESS'); - assertEqual(attempts, 3, '应该尝试3次'); - }); - - console.log('\n📋 5. 黑板数据存储与隔离测试'); - - runTest('黑板基本读写功能', () => { - reset(); - const action = new Action((node) => { - node.set('test_key', 'test_value'); - const value = node.get('test_key'); - assertEqual(value, 'test_value', '应该能读取设置的值'); - return Status.SUCCESS; - }); - const tree = new BehaviorTree(testEntity, action); - tree.tick(); - }); - - runTest('黑板层级数据隔离 - 树级黑板', () => { - reset(); - let rootValue: string | undefined; - let localValue: string | undefined; - - const action = new Action((node) => { - node.setRoot('root_key', 'root_value'); - node.set('local_key', 'local_value'); - - rootValue = node.getRoot('root_key'); - localValue = node.get('local_key'); - - return Status.SUCCESS; - }); - const tree = new BehaviorTree(testEntity, action); - tree.tick(); - - assertEqual(rootValue, 'root_value', '应该能读取树级数据'); - assertEqual(localValue, 'local_value', '应该能读取本地数据'); - assertEqual(tree.blackboard.get('root_key'), 'root_value', '树级黑板应该有数据'); - }); - - runTest('黑板层级数据隔离 - 全局黑板', () => { - reset(); - const action1 = new Action((node) => { - node.setGlobal('global_key', 'global_value'); - return Status.SUCCESS; - }); - - const action2 = new Action((node) => { - const value = node.getGlobal('global_key'); - assertEqual(value, 'global_value', '应该能读取全局数据'); - return Status.SUCCESS; - }); - - const tree1 = new BehaviorTree(testEntity, action1); - const tree2 = new BehaviorTree({ name: 'test2', value: 1 }, action2); - - tree1.tick(); - tree2.tick(); - }); - - runTest('实体数据关联', () => { - reset(); - const action = new Action((node) => { - const entity = node.getEntity(); - assertEqual(entity.name, 'test', '实体name应该正确'); - assertEqual(entity.value, 0, '实体value应该正确'); - return Status.SUCCESS; - }); - const tree = new BehaviorTree(testEntity, action); - tree.tick(); - }); - - console.log('\n📋 6. 行为树重置逻辑测试'); - - runTest('行为树重置清空黑板数据', () => { - reset(); - const action = new Action((node) => { - node.set('test_key', 'test_value'); - node.setRoot('root_key', 'root_value'); - return Status.SUCCESS; - }); - - const tree = new BehaviorTree(testEntity, action); - tree.tick(); - - // 确认数据存在 - assertEqual(tree.blackboard.get('test_key'), 'test_value', '数据应该存在'); - assertEqual(tree.blackboard.get('root_key'), 'root_value', '根数据应该存在'); - - // 重置 - tree.reset(); - - // 确认数据被清空 - assertEqual(tree.blackboard.get('test_key'), undefined, '数据应该被清空'); - assertEqual(tree.blackboard.get('root_key'), undefined, '根数据应该被清空'); - }); - - runTest('Memory节点重置后内存索引重置', () => { - reset(); - let firstCallCount = 0; - let secondCallCount = 0; - - const memSequence = new MemSequence( - new Action(() => { - firstCallCount++; - return Status.SUCCESS; - }), - new Action(() => { - secondCallCount++; - return Status.RUNNING; - }) - ); - - const tree = new BehaviorTree(testEntity, memSequence); - - // 第一次运行 - assertEqual(tree.tick(), Status.RUNNING, '应该返回RUNNING'); - assertEqual(firstCallCount, 1, '第一个节点应该执行1次'); - assertEqual(secondCallCount, 1, '第二个节点应该执行1次'); - - // 第二次运行,应该从第二个节点继续 - assertEqual(tree.tick(), Status.RUNNING, '应该继续返回RUNNING'); - assertEqual(firstCallCount, 1, '第一个节点不应该再执行'); - assertEqual(secondCallCount, 2, '第二个节点应该执行2次'); - - // 重置 - tree.reset(); - - // 重置后运行,应该从第一个节点重新开始 - assertEqual(tree.tick(), Status.RUNNING, '重置后应该返回RUNNING'); - assertEqual(firstCallCount, 2, '第一个节点应该重新执行'); - assertEqual(secondCallCount, 3, '第二个节点应该再次执行'); - }); - - console.log('\n📋 7. 其他关键功能测试'); - - runTest('复杂行为树结构测试', () => { - reset(); - // 构建复杂嵌套结构 - const complexTree = new Selector( - new Sequence( - new Condition(() => false), // 导致Sequence失败 - new Action(() => Status.SUCCESS) - ), - new MemSelector( - new Inverter(new Action(() => Status.SUCCESS)), // 反转为FAILURE - new Repeat( - new Action(() => Status.SUCCESS), - 2 - ) - ) - ); - - const tree = new BehaviorTree(testEntity, complexTree); - - assertEqual(tree.tick(), Status.RUNNING, '第一次应该返回RUNNING(Repeat第1次)'); - assertEqual(tree.tick(), Status.SUCCESS, '第二次应该返回SUCCESS(Repeat完成)'); - }); - - runTest('边界情况 - 空子节点', () => { - reset(); - const emptySelector = new Selector(); - const emptySequence = new Sequence(); - const emptyParallel = new Parallel(); - - const tree1 = new BehaviorTree(testEntity, emptySelector); - const tree2 = new BehaviorTree(testEntity, emptySequence); - const tree3 = new BehaviorTree(testEntity, emptyParallel); - - assertEqual(tree1.tick(), Status.FAILURE, '空Selector应该返回FAILURE'); - assertEqual(tree2.tick(), Status.SUCCESS, '空Sequence应该返回SUCCESS'); - assertEqual(tree3.tick(), Status.SUCCESS, '空Parallel应该返回SUCCESS'); - }); - - runTest('黑板数据类型测试', () => { - reset(); - const action = new Action((node) => { - // 测试各种数据类型 - node.set('string', 'hello'); - node.set('number', 42); - node.set('boolean', true); - node.set('array', [1, 2, 3]); - node.set('object', { a: 1, b: 'test' }); - node.set('null', null); - - assertEqual(node.get('string'), 'hello', 'string类型'); - assertEqual(node.get('number'), 42, 'number类型'); - assertEqual(node.get('boolean'), true, 'boolean类型'); - - const arr = node.get('array'); - assert(Array.isArray(arr) && arr.length === 3, 'array类型'); - - const obj = node.get('object'); - assertEqual(obj.a, 1, 'object属性a'); - assertEqual(obj.b, 'test', 'object属性b'); - - assertEqual(node.get('null'), null, 'null值'); - - return Status.SUCCESS; - }); - - const tree = new BehaviorTree(testEntity, action); - tree.tick(); - }); - - console.log(`\n🎉 测试完成! 通过 ${passedTests}/${totalTests} 个测试`); - - if (passedTests === totalTests) { - console.log('✅ 所有测试通过!'); - - console.log('\n📊 测试覆盖总结:'); - console.log('✅ Action节点: 4个测试 (Action, WaitTicks, WaitTime)'); - console.log('✅ Condition节点: 2个测试 (true/false条件)'); - console.log('✅ Composite节点: 7个测试 (Selector, Sequence, MemSelector, Parallel等)'); - console.log('✅ Decorator节点: 6个测试 (Inverter, LimitTicks, Repeat等)'); - console.log('✅ 黑板功能: 4个测试 (数据存储、隔离、实体关联)'); - console.log('✅ 重置逻辑: 2个测试 (数据清空、状态重置)'); - console.log('✅ 其他功能: 3个测试 (复杂结构、边界情况、数据类型)'); - - console.log('\n🔍 验证的核心功能:'); - console.log('• 所有节点类型的正确行为'); - console.log('• 黑板三级数据隔离 (本地/树级/全局)'); - console.log('• Memory节点的状态记忆'); - console.log('• 行为树重置的完整清理'); - console.log('• 复杂嵌套结构的正确执行'); - console.log('• 各种数据类型的存储支持'); - - process.exit(0); - } else { - console.log('❌ 有测试失败!'); - process.exit(1); - } -} - -main().catch(console.error); \ No newline at end of file