AIゲーム開発ラボ

Unity ML-Agentsと行動ツリー連携:インディーズゲームに個性的なNPC行動を実装する実践ガイド

Tags: Unity, ML-Agents, ゲームAI, 行動ツリー, インディーズゲーム

インディーズゲーム開発において、プレイヤーを惹きつけ、ゲームに深みを与える要素の一つに、NPC(Non-Player Character)の行動があります。しかし、限られた時間と予算の中で、多様で魅力的なNPC行動を一つ一つ手作業で設計し、実装することは容易ではありません。従来のルールベースのAIでは、行動がパターン化しやすく、プレイヤーに飽きを感じさせてしまう可能性も懸念されます。

本記事では、このような課題に対し、Unityの「ML-Agents」と既存の「行動ツリー(Behavior Tree)」を連携させることで、小規模なプロジェクトでも個性的なNPC行動を効率的に実装する実践的なアプローチをご紹介します。機械学習未経験の開発者の方にも理解しやすいよう、基本的な概念から具体的な実装方法、費用対効果、導入後のメンテナンス性について詳細に解説いたします。この組み合わせにより、ゲームの差別化を図り、プレイヤーエンゲージメントを高める一助となれば幸いです。

ML-Agentsの概要とインディーズ開発での位置づけ

ML-Agentsは、Unity上で機械学習を利用したAIを開発するためのオープンソースツールキットです。通常、機械学習と聞くと専門的な知識が必要という印象を持たれがちですが、ML-Agentsはゲーム開発者が直感的に扱えるよう設計されています。

ML-Agentsの主な特徴は、エージェント(AIを搭載するキャラクター)が環境と相互作用しながら、目標達成のための行動を学習する点にあります。この学習プロセスには、主に「強化学習」が用いられますが、特定の手本となる行動を模倣させる「模倣学習」や、過去のデータから行動パターンを抽出する「行動クローニング」といった手法も利用可能です。インディーズ開発においては、データ収集の手間を抑えつつ、既存の行動設計と組み合わせやすい模倣学習や行動クローニング、あるいはシンプルな強化学習が特に有効となる場合があります。

ML-Agentsを導入することで、手作業で複雑な条件分岐やパラメータを調整する労力を削減し、NPCの行動に予期せぬ多様性や適応性をもたらすことが期待できます。これにより、ゲームのリプレイ性が向上し、プレイヤーの興味を持続させることに繋がります。

行動ツリー(Behavior Tree)の基礎と限界

行動ツリーは、ゲームAIの設計で広く利用されている手法の一つです。NPCの行動を階層的なツリー構造で表現し、条件に応じて次の行動を決定します。シーケンス(複数の行動を順番に実行)、セレクター(複数の行動の中から成功するまで試行)、デコレーター(特定の行動に条件や制限を追加)といったノードを組み合わせることで、複雑な行動ロジックを視覚的かつ構造的に構築できる点が大きな利点です。Unity Asset Storeには、Simple Behavior TreeやBehavior Designerなど、行動ツリー実装を支援する優れたアセットも豊富に提供されています。

行動ツリーは、その明確な構造とデバッグの容易さから、多くのゲーム開発で重宝されてきました。しかし、行動パターンが複雑化したり、環境やプレイヤーの行動に動的に適応する必要が生じたりする場合、以下の課題に直面することがあります。

これらの限界を克服し、より動的で個性的なNPC行動を実現するために、ML-Agentsの活用が有効な選択肢となります。

ML-Agentsと行動ツリーの連携戦略

ML-Agentsと行動ツリーを連携させる際の基本的な方針は、行動ツリーでNPCの全体的な行動フローや大まかな戦略を定義し、その中の特定の「意思決定ノード」や「サブ行動」をML-Agentsに置き換えるというものです。

例えば、NPCが「攻撃する」「回避する」「援護に回る」といった複数の選択肢を持つ場面で行われる意思決定を、ML-Agentsに学習させることができます。行動ツリーは「いつ、どのような状況でAIに意思決定を委ねるか」を制御し、ML-Agentsはその状況下で「どの行動を取るのが最適か」を推論する役割を担います。

この連携戦略のメリットは以下の通りです。

実践的チュートリアル:NPC行動の動的生成

ここでは、UnityとML-Agents、そして行動ツリーを連携させ、敵NPCがプレイヤーの行動パターンを学習し、それに合わせて攻撃・防御・回避戦略を変化させるシナリオを例に、具体的な実装手順を解説します。

ステップ1: 環境設定とML-Agentsの導入

  1. Unityの準備: Unity Hubから最新版のUnityエディターをインストールします。
  2. ML-Agentsの導入:
    • Unityエディターを開き、「Window > Package Manager」から「Unity Registry」を選択します。
    • 「ML-Agents」を検索し、インストールします。
    • ML-AgentsのGitHubリポジトリから最新のRelease版をダウンロードし、ml-agents-x.x.x/UnitySDK/Assets/ML-AgentsフォルダをプロジェクトのAssetsフォルダにコピーする形式でも導入可能です。

ステップ2: 行動ツリーの基本構成

