Skip to content

核心概念

本文介绍行为树系统的核心概念和工作原理。

什么是行为树?

行为树(Behavior Tree)是一种用于控制AI和自动化系统的决策结构。它通过树状层次结构组织任务,从根节点开始逐层执行,直到找到合适的行为。

与状态机的对比

传统状态机:

  • 基于状态和转换
  • 状态之间的转换复杂
  • 难以扩展和维护
  • 不便于复用

行为树:

  • 基于任务和层次结构
  • 模块化、易于复用
  • 可视化编辑
  • 灵活的决策逻辑

树结构

行为树由节点组成,形成树状结构:

Root (根节点)
├── Selector (选择器)
│   ├── Sequence (序列)
│   │   ├── Condition (条件)
│   │   └── Action (动作)
│   └── Action (动作)
└── Sequence (序列)
    ├── Action (动作)
    └── Wait (等待)

每个节点都有:

  • 父节点(除了根节点)
  • 零个或多个子节点
  • 执行状态
  • 返回结果

节点类型

复合节点(Composite)

复合节点有多个子节点,按特定规则执行它们。

Selector(选择器)

按顺序尝试执行子节点,直到某个子节点成功。

typescript
.selector('FindFood')
    .action('EatNearbyFood', () => {
        if (hasNearbyFood()) {
            eat();
            return TaskStatus.Success;
        }
        return TaskStatus.Failure;
    })
    .action('SearchFood', () => {
        searchForFood();
        return TaskStatus.Running;
    })
    .action('GiveUp', () => {
        return TaskStatus.Failure;
    })
.end()

执行逻辑:

  1. 尝试第一个子节点
  2. 如果返回Success,选择器成功
  3. 如果返回Failure,尝试下一个子节点
  4. 如果返回Running,选择器返回Running
  5. 所有子节点都失败时,选择器失败

Sequence(序列)

按顺序执行所有子节点,直到某个子节点失败。

typescript
.sequence('AttackSequence')
    .condition((e, bb) => hasTarget())  // 检查是否有目标
    .action('Aim', () => TaskStatus.Success)  // 瞄准
    .action('Fire', () => TaskStatus.Success)  // 开火
.end()

执行逻辑:

  1. 依次执行子节点
  2. 如果子节点返回Failure,序列失败
  3. 如果子节点返回Running,序列返回Running
  4. 如果子节点返回Success,继续下一个子节点
  5. 所有子节点都成功时,序列成功

Parallel(并行)

同时执行多个子节点。

typescript
import { ParallelPolicy } from '@esengine/behavior-tree';

.parallel(ParallelPolicy.RequireAll)  // 所有任务都要成功
    .action('PlayAnimation', () => TaskStatus.Success)
    .action('PlaySound', () => TaskStatus.Success)
    .action('SpawnEffect', () => TaskStatus.Success)
.end()

策略类型:

  • RequireAll:所有子节点都成功才成功
  • RequireOne:任意一个子节点成功就成功

装饰器节点(Decorator)

装饰器节点只有一个子节点,用于修改子节点的行为或结果。

Inverter(反转)

反转子节点的结果:

typescript
.inverter()
    .condition((e, bb) => isEnemyNearby())  // 检查敌人是否附近
.end()
// 如果有敌人返回false,没有敌人返回true

Repeater(重复)

重复执行子节点:

typescript
.repeat(3)  // 重复3次
    .action('Jump', () => TaskStatus.Success)
.end()

Cooldown(冷却)

限制子节点的执行频率:

typescript
.cooldown(5.0)  // 5秒冷却
    .action('UseSpecialAbility', () => {
        console.log('使用特殊技能');
        return TaskStatus.Success;
    })
.end()

Timeout(超时)

限制子节点的执行时间:

typescript
.timeout(10.0)  // 10秒超时
    .action('ComplexTask', () => {
        // 长时间运行的任务
        return TaskStatus.Running;
    })
.end()

叶节点(Leaf)

叶节点没有子节点,执行具体的任务。

Action(动作)

执行具体操作:

typescript
.action('Move', (entity, blackboard, deltaTime) => {
    const target = blackboard?.getValue('targetPosition');

    if (!target) {
        return TaskStatus.Failure;
    }

    // 移动逻辑
    const moved = moveTowards(target, deltaTime);

    if (moved) {
        return TaskStatus.Success;
    }

    return TaskStatus.Running;
})

Condition(条件)

检查条件:

typescript
.condition((entity, blackboard) => {
    const health = blackboard?.getValue('health');
    return health > 50;
}, 'CheckHealthHigh')

Wait(等待)

等待指定时间:

typescript
.wait(2.0)  // 等待2秒

任务状态

每个节点执行后返回以下状态之一:

Success(成功)

任务成功完成。

typescript
.action('CollectCoin', () => {
    coin.collect();
    return TaskStatus.Success;
})

Failure(失败)

任务执行失败。

typescript
.condition((e, bb) => {
    const hasKey = bb?.getValue('hasKey');
    return hasKey ? TaskStatus.Success : TaskStatus.Failure;
})

Running(运行中)

任务需要多帧完成,仍在执行中。

typescript
.action('ChargeLaser', (entity, blackboard, deltaTime) => {
    let chargeTime = blackboard?.getValue('chargeTime') || 0;
    chargeTime += deltaTime;
    blackboard?.setValue('chargeTime', chargeTime);

    if (chargeTime >= 3.0) {
        // 充能完成
        blackboard?.setValue('chargeTime', 0);
        return TaskStatus.Success;
    }

    return TaskStatus.Running;  // 继续充能
})

