TypeScriptで構築するビヘイビアツリーとキャラクターAI

ビヘイビアツリーの基本概念

ビヘイビアツリー(Behavior Tree)は、AIの意思決定を木構造で表現する手法で、ゲーム開発やロボット制御の分野で広く使われている。複雑な行動ロジックを小さなノードに分割し、それらを組み合わせることで、拡張性と再利用性に優れたAIシステムを実現する。

各ノードは評価時に「成功」「失敗」「実行中」のいずれかのステータスを返す。ツリーはルートから深さ優先で評価され、途中で「実行中」が返るとそのフレームの処理を中断し、次のフレームで継続できる。

ノードの種類

  • コンポジットノード(Composite): 子ノードを順序付きで評価する。代表的なものにシーケンス(すべて成功すれば成功)やセレクター(いずれかが成功すれば成功)がある。
  • デコレータノード(Decorator): 子ノードを一つだけ持ち、その結果を加工したり実行条件を変えたりする。
  • リーフノード(Leaf): 条件評価を行うConditionノードと、実際の行動を実行するActionノードに分かれる。

TypeScriptによるビヘイビアツリーの実装

以下では、最小限のビヘイビアツリーライブラリをTypeScriptで実装する。状態や基本クラスから定義し、最終的にキャラクターAIを構築する。

// ノードの実行結果を表す列挙型
enum BTStatus {
  Running,
  Success,
  Failure
}

// 全ノードの基底クラス
abstract class BTNode {
  constructor(public readonly name: string) {}
  abstract evaluate(context: any): BTStatus;
}

// 子ノードを持つコンポジットの基底クラス
abstract class BTComposite extends BTNode {
  protected children: BTNode[] = [];

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

// シーケンスノード:子を順に実行し、一つでも失敗すれば即座にFailure
class BTSequence extends BTComposite {
  evaluate(context: any): BTStatus {
    for (const child of this.children) {
      const status = child.evaluate(context);
      if (status === BTStatus.Failure || status === BTStatus.Running) {
        return status;
      }
    }
    return BTStatus.Success;
  }
}

// セレクターノード:子を順に実行し、一つでも成功すれば即座にSuccess
class BTSelector extends BTComposite {
  evaluate(context: any): BTStatus {
    for (const child of this.children) {
      const status = child.evaluate(context);
      if (status === BTStatus.Success || status === BTStatus.Running) {
        return status;
      }
    }
    return BTStatus.Failure;
  }
}

// 条件ノード:指定された述語をもとに成功/失敗を返す
class BTCondition extends BTNode {
  private predicate: (context: any) => boolean;

  constructor(name: string, predicate: (context: any) => boolean) {
    super(name);
    this.predicate = predicate;
  }

  evaluate(context: any): BTStatus {
    return this.predicate(context) ? BTStatus.Success : BTStatus.Failure;
  }
}

// 行動ノードの抽象クラス
abstract class BTAction extends BTNode {
  constructor(name: string) {
    super(name);
  }
}

キャラクターAIの構築例

上記のノードを用いて、ゲーム内キャラクターの簡単なAIを組み立てる。このキャラクターは以下の優先順位で行動する。

  1. 戦闘不能(死亡)状態なら、倒れモーションを再生する。
  2. 敵を発見しているなら、攻撃行動をとる。
  3. 巡回経路が設定されていれば巡回する。
  4. どれにも該当しなければ待機する。

実装は次のようになる。contextオブジェクトには、キャラクターの状態(isDefeated, hasEnemy, patrolPathなど)や、アクションを実行するためのメソッドを持たせる。

function buildAI(): BTNode {
  const root = new BTSelector("Root");

  // 戦闘不能シーケンス
  const defeatedSeq = new BTSequence("DefeatedSequence");
  defeatedSeq.addChild(new BTCondition("IsDefeated", (ctx) => ctx.agent.isDefeated));
  defeatedSeq.addChild(new BTAction("PlayDefeat") {
    evaluate(ctx: any): BTStatus {
      ctx.agent.playDefeatAnimation();
      return BTStatus.Running; // 倒れアニメーション中は継続
    }
  });

  // 戦闘シーケンス
  const combatSeq = new BTSequence("CombatSequence");
  combatSeq.addChild(new BTCondition("HasEnemy", (ctx) => ctx.agent.hasEnemy));
  combatSeq.addChild(new BTAction("Attack") {
    evaluate(ctx: any): BTStatus {
      ctx.agent.performAttack();
      return BTStatus.Running;
    }
  });

  // 巡回シーケンス
  const patrolSeq = new BTSequence("PatrolSequence");
  patrolSeq.addChild(new BTCondition("HasPatrolPath", (ctx) => ctx.agent.patrolPath != null));
  patrolSeq.addChild(new BTAction("Patrol") {
    evaluate(ctx: any): BTStatus {
      ctx.agent.moveAlongPath();
      return BTStatus.Running;
    }
  });

  // 待機アクション(条件なしで常に成功)
  const idleAction = new BTAction("Idle") {
    evaluate(ctx: any): BTStatus {
      ctx.agent.playIdle();
      return BTStatus.Running;
    }
  };

  root.addChild(defeatedSeq);
  root.addChild(combatSeq);
  root.addChild(patrolSeq);
  root.addChild(idleAction);

  return root;
}

// 毎フレーム context を更新し、ルートを評価する
const behaviorTree = buildAI();
const aiContext = { agent: myCharacter };

function updateAI() {
  behaviorTree.evaluate(aiContext);
}

このツリーでは、セレクターノードが上から順にシーケンスを評価する。戦闘不能状態でなければ次の戦闘シーケンスに進み、敵がいなければ巡回、それもなければ最終的に待機する。各アクションは「Running」を返すことで複数フレームにわたって動作を継続でき、攻撃や移動が完了したらSuccessを返すように拡張できる。

タグ: ビヘイビアツリー TypeScript ゲームAI AIアーキテクチャ

10月26日 17:56 投稿