ここでは、Asset Storeで提供されている「Simple Behavior Tree」のようなアセットを導入したと仮定します。

  1. 基本的な行動ツリーの作成:
    • NPCが「プレイヤーを追跡する」「プレイヤーを攻撃する」「障害物を避ける」「巡回する」といった基本的な行動を定義します。
    • 例えば、最上位にSelectorノードを置き、その子ノードとして「プレイヤーを発見したか」→「攻撃ノード(Sequence)」、「プレイヤーを追跡ノード(Sequence)」、「巡回ノード(Sequence)」などを配置します。
    • ML-Agentsに制御を渡す場所として、例えば「プレイヤーが射程内に入ったが、どの攻撃方法を選択するか」や「ダメージを受けた時に反撃するか、回避するか」といった判断を行う「AI決定ノード」を仮のノードとして行動ツリー内に組み込みます。このノードがML-AgentsのAgentを呼び出す役割を担います。

ステップ3: ML-Agentsエージェントの実装

ML-AgentsのエージェントとなるAgentスクリプトをC#で作成します。

using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;

public class NPCAgent : Agent
{
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private float rotateSpeed = 100f;
    [SerializeField] private GameObject targetPlayer; // プレイヤーオブジェクト
    private Rigidbody rBody;
    private BehaviorTreeManager behaviorTree; // 行動ツリーのマネージャー

    // 行動決定の種類を定義 (例: 攻撃, 回避, 接近)
    public enum NPCActions { Attack = 0, Evade = 1, Approach = 2, MaxActions = 3 }

    void Start()
    {
        rBody = GetComponent<Rigidbody>();
        // BehaviorTreeManagerは、Simple Behavior Treeなどのアセットで提供されるものと仮定
        // behaviorTree = GetComponent<BehaviorTreeManager>(); 
    }

    // エピソード開始時のリセット処理
    public override void OnEpisodeBegin()
    {
        // NPCの位置をランダムにリセット
        transform.localPosition = new Vector3(Random.value * 8 - 4, 0.5f, Random.value * 8 - 4);
        // プレイヤーの位置もリセット
        if (targetPlayer != null)
        {
            targetPlayer.transform.localPosition = new Vector3(Random.value * 8 - 4, 0.5f, Random.value * 8 - 4);
        }
        rBody.velocity = Vector3.zero;
        // その他のゲーム固有のリセット処理 (HP, 弾薬など)
    }

    // 環境の観察データを収集
    public override void CollectObservations(VectorSensor sensor)
    {
        // プレイヤーの位置とNPC自身の位置、速度を観察データとして追加
        sensor.AddObservation(targetPlayer.transform.localPosition); // 3 float
        sensor.AddObservation(transform.localPosition); // 3 float
        sensor.AddObservation(rBody.velocity.x); // 1 float
        sensor.AddObservation(rBody.velocity.z); // 1 float

        // その他の重要な情報 (例: NPCのHP, プレイヤーのHP, プレイヤーとの距離、プレイヤーの攻撃タイプなど)
        // sensor.AddObservation(Vector3.Distance(transform.localPosition, targetPlayer.transform.localPosition));
    }

    // AIからの行動指示を受け取る
    public override void OnActionReceived(ActionBuffers actions)
    {
        // 行動バッファからIntアクション(どの行動を取るか)を取得
        int chosenAction = actions.DiscreteActions[0];

        // 行動ツリーのAI決定ノードに結果を渡し、次の行動を促す
        // ここでは仮のメソッドとして HandleAIChoice を想定
        // behaviorTree.HandleAIChoice((NPCActions)chosenAction);

        // 簡単な移動行動の例 (学習中のデバッグ用)
        Vector3 controlSignal = Vector3.zero;
        if (chosenAction == (int)NPCActions.Attack)
        {
            // 攻撃行動のロジックをここに記述
            // 例えば、プレイヤーに向かって発砲する、接近して近接攻撃を行うなど
            Debug.Log("NPCが攻撃を選択");
            AddReward(0.1f); // 攻撃が成功したら報酬を与える
        }
        else if (chosenAction == (int)NPCActions.Evade)
        {
            // 回避行動のロジック
            // 例えば、プレイヤーから離れる、遮蔽物に隠れるなど
            controlSignal.x = -1f; // 左に移動する例
            Debug.Log("NPCが回避を選択");
            AddReward(0.05f); // 回避成功で報酬
        }
        else if (chosenAction == (int)NPCActions.Approach)
        {
            // 接近行動のロジック
            // 例えば、プレイヤーに向かって移動する
            controlSignal.x = 1f; // 右に移動する例
            Debug.Log("NPCが接近を選択");
            AddReward(0.05f); // 接近成功で報酬
        }

        rBody.AddForce(controlSignal * moveSpeed, ForceMode.VelocityChange);

        // タイムアウトによるペナルティ
        AddReward(-1f / MaxStep);
    }

    // 手動操作 (デバッグ用や模倣学習の教師データ作成用)
    public override void Heuristic(in ActionBuffers actionsOut)
    {
        var discreteActionsOut = actionsOut.DiscreteActions;
        discreteActionsOut[0] = (int)NPCActions.Approach; // デフォルトで接近
        if (Input.GetKey(KeyCode.Space))
        {
            discreteActionsOut[0] = (int)NPCActions.Attack;
        }
        if (Input.GetKey(KeyCode.LeftShift))
        {
            discreteActionsOut[0] = (int)NPCActions.Evade;
        }
    }

