行为树行为树行为树

行为树由一个个节点组成

  • 结构:树状结构
  • 运行流程:从根节点开始自顶向下往下遍历,每经过一个节点就执行节点对应的功能。

我们规定,每个节点都提供自己的excute函数,返还执行失败/成功结果。
然后根据不同节点的执行结果,遍历的路径随之改变,而这个过程中遍历到什么节点就执行excute函数。

主流的行为树实现,将节点主要分为四种类型。
下面列举四种节点类型及其对应excute函数的行为:

  • 控制节点(非叶节点),用于控制如何执行子节点(控制遍历路径的走向)。
  • 条件节点(叶节点),行为是提供条件的判断结果。
  • 行为节点(叶节点):

    行为节点是代表智能体行为的叶节点,其执行函数一般位该节点代表的行为。

    行为节点的类型是比较多的,毕竟一个智能体的行为是多种多样的,而且都得根据自己的智能体模型定制行为节点类型。
    这里列举一些行为:站立,射击,移动,跟随,远离,保持距离....

  • 装饰节点 :行为是修饰(辅助)其他三类节点。例如执行结果取反/并/或,重复执行若干次等辅助修饰节点的作用,均可做成装饰节点。

持续行为

一些行为是可以瞬间执行完的(例如转身?),
而另外一些动作则是执行持续一段时间才能完成的(例如攻击从启动攻击行为到攻击结算要1秒左右的时间)。

因此,这些持续行为节点的excute函数里,应先启动智能体的持续行为,然后挂起该行为树(更通俗地说是暂停行为树),等到持续时间结束才允许退出excute函数并继续遍历该行为树。
为了支持挂起行为树而不影响其他CPU代码执行,我们往往需要利用协程等待该其行为完成而不产生CPU阻塞,而且开销远低于真正的线程。
此外,一般是一个行为树对应维护一个协程。

条件节点:

执行节点不会总是一帆风顺的,有成功也总会有失败的结果。
这就是引入前提条件的作用——
满足前提条件,才能成功执行行为,返还执行成功结果。否则不能执行行为,返还执行失败结果。

但是每个节点的前提总会不同,或有些没有前提(换句话说总是能满足前提)。

一个可行的做法是:让行为节点含有bool函数对象(或函数接口)。这样对于不同的逻辑条件,就可以写成不同的bool函数,绑定给相应的行为节点。

现在比较成熟的做法是把前提条件抽象分离成新的节点类型,称之为条件节点
将其作为叶节点混入行为树,提供条件的判断结果,交给控制节点决策。

它相当模块化,更加方便适用。

这里的Sequence节点是上面控制节点的一种:能够让其所有子节点依次运行,若运行到其中一个子节点失败则不继续往下运行。
这样可以实现出不满足条件则失败的效果。

相比较传统的有限状态机:

  • 易脚本化/可视化的决策逻辑
  • 逻辑和实现的低耦合,可复用的节点
  • 可以迅速而便捷的组织较复杂的行为决策

这里并不是说有限状态机一无所用:

  • 状态机可以搭配行为树:状态机负责智能体的身体状态,行为树则负责智能体的智能决策。这样在行为树做决策前,得考虑状态机的状态。
  • 状态机适用于简单的AI:对于区区需两三个状态的智能,状态机解决绰绰有余。
  • 状态机运行效率略高于行为树:因为状态机的运行总是在当前状态开始,而行为树的运行总在根开始,这样就额外多了一些要遍历的节点(也就多了一些运行开销)。

在《杀手:赦免》的人群系统里,人群的状态机AI只有简单的3种状态,由于人群的智能体数量较多,若采取行为树AI,则会大大影响性能。

代码示例:

enum Status {
    SUCCESS,
    FAILURE,
    RUNNING
}

// 基础节点类
abstract class Node {
    abstract execute(): Status;
}

// 非叶节点类,用于容纳子节点
abstract class CompositeNode extends Node {
    protected children: Node[] = [];