Invalid(无效)

节点未初始化或已重置。通常不需要手动返回此状态。

黑板系统

黑板(Blackboard)是行为树的数据存储系统,用于在节点之间共享数据。

本地黑板

每个行为树实例都有自己的本地黑板:

typescript
const ai = BehaviorTreeBuilder.create(scene, 'EnemyAI')
    .blackboard()
        .defineVariable('health', BlackboardValueType.Number, 100)
        .defineVariable('target', BlackboardValueType.Object, null)
        .defineVariable('state', BlackboardValueType.String, 'idle')
    .endBlackboard()
    .build();

支持的数据类型

typescript
import { BlackboardValueType } from '@esengine/behavior-tree';

.blackboard()
    .defineVariable('count', BlackboardValueType.Number, 0)
    .defineVariable('name', BlackboardValueType.String, 'Enemy')
    .defineVariable('isActive', BlackboardValueType.Boolean, true)
    .defineVariable('position', BlackboardValueType.Vector2, { x: 0, y: 0 })
    .defineVariable('direction', BlackboardValueType.Vector3, { x: 0, y: 0, z: 0 })
    .defineVariable('data', BlackboardValueType.Object, {})
    .defineVariable('items', BlackboardValueType.Array, [])
.endBlackboard()

读写变量

typescript
.action('UseBlackboard', (entity, blackboard) => {
    // 读取变量
    const health = blackboard?.getValue('health');
    const target = blackboard?.getValue('target');

    // 写入变量
    blackboard?.setValue('health', health - 10);
    blackboard?.setValue('lastAttackTime', Date.now());

    // 检查变量是否存在
    if (blackboard?.hasVariable('powerup')) {
        const powerup = blackboard.getValue('powerup');
        console.log('已获得强化:', powerup);
    }

    return TaskStatus.Success;
})

全局黑板

所有行为树实例共享的黑板:

typescript
import { GlobalBlackboard } from '@esengine/behavior-tree';

// 设置全局变量
GlobalBlackboard.setValue('gameState', 'playing');
GlobalBlackboard.setValue('difficulty', 5);

// 在任何行为树中访问
.action('CheckGlobalState', () => {
    const gameState = GlobalBlackboard.getValue('gameState');

    if (gameState === 'paused') {
        return TaskStatus.Failure;
    }

    return TaskStatus.Success;
})

执行流程

初始化

typescript
// 1. 初始化Core和场景
Core.create();
const scene = new Scene();
Core.setScene(scene);

// 2. 构建行为树
const ai = BehaviorTreeBuilder.create(scene, 'AI')
    // ... 定义节点
    .build();

// 3. 启动
BehaviorTreeStarter.start(ai);

更新循环

typescript
// 每帧更新
gameLoop(() => {
    const deltaTime = getDeltaTime();
    Core.update(deltaTime);  // Core会自动更新场景和所有行为树
});

执行顺序

1. 从根节点开始
2. 根节点执行其逻辑(通常是Selector或Sequence)
3. 根节点的子节点按顺序执行
4. 每个子节点可能有自己的子节点
5. 叶节点执行具体操作并返回状态
6. 状态向上传播到父节点
7. 父节点根据策略决定如何处理子节点的状态
8. 最终根节点返回整体状态

执行示例

typescript
const tree = BehaviorTreeBuilder.create(scene, 'Example')
    .selector('Root')  // 1. 执行选择器
        .sequence('Branch1')  // 2. 尝试第一个分支
            .condition(() => false)  // 3. 条件失败
        .end()  // 4. 序列失败,选择器继续下一个分支
        .sequence('Branch2')  // 5. 尝试第二个分支
            .condition(() => true)  // 6. 条件成功
            .action(() => TaskStatus.Success)  // 7. 动作成功
        .end()  // 8. 序列成功,选择器成功
    .end()  // 9. 整个树成功
    .build();

执行流程图:

Root(Selector)
  → Branch1(Sequence)
    → Condition: Failure
  → Branch1 fails
  → Branch2(Sequence)
    → Condition: Success
    → Action: Success
  → Branch2 succeeds
→ Root succeeds

ECS集成

本框架的行为树完全基于ECS架构:

节点即实体

每个行为树节点都是一个Entity:

typescript
// 行为树节点在内部被表示为:
const nodeEntity = scene.createEntity('SelectorNode');
nodeEntity.addComponent(new SelectorComponent());
nodeEntity.addComponent(new ParentComponent(parentEntity));

组件存储数据

节点属性存储在组件中:

typescript
// Action节点的数据组件
class ActionComponent extends Component {
    actionFunc: ActionFunction;
    name: string;
}

// Blackboard组件
class BlackboardComponent extends Component {
    private variables: Map<string, any>;
}

系统驱动行为

行为树系统负责更新所有节点:

typescript
class BehaviorTreeSystem extends EntitySystem {
    update() {
        // 更新所有活跃的行为树
        for (const entity of this.entities) {
            const root = entity.getComponent(BehaviorTreeRootComponent);
            if (root && root.isActive) {
                this.updateNode(root.rootEntity);
            }
        }
    }
}

下一步

现在你已经理解了行为树的核心概念,接下来可以:

Released under the MIT License.