    // 衝突時の報酬例
    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Wall"))
        {
            SetReward(-0.5f); // 壁に衝突したらペナルティ
        }
        // プレイヤーからのダメージを受けた際のペナルティなど
    }
}

ステップ4: 行動ツリーとML-Agentsの連携コード

行動ツリーのアセットに応じて実装は異なりますが、概念的には以下のようになります。

  1. AI決定ノードの実装: 行動ツリー内にカスタムノードを作成し、このノードがNPCAgentRequestDecision()メソッドを呼び出します。
  2. 結果の受け渡し: NPCAgentが推論した行動(例: NPCActions.Attack)を行動ツリーのAI決定ノードに渡し、ノードはその結果に基づいて行動ツリーの次のブランチ(例: 攻撃行動のシーケンス)に進みます。
// 仮のAI決定ノードのExecuteメソッドのイメージ (Simple Behavior Treeなどアセットの書き方に準拠)
public class AIDecisionNode : BehaviorTreeNode
{
    private NPCAgent npcAgent;

    public override NodeState Execute()
    {
        if (npcAgent == null)
        {
            npcAgent = GetOwner().GetComponent<NPCAgent>(); // NPCのルートオブジェクトから取得
        }

        // ML-Agentsに意思決定を要求
        npcAgent.RequestDecision();

        // ML-Agentsからの結果(OnActionReceivedで処理された結果)に基づいて、子ノードに進む
        // これは非同期処理になるため、実際には別のメカニズムが必要
        // 例: NPCAgentが決定したアクションを内部変数に保持し、このノードがそれを取得
        // または、OnActionReceived内で直接行動ツリーの特定のブランチをトリガーする。
        // ここでは簡略化のため、常にRunningを返し、AgentからのActionReceivedで実際の行動が行われることを想定
        return NodeState.Running; // 決定を待つ間はRunningを返す
    }
}

実際には、ML-AgentsのOnActionReceived内で、行動ツリーを直接制御するか、ML-Agentsが推論した行動を保持する変数を用意し、行動ツリーのフレームワークがその変数をチェックして次の行動を決定するような設計が考えられます。

ステップ5: 学習プロセスとデプロイ

  1. 学習環境の構築:
    • 複数のAgent(NPC)をシーンに配置し、学習を並列で行えるようにします。
    • 各エージェントにBehavior Parametersコンポーネントを追加し、Behavior Nameを設定します。
    • Modelスロットは最初は空欄にしておきます。
    • Behavior TypeDefaultにし、学習時にPythonスクリプトから制御できるようにします。
  2. Pythonでの学習:

    • PCにPythonとML-AgentsのPythonパッケージをインストールします。
    • mlagents-learnコマンドを使用して学習を実行します。例えば、模倣学習を行う場合、Heuristicメソッドで手動操作したデータを記録し、それを教師データとして学習させることが可能です。

    ```bash mlagents-learn /config/my_trainer_config.yaml --run-id=MyNPCAgent --base-port 5005

    --force を追加すると既存の実行IDを上書きします

    `` *my_trainer_config.yamlには、学習アルゴリズム(PPO, GAILなど)、学習ステップ数、報酬スケールなどを定義します。小規模プロジェクトでは、複雑な強化学習よりも、シンプルな設定や模倣学習から始めることを推奨します。 3. **学習済みモデルのデプロイ:** * 学習が完了すると、resultsフォルダ内に.onnx形式の学習済みモデルが生成されます。 * この.onnxファイルをUnityプロジェクトにインポートし、ML-AgentsのBehavior ParametersコンポーネントのModelスロットにドラッグ&ドロップします。 *Behavior TypeInference Only`に変更することで、学習済みモデルに基づいた推論が実行されます。

小規模プロジェクトでの留意点とメンテナンス性

AI導入によるメリットと費用対効果

ML-Agentsと行動ツリーの連携によるAI導入は、インディーズゲーム開発に多大なメリットをもたらし、費用対効果の面でも優れています。

応用例

本記事で紹介した連携アプローチは、様々なゲームプレイに応用可能です。

結論

Unity ML-Agentsと行動ツリーの連携は、限られたリソースの中でゲームの差別化とプレイヤー体験の深化を目指すインディーズ開発者にとって、非常に現実的かつ費用対効果の高いAI導入戦略となります。機械学習の専門知識がなくても、行動ツリーという馴染み深いフレームワークと組み合わせることで、一歩進んだAI開発に挑戦することが可能です。

本記事で解説した具体的なアプローチと応用例を参考に、ぜひご自身のプロジェクトにML-Agentsと行動ツリーの連携を導入し、NPCの行動に新たな次元を加えてみてください。プレイヤーが予測不能で個性的なNPCとの相互作用を通じて、より豊かなゲーム体験を得られることでしょう。AIを活用したゲーム開発は、あなたの創造性をさらに広げる強力なツールとなるはずです。