2025-10-28 11:45:35 +08:00
|
|
|
|
# 编辑器工作流
|
|
|
|
|
|
|
|
|
|
|
|
本教程介绍如何使用行为树编辑器创建AI,并在游戏中加载使用。
|
|
|
|
|
|
|
|
|
|
|
|
## 完整流程
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
1. 启动编辑器
|
|
|
|
|
|
2. 创建行为树并定义黑板变量
|
|
|
|
|
|
3. 添加和配置节点
|
|
|
|
|
|
4. 导出JSON文件
|
|
|
|
|
|
5. 在游戏中加载并使用
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 使用编辑器创建
|
|
|
|
|
|
|
|
|
|
|
|
### 启动编辑器
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd packages/editor-app
|
|
|
|
|
|
npm run tauri:dev
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 基本操作
|
|
|
|
|
|
|
|
|
|
|
|
1. **创建行为树**:`文件` → `新建项目` → 创建行为树文件
|
|
|
|
|
|
2. **定义黑板变量**:在黑板面板中添加共享变量
|
|
|
|
|
|
3. **添加节点**:从节点面板拖拽到画布
|
|
|
|
|
|
4. **连接节点**:拖拽连接点建立父子关系
|
|
|
|
|
|
5. **配置属性**:选中节点后在属性面板编辑
|
|
|
|
|
|
6. **导出**:`文件` → `导出` → `JSON格式`
|
|
|
|
|
|
|
|
|
|
|
|
### 示例:敌人AI的黑板变量
|
|
|
|
|
|
|
|
|
|
|
|
在编辑器黑板面板中定义:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
health: Number = 100
|
|
|
|
|
|
target: Object = null
|
|
|
|
|
|
moveSpeed: Number = 5.0
|
|
|
|
|
|
attackRange: Number = 2.0
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 示例:行为树结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Root: Selector
|
|
|
|
|
|
├── Combat Sequence
|
|
|
|
|
|
│ ├── CheckHasTarget (Condition)
|
|
|
|
|
|
│ ├── CheckInAttackRange (Condition)
|
|
|
|
|
|
│ └── ExecuteAttack (Action)
|
|
|
|
|
|
├── Patrol Sequence
|
|
|
|
|
|
│ ├── MoveToNextPatrolPoint (Action)
|
|
|
|
|
|
│ └── Wait 2s
|
|
|
|
|
|
└── Idle (Action)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
## 在游戏中使用
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
### 使用Builder API创建
|
|
|
|
|
|
|
|
|
|
|
|
推荐使用Builder API在代码中创建行为树:
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-12-08 21:23:37 +08:00
|
|
|
|
import { Core, Scene } from '@esengine/esengine';
|
2025-10-28 11:45:35 +08:00
|
|
|
|
import {
|
|
|
|
|
|
BehaviorTreePlugin,
|
2025-10-31 17:27:38 +08:00
|
|
|
|
BehaviorTreeBuilder,
|
|
|
|
|
|
BehaviorTreeStarter,
|
|
|
|
|
|
BehaviorTreeRuntimeComponent
|
2025-10-28 11:45:35 +08:00
|
|
|
|
} from '@esengine/behavior-tree';
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
|
Core.create();
|
|
|
|
|
|
const plugin = new BehaviorTreePlugin();
|
|
|
|
|
|
await Core.installPlugin(plugin);
|
|
|
|
|
|
|
|
|
|
|
|
const scene = new Scene();
|
|
|
|
|
|
plugin.setupScene(scene);
|
|
|
|
|
|
Core.setScene(scene);
|
|
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
// 使用Builder创建行为树
|
|
|
|
|
|
const tree = BehaviorTreeBuilder.create('EnemyAI')
|
|
|
|
|
|
.defineBlackboardVariable('health', 100)
|
|
|
|
|
|
.defineBlackboardVariable('target', null)
|
|
|
|
|
|
.defineBlackboardVariable('moveSpeed', 5.0)
|
|
|
|
|
|
.selector('MainBehavior')
|
|
|
|
|
|
.sequence('AttackBranch')
|
|
|
|
|
|
.blackboardExists('target')
|
|
|
|
|
|
.blackboardCompare('health', 30, 'greater')
|
|
|
|
|
|
.log('攻击目标', 'Attack')
|
|
|
|
|
|
.end()
|
|
|
|
|
|
.log('巡逻', 'Patrol')
|
|
|
|
|
|
.end()
|
|
|
|
|
|
.build();
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
// 创建实体并启动行为树
|
|
|
|
|
|
const entity = scene.createEntity('Enemy');
|
|
|
|
|
|
BehaviorTreeStarter.start(entity, tree);
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
// 访问和修改黑板
|
|
|
|
|
|
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
|
|
|
|
|
|
runtime?.setBlackboardValue('target', someTarget);
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 游戏循环
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
Core.update(0.016); // 60 FPS
|
|
|
|
|
|
}, 16);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
## 实现自定义执行器
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
要扩展行为树的功能,需要创建自定义执行器(详见[自定义节点执行器](./custom-actions.md)):
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-10-31 17:27:38 +08:00
|
|
|
|
import {
|
|
|
|
|
|
INodeExecutor,
|
|
|
|
|
|
NodeExecutionContext,
|
|
|
|
|
|
BindingHelper,
|
|
|
|
|
|
NodeExecutorMetadata
|
|
|
|
|
|
} from '@esengine/behavior-tree';
|
|
|
|
|
|
import { TaskStatus, NodeType } from '@esengine/behavior-tree';
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
@NodeExecutorMetadata({
|
|
|
|
|
|
implementationType: 'AttackAction',
|
|
|
|
|
|
nodeType: NodeType.Action,
|
2025-10-28 11:45:35 +08:00
|
|
|
|
displayName: '攻击目标',
|
2025-10-31 17:27:38 +08:00
|
|
|
|
description: '对目标造成伤害',
|
2025-10-28 11:45:35 +08:00
|
|
|
|
category: '战斗',
|
2025-10-31 17:27:38 +08:00
|
|
|
|
configSchema: {
|
|
|
|
|
|
damage: {
|
|
|
|
|
|
type: 'number',
|
|
|
|
|
|
default: 10,
|
|
|
|
|
|
supportBinding: true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-28 11:45:35 +08:00
|
|
|
|
})
|
2025-10-31 17:27:38 +08:00
|
|
|
|
export class AttackAction implements INodeExecutor {
|
|
|
|
|
|
execute(context: NodeExecutionContext): TaskStatus {
|
|
|
|
|
|
const damage = BindingHelper.getValue<number>(context, 'damage', 10);
|
|
|
|
|
|
const target = context.runtime.getBlackboardValue('target');
|
|
|
|
|
|
|
|
|
|
|
|
if (!target) {
|
|
|
|
|
|
return TaskStatus.Failure;
|
|
|
|
|
|
}
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 执行攻击逻辑
|
2025-10-31 17:27:38 +08:00
|
|
|
|
performAttack(context.entity, target, damage);
|
2025-10-28 11:45:35 +08:00
|
|
|
|
return TaskStatus.Success;
|
|
|
|
|
|
}
|
2025-10-31 17:27:38 +08:00
|
|
|
|
|
|
|
|
|
|
reset(context: NodeExecutionContext): void {
|
|
|
|
|
|
// 清理状态
|
|
|
|
|
|
}
|
2025-10-28 11:45:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 调试技巧
|
|
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
### 1. 使用日志节点
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
在行为树中添加Log节点输出调试信息:
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-10-31 17:27:38 +08:00
|
|
|
|
const tree = BehaviorTreeBuilder.create('DebugAI')
|
|
|
|
|
|
.log('开始战斗序列', 'StartCombat')
|
|
|
|
|
|
.sequence('Combat')
|
|
|
|
|
|
.blackboardCompare('health', 0, 'greater')
|
|
|
|
|
|
.log('执行攻击', 'Attack')
|
|
|
|
|
|
.end()
|
|
|
|
|
|
.build();
|
2025-10-28 11:45:35 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
### 2. 监控黑板状态
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-10-31 17:27:38 +08:00
|
|
|
|
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
|
|
|
|
|
|
console.log('黑板变量:', runtime?.getAllBlackboardVariables());
|
|
|
|
|
|
console.log('活动节点:', Array.from(runtime?.activeNodeIds || []));
|
2025-10-28 11:45:35 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
### 3. 在自定义执行器中调试
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-10-31 17:27:38 +08:00
|
|
|
|
export class DebugAction implements INodeExecutor {
|
|
|
|
|
|
execute(context: NodeExecutionContext): TaskStatus {
|
|
|
|
|
|
const { nodeData, runtime, state } = context;
|
|
|
|
|
|
|
|
|
|
|
|
console.group(`[${nodeData.name}]`);
|
|
|
|
|
|
console.log('配置:', nodeData.config);
|
|
|
|
|
|
console.log('状态:', state);
|
|
|
|
|
|
console.log('黑板:', runtime.getAllBlackboardVariables());
|
|
|
|
|
|
console.groupEnd();
|
|
|
|
|
|
|
|
|
|
|
|
return TaskStatus.Success;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-28 11:45:35 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 完整示例
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-12-08 21:23:37 +08:00
|
|
|
|
import { Core, Scene } from '@esengine/esengine';
|
2025-10-28 11:45:35 +08:00
|
|
|
|
import {
|
|
|
|
|
|
BehaviorTreePlugin,
|
|
|
|
|
|
BehaviorTreeBuilder,
|
|
|
|
|
|
BehaviorTreeStarter,
|
2025-10-31 17:27:38 +08:00
|
|
|
|
BehaviorTreeRuntimeComponent
|
2025-10-28 11:45:35 +08:00
|
|
|
|
} from '@esengine/behavior-tree';
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
|
Core.create();
|
|
|
|
|
|
const plugin = new BehaviorTreePlugin();
|
|
|
|
|
|
await Core.installPlugin(plugin);
|
|
|
|
|
|
|
|
|
|
|
|
const scene = new Scene();
|
|
|
|
|
|
plugin.setupScene(scene);
|
|
|
|
|
|
Core.setScene(scene);
|
|
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
// 使用Builder API构建行为树
|
|
|
|
|
|
const tree = BehaviorTreeBuilder.create('EnemyAI')
|
|
|
|
|
|
.defineBlackboardVariable('health', 100)
|
|
|
|
|
|
.defineBlackboardVariable('hasTarget', false)
|
2025-10-28 11:45:35 +08:00
|
|
|
|
.selector('Root')
|
|
|
|
|
|
.sequence('Combat')
|
2025-10-31 17:27:38 +08:00
|
|
|
|
.blackboardCompare('hasTarget', true, 'equals')
|
|
|
|
|
|
.log('攻击玩家', 'Attack')
|
2025-10-28 11:45:35 +08:00
|
|
|
|
.end()
|
2025-10-31 17:27:38 +08:00
|
|
|
|
.log('空闲', 'Idle')
|
2025-10-28 11:45:35 +08:00
|
|
|
|
.end()
|
|
|
|
|
|
.build();
|
|
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
// 创建实体并启动
|
|
|
|
|
|
const entity = scene.createEntity('Enemy');
|
|
|
|
|
|
BehaviorTreeStarter.start(entity, tree);
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟发现目标
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
|
|
|
|
|
|
runtime?.setBlackboardValue('hasTarget', true);
|
|
|
|
|
|
}, 2000);
|
2025-10-28 11:45:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 游戏循环
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
Core.update(0.016);
|
|
|
|
|
|
}, 16);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 下一步
|
|
|
|
|
|
|
2025-10-31 17:27:38 +08:00
|
|
|
|
- 查看[自定义节点执行器](./custom-actions.md)学习如何创建自定义节点
|
|
|
|
|
|
- 查看[高级用法](./advanced-usage.md)了解性能优化等高级特性
|
2025-10-28 11:45:35 +08:00
|
|
|
|
- 查看[最佳实践](./best-practices.md)优化你的AI设计
|