参考
豆包
目录
1 行为树定义
行为树(Behavior Tree)是一种用于管理和控制人工智能(AI)行为的树形结构,广泛应用于游戏开发、机器人控制和自动化系统中。
它将复杂的行为逻辑分解为多个简单的节点,通过组合这些节点来实现灵活、可扩展的 AI 决策系统。
例如一个NPC巡逻、索敌、追击、自言自语等。
例如一个弓箭手来回巡逻,靠近了攻击你,你追击他他会逃跑。
2 行为树基本节点
控制流节点
控制子节点的执行顺序和逻辑
序列节点(Sequence):按顺序执行所有子节点,若任一失败则终止。
选择节点(Selector):按顺序尝试子节点,直到一个成功为止。
并行节点(Parallel):同时执行多个子节点,根据策略决定成功或失败。
装饰器节点
修改或增强子节点的行为,例如重复执行子节点、反转子节点结果、限制子节点执行时间或次数。
叶节点
执行具体行为或检查条件
动作节点(Action):执行游戏角色的具体动作(如移动、攻击)。
条件节点(Condition):检查游戏状态(如 “是否看到敌人”“生命值是否低于阈值”)。
3 行为树执行流程
行为树从根节点开始执行,递归遍历子节点。
每个节点返回三种状态之一:成功(Success)、失败(Failure) 或 运行中(Running)。
运行中(Running) 状态允许行为树在多帧中持续执行复杂行为(如持续移动或攻击)。
4 行为树和状态机区别

5 应用场景
NPC 行为控制:巡逻、战斗、对话等。
敌人 AI:发现玩家时攻击,生命值低时逃跑,被围攻时呼叫支援。
群体行为:狼群协作狩猎、士兵阵型移动。
6 行为树优缺点
优点:
模块化,行为和条件独立封装,便于维护和复用。
可配置:可调整节点的组合和参数,便于改变角色行为。
缺点:
复杂:不适合简单场景
行为树类,包含序列、选择、条件、动作等节点。
BehaviorTree.ts:
// 行为节点状态
enum BehaviorStatus {
RUNNING,
SUCCESS,
FAILURE
}
// 行为节点基类
abstract class BehaviorNode {
protected name: string;
constructor(name: string) {
this.name = name;
}
abstract tick(blackboard: any): BehaviorStatus;
}
// 组合节点基类
abstract class CompositeNode extends BehaviorNode {
protected children: BehaviorNode[] = [];
constructor(name: string, children: BehaviorNode[] = []) {
super(name);
this.children = children;
}
addChild(child: BehaviorNode) {
this.children.push(child);
}
}
// 序列节点
class SequenceNode extends CompositeNode {
tick(blackboard: any): BehaviorStatus {
for (const child of this.children) {
const status = child.tick(blackboard);
if (status === BehaviorStatus.FAILURE) {
return BehaviorStatus.FAILURE;
}
if (status === BehaviorStatus.RUNNING) {
return BehaviorStatus.RUNNING;
}
}
return BehaviorStatus.SUCCESS;
}
}
// 选择节点
class SelectorNode extends CompositeNode {
tick(blackboard: any): BehaviorStatus {
for (const child of this.children) {
const status = child.tick(blackboard);
if (status === BehaviorStatus.SUCCESS) {
return BehaviorStatus.SUCCESS;
}
if (status === BehaviorStatus.RUNNING) {
return BehaviorStatus.RUNNING;
}
}
return BehaviorStatus.FAILURE;
}
}
// 装饰节点基类
abstract class DecoratorNode extends BehaviorNode {
protected child: BehaviorNode;
constructor(name: string, child: BehaviorNode) {
super(name);
this.child = child;
}
}
// 条件节点
class ConditionNode extends BehaviorNode {
private condition: (blackboard: any) => boolean;
constructor(name: string, condition: (blackboard: any) => boolean) {
super(name);
this.condition = condition;
}
tick(blackboard: any): BehaviorStatus {
return this.condition(blackboard) ? BehaviorStatus.SUCCESS : BehaviorStatus.FAILURE;
}
}
// 动作节点基类
abstract class ActionNode extends BehaviorNode {
constructor(name: string) {
super(name);
}
}
角色控制类中初始化行为树,实现如下逻辑
死亡序列:死亡则执行死亡动作;没有死亡,则执行攻击序列
攻击序列:攻击则执行攻击动作;没有攻击,则执行巡逻序列
巡逻序列:巡逻则执行巡逻动作;没有巡逻,则执行移动序列
移动序列:移动则执行移动动作;没有移动,则执行站立动作
站立动作:执行站立动作

