mirror of
https://github.com/gongxh0901/kunpocc-behaviortree.git
synced 2025-12-26 16:48:56 +00:00
fix: 修复黑板数据清理不彻底bug
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kunpocc-behaviortree",
|
"name": "kunpocc-behaviortree",
|
||||||
"version": "0.0.5",
|
"version": "0.0.6",
|
||||||
"description": "行为树",
|
"description": "行为树",
|
||||||
"main": "./dist/kunpocc-behaviortree.cjs",
|
"main": "./dist/kunpocc-behaviortree.cjs",
|
||||||
"module": "./dist/kunpocc-behaviortree.mjs",
|
"module": "./dist/kunpocc-behaviortree.mjs",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dts from 'rollup-plugin-dts';
|
|||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
// 生成未压缩的 JS 文件
|
// 生成未压缩的 JS 文件
|
||||||
input: 'src/kunpocc-behaviortree.ts',
|
input: 'src/index.ts',
|
||||||
external: ['cc', 'fairygui-cc'],
|
external: ['cc', 'fairygui-cc'],
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
@@ -38,7 +38,7 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 生成压缩的 JS 文件
|
// 生成压缩的 JS 文件
|
||||||
input: 'src/kunpocc-behaviortree.ts',
|
input: 'src/index.ts',
|
||||||
external: ['cc', 'fairygui-cc'],
|
external: ['cc', 'fairygui-cc'],
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
@@ -72,7 +72,7 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 生成声明文件的配置
|
// 生成声明文件的配置
|
||||||
input: 'src/kunpocc-behaviortree.ts',
|
input: 'src/index.ts',
|
||||||
output: {
|
output: {
|
||||||
file: 'dist/kunpocc-behaviortree.d.ts',
|
file: 'dist/kunpocc-behaviortree.d.ts',
|
||||||
format: 'es'
|
format: 'es'
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* 专门用于存储和管理行为树执行过程中的共享数据
|
* 专门用于存储和管理行为树执行过程中的共享数据
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IBTNode } from "../kunpocc-behaviortree";
|
import { IBTNode } from "./BTNode/BTNode";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 黑板数据接口
|
* 黑板数据接口
|
||||||
@@ -81,9 +81,14 @@ export class Blackboard implements IBlackboard {
|
|||||||
public clean(): void {
|
public clean(): void {
|
||||||
// 清空当前黑板数据
|
// 清空当前黑板数据
|
||||||
this._data.clear();
|
this._data.clear();
|
||||||
|
|
||||||
// 重置运行状态
|
// 重置运行状态
|
||||||
this.openNodes = new WeakMap<IBTNode, boolean>();
|
this.openNodes = new WeakMap<IBTNode, boolean>();
|
||||||
|
|
||||||
|
// 递归清理所有子黑板
|
||||||
|
for (const child of this.children) {
|
||||||
|
child.clean();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
581
test/simple-runner.ts
Normal file
581
test/simple-runner.ts
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
/**
|
||||||
|
* 简单的测试运行器 - 无需外部依赖
|
||||||
|
* 验证所有行为树功能
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<T>(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<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试计数器
|
||||||
|
let totalTests = 0;
|
||||||
|
let passedTests = 0;
|
||||||
|
|
||||||
|
function runTest(testName: string, testFn: () => void | Promise<void>) {
|
||||||
|
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<string>('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<string>('root_key');
|
||||||
|
localValue = node.get<string>('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<string>('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<string>('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<TestEntity>();
|
||||||
|
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<string>('test_key'), 'test_value', '数据应该存在');
|
||||||
|
assertEqual(tree.blackboard.get<string>('root_key'), 'root_value', '根数据应该存在');
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
tree.reset();
|
||||||
|
|
||||||
|
// 确认数据被清空
|
||||||
|
assertEqual(tree.blackboard.get<string>('test_key'), undefined, '数据应该被清空');
|
||||||
|
assertEqual(tree.blackboard.get<string>('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>('string'), 'hello', 'string类型');
|
||||||
|
assertEqual(node.get<number>('number'), 42, 'number类型');
|
||||||
|
assertEqual(node.get<boolean>('boolean'), true, 'boolean类型');
|
||||||
|
|
||||||
|
const arr = node.get<number[]>('array');
|
||||||
|
assert(Array.isArray(arr) && arr.length === 3, 'array类型');
|
||||||
|
|
||||||
|
const obj = node.get<any>('object');
|
||||||
|
assertEqual(obj.a, 1, 'object属性a');
|
||||||
|
assertEqual(obj.b, 'test', 'object属性b');
|
||||||
|
|
||||||
|
assertEqual(node.get<any>('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);
|
||||||
Reference in New Issue
Block a user