241 lines
5.8 KiB
Markdown
241 lines
5.8 KiB
Markdown
|
|
# 编辑器工作流
|
|||
|
|
|
|||
|
|
本教程介绍如何使用行为树编辑器创建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)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 在游戏中加载
|
|||
|
|
|
|||
|
|
### 加载JSON资产
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { Core, Scene } from '@esengine/ecs-framework';
|
|||
|
|
import {
|
|||
|
|
BehaviorTreePlugin,
|
|||
|
|
BehaviorTreeAssetSerializer,
|
|||
|
|
BehaviorTreeAssetLoader,
|
|||
|
|
BehaviorTreeStarter
|
|||
|
|
} from '@esengine/behavior-tree';
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
Core.create();
|
|||
|
|
const plugin = new BehaviorTreePlugin();
|
|||
|
|
await Core.installPlugin(plugin);
|
|||
|
|
|
|||
|
|
const scene = new Scene();
|
|||
|
|
plugin.setupScene(scene);
|
|||
|
|
Core.setScene(scene);
|
|||
|
|
|
|||
|
|
// 加载行为树
|
|||
|
|
const jsonString = await loadJsonFromFile('enemy-ai.btree.json');
|
|||
|
|
const asset = BehaviorTreeAssetSerializer.deserialize(jsonString);
|
|||
|
|
const aiEntity = BehaviorTreeAssetLoader.instantiate(asset, scene);
|
|||
|
|
|
|||
|
|
// 设置黑板初始值
|
|||
|
|
const blackboard = aiEntity.getComponent(BlackboardComponent);
|
|||
|
|
blackboard?.setValue('health', 100);
|
|||
|
|
blackboard?.setValue('moveSpeed', 5.0);
|
|||
|
|
|
|||
|
|
// 启动AI
|
|||
|
|
BehaviorTreeStarter.start(aiEntity);
|
|||
|
|
|
|||
|
|
// 游戏循环
|
|||
|
|
setInterval(() => {
|
|||
|
|
Core.update(0.016); // 60 FPS
|
|||
|
|
}, 16);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 实现自定义动作
|
|||
|
|
|
|||
|
|
编辑器中的ExecuteAction节点需要在代码中提供实际逻辑。有两种方式:
|
|||
|
|
|
|||
|
|
### 方式1:通过事件系统(推荐)
|
|||
|
|
|
|||
|
|
在Action节点中触发事件,在游戏代码中监听:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 在编辑器的ExecuteAction节点中
|
|||
|
|
entity.scene?.eventSystem.emit('ai:attack', {
|
|||
|
|
attacker: entity,
|
|||
|
|
target: blackboard?.getValue('target')
|
|||
|
|
});
|
|||
|
|
return TaskStatus.Success;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 在游戏代码中监听
|
|||
|
|
Core.scene.eventSystem.on('ai:attack', (data) => {
|
|||
|
|
const { attacker, target } = data;
|
|||
|
|
// 执行实际的攻击逻辑
|
|||
|
|
performAttack(attacker, target);
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方式2:创建自定义组件
|
|||
|
|
|
|||
|
|
创建专用的Action组件(详见[自定义动作](./custom-actions.md)):
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { Component, ECSComponent, Entity } from '@esengine/ecs-framework';
|
|||
|
|
import { BehaviorNode, BehaviorProperty, NodeType, TaskStatus } from '@esengine/behavior-tree';
|
|||
|
|
|
|||
|
|
@BehaviorNode({
|
|||
|
|
displayName: '攻击目标',
|
|||
|
|
category: '战斗',
|
|||
|
|
type: NodeType.Action,
|
|||
|
|
description: '对目标造成伤害'
|
|||
|
|
})
|
|||
|
|
@ECSComponent('AttackAction')
|
|||
|
|
export class AttackAction extends Component {
|
|||
|
|
@BehaviorProperty({
|
|||
|
|
label: '伤害值',
|
|||
|
|
type: 'number'
|
|||
|
|
})
|
|||
|
|
damage: number = 10;
|
|||
|
|
|
|||
|
|
execute(entity: Entity, blackboard?: BlackboardComponent): TaskStatus {
|
|||
|
|
const target = blackboard?.getValue('target');
|
|||
|
|
if (!target) return TaskStatus.Failure;
|
|||
|
|
|
|||
|
|
// 执行攻击逻辑
|
|||
|
|
performAttack(entity, target, this.damage);
|
|||
|
|
return TaskStatus.Success;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 调试技巧
|
|||
|
|
|
|||
|
|
### 1. 使用日志
|
|||
|
|
|
|||
|
|
在编辑器中添加Log节点输出调试信息:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
.log('进入战斗分支', 'info')
|
|||
|
|
.action('Attack', (entity, blackboard) => {
|
|||
|
|
console.log('目标:', blackboard?.getValue('target'));
|
|||
|
|
return TaskStatus.Success;
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 监控黑板
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const blackboard = aiEntity.getComponent(BlackboardComponent);
|
|||
|
|
console.log('黑板状态:', blackboard?.getAllVariables());
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 检查节点状态
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const node = aiEntity.getComponent(BehaviorTreeNode);
|
|||
|
|
console.log('节点状态:', node?.status);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 完整示例
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { Core, Scene, Entity } from '@esengine/ecs-framework';
|
|||
|
|
import {
|
|||
|
|
BehaviorTreePlugin,
|
|||
|
|
BehaviorTreeBuilder,
|
|||
|
|
BehaviorTreeStarter,
|
|||
|
|
BlackboardValueType,
|
|||
|
|
TaskStatus
|
|||
|
|
} from '@esengine/behavior-tree';
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
Core.create();
|
|||
|
|
const plugin = new BehaviorTreePlugin();
|
|||
|
|
await Core.installPlugin(plugin);
|
|||
|
|
|
|||
|
|
const scene = new Scene();
|
|||
|
|
plugin.setupScene(scene);
|
|||
|
|
Core.setScene(scene);
|
|||
|
|
|
|||
|
|
// 直接用代码构建(不用编辑器)
|
|||
|
|
const aiEntity = BehaviorTreeBuilder.create(scene, 'EnemyAI')
|
|||
|
|
.blackboard()
|
|||
|
|
.defineVariable('health', BlackboardValueType.Number, 100)
|
|||
|
|
.defineVariable('hasTarget', BlackboardValueType.Boolean, false)
|
|||
|
|
.endBlackboard()
|
|||
|
|
.selector('Root')
|
|||
|
|
.sequence('Combat')
|
|||
|
|
.condition((e, bb) => bb?.getValue('hasTarget') === true, 'CheckTarget')
|
|||
|
|
.action('Attack', (e, bb) => {
|
|||
|
|
console.log('攻击!');
|
|||
|
|
return TaskStatus.Success;
|
|||
|
|
})
|
|||
|
|
.end()
|
|||
|
|
.action('Idle', () => {
|
|||
|
|
console.log('空闲');
|
|||
|
|
return TaskStatus.Success;
|
|||
|
|
})
|
|||
|
|
.end()
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
BehaviorTreeStarter.start(aiEntity);
|
|||
|
|
|
|||
|
|
// 游戏循环
|
|||
|
|
setInterval(() => {
|
|||
|
|
Core.update(0.016);
|
|||
|
|
}, 16);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 下一步
|
|||
|
|
|
|||
|
|
- 查看[自定义动作](./custom-actions.md)学习如何创建专用的Action组件
|
|||
|
|
- 查看[高级用法](./advanced-usage.md)了解子树、异步操作等高级特性
|
|||
|
|
- 查看[最佳实践](./best-practices.md)优化你的AI设计
|