CharaterController.ts:
private initBehaviorTree() {
// 创建行为树
const root = new SelectorNode("Root");
// 死亡条件和动作
const isDeadCondition = new ConditionNode("IsDead", (bb) => bb.isDead);
const deadAction = new ActionNode("Dead") {
tick(bb: any): BehaviorStatus {
bb.character.playDeath();
return BehaviorStatus.RUNNING;
}
};
const deadSequence = new SequenceNode("DeadSequence");
deadSequence.addChild(isDeadCondition);
deadSequence.addChild(deadAction);
// 攻击条件和动作
const isAttackingCondition = new ConditionNode("IsAttacking", (bb) => bb.isAttacking);
const attackAction = new ActionNode("Attack") {
tick(bb: any): BehaviorStatus {
bb.character.playAttack();
return BehaviorStatus.RUNNING;
}
};
const attackSequence = new SequenceNode("AttackSequence");
attackSequence.addChild(isAttackingCondition);
attackSequence.addChild(attackAction);
// 巡逻条件和动作
const isPatrollingCondition = new ConditionNode("IsPatrolling", (bb) => bb.isPatrolling);
const patrolAction = new ActionNode("Patrol") {
tick(bb: any): BehaviorStatus {
bb.character.patrol();
return BehaviorStatus.RUNNING;
}
};
const patrolSequence = new SequenceNode("PatrolSequence");
patrolSequence.addChild(isPatrollingCondition);
patrolSequence.addChild(patrolAction);
// 移动条件和动作
const hasTargetCondition = new ConditionNode("HasTarget", (bb) => bb.targetPosition !== null);
const moveAction = new ActionNode("Move") {
tick(bb: any): BehaviorStatus {
const reached = bb.character.moveTo(bb.targetPosition);
if (reached) {
bb.targetPosition = null;
return BehaviorStatus.SUCCESS;
}
return BehaviorStatus.RUNNING;
}
};
const moveSequence = new SequenceNode("MoveSequence");
moveSequence.addChild(hasTargetCondition);
moveSequence.addChild(moveAction);
// 跳跃条件和动作
const shouldJumpCondition = new ConditionNode("ShouldJump", (bb) =>
bb.character.currentState === CharacterState.JUMPING && bb.character.isGrounded
);
const jumpAction = new ActionNode("Jump") {
tick(bb: any): BehaviorStatus {
bb.character.jump();
return BehaviorStatus.SUCCESS;
}
};
const jumpSequence = new SequenceNode("JumpSequence");
jumpSequence.addChild(shouldJumpCondition);
jumpSequence.addChild(jumpAction);
// 站立动作
const idleAction = new ActionNode("Idle") {
tick(bb: any): BehaviorStatus {
bb.character.playIdle();
return BehaviorStatus.RUNNING;
}
};
// 构建行为树
root.addChild(deadSequence);
root.addChild(attackSequence);
root.addChild(jumpSequence);
root.addChild(moveSequence);
root.addChild(patrolSequence);
root.addChild(idleAction);
this.behaviorTree = root;
}
由上面代码可知,行为树是按照树状结构,组合了一套自定义的AI行为,优先干什么,条件是什么都预先设定好。
假如上面的AI行为用状态机去实现,那么得在State中去判断条件然后切换行为,假如修改了行为组合、行为优先级、行为条件,得去修改这个状态的代码,这样非常不便。
在行为树(Behavior Tree)中,并行节点(Parallel Node) 是一种复合节点(Composite Node),其核心作用是同时执行多个子节点,而非像序列节点(Sequence)或选择节点(Selector)那样按顺序执行。
它主要用于需要并行处理多个任务的场景,例如游戏中角色同时需要移动、攻击,同时还要需要检测周围环境。
例如实现攻击和检测并行,只要攻击或检测任一成功则算成功,只有攻击或检测都失败才算失败。
// 并行节点类
export class ParallelNode extends CompositeNode {
// 并行节点的完成条件
private requiredSuccesses: number; // 需要多少个子节点成功
private failOnAnyFailure: boolean; // 任何子节点失败是否导致整体失败
constructor(
name: string,
children: Node[],
requiredSuccesses: number = 1,
failOnAnyFailure: boolean = true
) {
super(name, children);
this.requiredSuccesses = requiredSuccesses;
this.failOnAnyFailure = failOnAnyFailure;
}
tick(deltaTime: number): NodeStatus {
let successCount = 0;
let failureCount = 0;
const totalChildren = this.children.length;
// 并行执行所有子节点
for (const child of this.children) {
const status = child.tick(deltaTime);
if (status === NodeStatus.SUCCESS) {
successCount++;
} else if (status === NodeStatus.FAILURE) {
failureCount++;
// 如果任何失败就整体失败,则立即返回失败
if (this.failOnAnyFailure) {
return NodeStatus.FAILURE;
}
}
}
// 检查是否达到了所需的成功数量
if (successCount >= this.requiredSuccesses) {
return NodeStatus.SUCCESS;
}
// 检查是否所有子节点都失败了
if (failureCount === totalChildren) {
return NodeStatus.FAILURE;
}
// 还有节点在运行中
return NodeStatus.RUNNING;
}
}
// 攻击行为节点
export class AttackNode extends Node {
tick(deltaTime: number): NodeStatus {
// 如果不在冷却中且没有在攻击,则开始攻击
return NodeStatus.RUNNING;
// 如果攻击结束
return NodeStatus.SUCCESS;
}
}
// 检测玩家位置节点
export class DetectPlayerNode extends Node {
tick(deltaTime: number): NodeStatus {
//检测到玩家在视野范围内!
return NodeStatus.SUCCESS;
//未检测到玩家
return NodeStatus.FAILURE;
}
// 检测间隔内返回运行中状态
return NodeStatus.RUNNING;
}
}
// 创建具体行为节点
const attackNode = new AttackNode(); // 攻击节点,2秒冷却,0.5秒攻击时间
const detectPlayerNode = new DetectPlayerNode(); // 检测玩家节点,1秒检测一次
// 创建并行节点:
// 同时执行攻击和检测玩家
// 至少1个成功即整体成功
// 任何失败不导致整体失败(因为检测玩家可能失败但攻击仍需继续)
const enemyBehavior = new ParallelNode(
"EnemyBehavior",
[attackNode, detectPlayerNode],
1, // 至少1个成功
false // 不因为任何失败而整体失败
);
装饰节点用于扩展基类节点,例如攻击节点,主要扩展为只有血量>50%才会执行。
那么再不修改攻击节点的情况下,使用装饰节点来实现这个功能。
// 具体的核心行为节点:攻击节点
export class AttackNode extends Node {
tick(): NodeStatus {
console.log("执行攻击动作!");
// 模拟攻击总是成功
return NodeStatus.SUCCESS;
}
}
// 具体的装饰节点:血量检查装饰器
export class HealthCheckDecorator extends DecoratorNode {
private health: number;
// 接收子节点和血量检查所需的参数
constructor(child: Node, health: number) {
super("HealthCheckDecorator", child);
this.health = health;
}
tick(): NodeStatus {
// 检查血量是否大于50%
if (this.health > 50) {
console.log(`血量${this.health}%,大于50%,允许执行子节点`);
// 执行子节点
return this.child.tick();
} else {
console.log(`血量${this.health}%,小于等于50%,不允许执行子节点`);
return NodeStatus.FAILURE;
}
}
}
// 使用示例
function demo() {
// 创建核心行为节点
const attackNode = new AttackNode();
// 情况1:血量60%,应该执行攻击
const healthyDecorator = new HealthCheckDecorator(attackNode, 60);
console.log("情况1结果:", healthyDecorator.tick());
// 情况2:血量40%,不应该执行攻击
const lowHealthDecorator = new HealthCheckDecorator(attackNode, 40);
console.log("情况2结果:", lowHealthDecorator.tick());
}
浙公网安备 33010602011771号