參考
豆包
目錄
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號