Skip to content

编辑器工作流

本教程介绍如何使用行为树编辑器创建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组件(详见自定义动作):

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);

下一步

Released under the MIT License.