参考

豆包

 

目录

一 什么是行为树

二 行为树实现角色行为

三 补充:并行节点

四 补充:装饰节点 

 

一 什么是行为树

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

  

 

posted on 2025-07-20 17:39  gamedaybyday  阅读(863)  评论(0)    收藏  举报