    addChild(child: Node): void {
        this.children.push(child);
    }
}

// 选择器节点:依次执行子节点,直到其中一个成功为止
class SelectorNode extends CompositeNode {
    execute(): Status {
        for (const child of this.children) {
            const status = child.execute();
            if (status === Status.SUCCESS) {
                return Status.SUCCESS;
            }
        }
        return Status.FAILURE;
    }
}

// 序列节点:依次执行子节点,直到其中一个失败为止
class SequenceNode extends CompositeNode {
    execute(): Status {
        for (const child of this.children) {
            const status = child.execute();
            if (status === Status.FAILURE) {
                return Status.FAILURE;
            }
        }
        return Status.SUCCESS;
    }
}

// 条件节点:判断某个条件是否满足,并返回结果
abstract class ConditionNode extends Node {
    abstract check(): boolean;

    execute(): Status {
        return this.check() ? Status.SUCCESS : Status.FAILURE;
    }
}

// 行为节点:执行具体的动作,并返回结果
abstract class ActionNode extends Node {
    abstract performAction(): boolean;

    execute(): Status {
        return this.performAction() ? Status.SUCCESS : Status.FAILURE;
    }
}

// 装饰节点:用于修改其他节点的行为,通常只有一个子节点
abstract class DecoratorNode extends Node {
    protected child: Node;

    constructor(child: Node) {
        super();
        this.child = child;
    }
}

// 反转节点:将子节点的结果反转
class InverterNode extends DecoratorNode {
    execute(): Status {
        const status = this.child.execute();
        if (status === Status.SUCCESS) {
            return Status.FAILURE;
        } else if (status === Status.FAILURE) {
            return Status.SUCCESS;
        } else {
            return status;
        }
    }
}

// 重复执行装饰节点:重复执行子节点指定的次数,或直到满足某个条件
class RepeaterNode extends DecoratorNode {
    private maxRepeats: number;
    private stopOnSuccess: boolean;
    private stopOnFailure: boolean;

    constructor(child: Node, maxRepeats: number, stopOnSuccess: boolean = false, stopOnFailure: boolean = false) {
        super(child);
        this.maxRepeats = maxRepeats;
        this.stopOnSuccess = stopOnSuccess;
        this.stopOnFailure = stopOnFailure;
    }

    execute(): Status {
        let count = 0;
        while (this.maxRepeats === -1 || count < this.maxRepeats) {
            const status = this.child.execute();
            if (this.stopOnSuccess && status === Status.SUCCESS) {
                return Status.SUCCESS;
            }
            if (this.stopOnFailure && status === Status.FAILURE) {
                return Status.FAILURE;
            }
            count++;
        }
        return Status.SUCCESS;
    }
}

// 判断敌人是否在范围内的条件节点
class IsEnemyInRange extends ConditionNode {
    check(): boolean {
        // 判断敌人是否在范围内
        return Math.random() < 0.5; // 50% 概率敌人在范围内
    }
}

// 追击敌人的行为节点
class ChaseEnemy extends ActionNode {
    performAction(): boolean {
        console.log("Chasing the enemy!");
        return true; // 假设追击成功
    }
}

// 攻击敌人的行为节点
class AttackEnemy extends ActionNode {
    performAction(): boolean {
        console.log("Attacking the enemy!");
        return true; // 假设攻击成功
    }
}

// 创建行为树
const root = new SelectorNode();

const sequence = new SequenceNode();
root.addChild(sequence);

const condition = new IsEnemyInRange();
sequence.addChild(condition);

const chase = new ChaseEnemy();
const repeater = new RepeaterNode(chase, 3); // 重复执行ChaseEnemy 3次
sequence.addChild(repeater);

const attack = new AttackEnemy();
sequence.addChild(attack);

// 执行行为树
const status = root.execute();
console.log(`Behavior tree execution status: ${Status[status]}`);


 

### 行为树管理的技术实现与工具 行为树(Behavior Tree, BT)是一种广泛应用于游戏开发、机器人控制、人工智能等领域中的逻辑管理架构。它通过节点组合的方式,将复杂的决策逻辑以树形结构进行组织,从而实现清晰、可维护的逻辑代码。 #### 实现方法 行为树的核心实现基于几种基本类型的节点: - **组合节点(Composite Node)**:包括顺序节点(Sequence)、选择节点(Selector)和并行节点(Parallel)。顺序节点会依次执行子节点,直到某个子节点失败;选择节点则会依次执行子节点,直到找到一个成功的结果。组合节点通常需要记录当前正在执行的子节点,以避免在每次Tick时从头开始执行,从而提高性能[^5]。 - **装饰节点(Decorator Node)**:用于修改单个子节点的行为,例如限制执行次数、反转结果等。 - **条件节点(Condition Node)**:用于判断某种状态是否成立,例如检测敌人是否可见。 - **动作节点(Action Node)**:用于执行具体的行为,例如攻击、移动等。 行为树的更新机制通常采用**持续Tick**的方式,即在每次游戏循环中调用行为树的Tick函数。这种方式虽然在性能上略显开销,但避免了复杂的事件触发机制,简化了逻辑设计[^5]。 #### 技术框架 在技术实现上,行为树可以通过面向对象的方式构建。例如,定义一个抽象的`Behavior`类,作为所有节点的基类: ```cpp class Behavior { public: virtual Status Tick() = 0; // 纯虚函数,子类必须实现 virtual void Reset() {} // 可选的重置方法 }; ``` 具体节点类如`Selector`、`Sequence`、`Condition`、`Action`等继承自`Behavior`类,并实现各自的`Tick()`方法。 子行为树的嵌套也是行为树设计中的一个重要特性。可以将一个子行为树封装为一个`Subtree`节点,并将其作为主行为树的一个子节点插入。例如: ```cpp BehaviorTree* CreateSubTree() { auto* selector = new Selector(); selector->AddChild(new Condition_IsSeeEnemy(false)); selector->AddChild(new Action_Attack()); return new BehaviorTree(selector); } BehaviorTree* CreateMainBehaviorTree() { auto* mainRoot = new Selector(); BehaviorTree* subTree = CreateSubTree(); mainRoot->AddChild(new Subtree(subTree)); // 添加更多主树逻辑... return new BehaviorTree(mainRoot); } ``` 这种设计使得行为树的模块化和复用成为可能,提升了代码的可维护性。 #### 管理机制 行为树的管理通常通过一个`BehaviorManager`类来统一调度。例如,在Unity中可以这样实现: ```csharp BehaviorManager.instance.Tick(); // 全局Tick所有行为树 // 或者针对特定行为树进行Tick BehaviorManager.instance.Tick(BehaviorTree); ``` 该机制允许对多个行为树进行统一管理,同时也可以为不同的行为树设置不同的更新频率,从而实现更精细的性能控制[^4]。 #### 工具支持 在实际开发中,除了手动编写行为树代码外,还可以借助一些可视化工具来辅助设计和调试行为树: 1. **Behavior Designer(Unity插件)**:这是一个非常流行的Unity行为树插件,提供了图形化编辑器,支持节点拖拽、调试、状态查看等功能,极大地提升了开发效率。 2. **NodeCanvas(Unity插件)**:另一个Unity行为树工具,支持行为树、状态机等多种AI逻辑设计方式,具有良好的扩展性和易用性。 3. **Groot(ROS集成工具)**:用于ROS(机器人操作系统)中的行为树可视化编辑器,支持与C++和Python后端集成,适用于机器人控制逻辑的设计。 4. **BehaviorTree.CPP**:一个开源的C++行为树库,广泛用于机器人和嵌入式系统中,支持节点插件化、序列化等功能。 这些工具不仅简化了行为树的设计过程,还提供了丰富的调试功能,帮助开发者快速定位逻辑错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值