【Unity】Unity AIナビゲーション:NavMeshで賢い敵キャラクターを作る

作成: 2025-12-07最終更新: 2026-07-13

敵がプレイヤーに向かって直進して壁に引っかかる——その解決策がNavMesh(経路探索)です。NavMeshSurfaceでのベイクからNavMesh Agentの設定、SetDestinationによる追跡AI、巡回→発見→追跡→捜索の実践パターンまで、賢い敵の作り方を解説します。

敵キャラクターをプレイヤーへ向かわせる最初の実装は、たいていtransform.positionをプレイヤーへ直進させるコードです。そして最初のバグもすぐ来ます—— 敵が壁に引っかかったまま、こちらをじっと見ている

壁や障害物を避けて目的地への最短ルートを自分で見つける——この 経路探索(Pathfinding) をUnityで実現する標準機能がNavMesh(ナビゲーションメッシュ) です。この記事では、NavMeshのベイクから追跡AI、そして「巡回→発見→追跡」の実践パターンまでを解説します。

NavMeshのイメージ。白いミニチュアの部屋の床一面に移動可能エリアを示す青いメッシュが広がり、青い人形が障害物を避けた点線の経路を辿ってプレイヤーの人形へ向かっている

この記事でわかること

  • NavMeshシステムの3点セット: NavMesh/NavMesh Agent/NavMesh Obstacle
  • NavMeshSurface でのベイク(Unity 6の現行ワークフロー)
  • SetDestination()だけで動く追跡AI
  • 巡回→発見→追跡→捜索——見失っても粘る、実戦的な敵AIパターンの組み立て
  • remainingDistanceでの到着判定と、動かないときに見る3つの計器

Sponsored

NavMeshシステムは3つの登場人物で構成されます。「地図」「旅人」「通せんぼ」 と覚えるのが早道です。

NavMeshの3要素の図。NavMeshは床に広がる青い地図、NavMesh Agentは経路を辿って歩く旅人の人形、NavMesh Obstacleは通路を塞いで地図に穴を開ける障害物として描かれている
  1. NavMesh(地図): AIが移動可能なエリアを示す多角形メッシュ。シーンのジオメトリから自動生成(ベイク)されます。
  2. NavMesh Agent(旅人): NavMesh上を移動する能力を持つコンポーネント。目的地を渡すと、経路計算・移動・他エージェントとの衝突回避まで自動でこなします。
  3. NavMesh Obstacle(通せんぼ): 動的な障害物用のコンポーネント。閉じたドアや倒れた柱などに付けると、エージェントが迂回するようになります。

Step 1: NavMeshをベイクする(NavMeshSurface)

現行のUnity(2022.3以降/Unity 6)では、AI NavigationパッケージNavMeshSurfaceコンポーネントを使うのが標準ワークフローです。

tips: 古いチュートリアルにある「Window > AI > NavigationウィンドウでNavigation Staticを設定してBake」という手順はレガシー版です。考え方は同じですが、これから作るプロジェクトではNavMeshSurfaceを使いましょう。

  1. パッケージの確認: Window > Package ManagerAI Navigationcom.unity.ai.navigation)がインストールされていることを確認します(Unity 6では通常デフォルトで入っています)。
  2. NavMeshSurfaceの追加: 地面となるオブジェクト(または空のGameObject)にNavMeshSurfaceコンポーネントを追加します。
  3. ベイク対象の指定: Collect Objectsで対象範囲(All Game Objectsなど)を選びます。壁や障害物もジオメトリとして収集され、「通れない場所」として反映されます。
  4. ベイク実行: InspectorのBakeボタンを押すと、シーンビューに青いポリゴンが表示されます。これがAIの歩ける範囲です。
NavMeshベイクの前後比較図。ベイク前はただの白い地形、ベイク後は床一面に青い半透明のメッシュが広がり、壁や障害物の周囲だけ穴が開いている

ベイク設定はエージェントの「体格」

ベイク設定の実体は、「どんな体格のキャラが通れる地図を作るか」 です。

  • Agent Radius: エージェントの半径。これより狭い隙間は通行不可になります。
  • Agent Height: エージェントの身長。これより低い天井の下は通行不可になります。
  • Max Slope: 登れる坂の最大角度。
  • Step Height: 乗り越えられる段差の高さ。

「敵が狭い通路を通ってくれない」ときは、たいていAgent Radiusが通路幅に対して大きすぎます。

Sponsored

Step 2: NavMesh Agentの設定

AIキャラクターにAdd Component > Navigation > NavMesh Agentを追加し、移動の性格をInspectorで決めます。

プロパティ役割目安
Speed最大移動速度歩く住人なら2前後、追跡する敵なら4〜6
Angular Speed回転速度速い敵ほど大きく(曲がり角でモタつかない)
Acceleration加速度低いと発進・方向転換がゆったりに
Stopping Distance目的地の手前どれだけで止まるか0は振動の元。0.5〜、攻撃AIなら攻撃レンジに
Radius / Heightこのエージェント個体のサイズエージェント同士の衝突回避に影響

Step 3: スクリプトからAgentを制御する

移動指示は1行です。SetDestination()に目的地を渡すだけで、経路探索も移動も回避もエージェントが引き受けます。

using UnityEngine;
using UnityEngine.AI; // NavMesh関連のクラスに必要

[RequireComponent(typeof(NavMeshAgent))]
public class EnemyChase : MonoBehaviour
{
    private NavMeshAgent agent;
    private Transform playerTarget;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();

        GameObject player = GameObject.FindGameObjectWithTag("Player");
        if (player != null)
        {
            playerTarget = player.transform;
        }
    }

    void Update()
    {
        if (playerTarget != null)
        {
            // プレイヤーの現在位置を目的地に設定し続ける
            agent.SetDestination(playerTarget.position);
        }
    }
}

これだけで、敵は障害物を避けながらプレイヤーを追いかけ始めます。壁に引っかかっていたあの敵が、曲がり角を先回りするように回り込んできます。

tips: 歩行アニメーションと連動させるなら、agent.velocity.magnitudeをAnimatorのSpeedパラメータに渡すのが定番です(Animator Controllerの記事 参照)。

実践:巡回→発見→追跡→捜索の敵AIを組む

実際のゲームの敵は「ずっと追跡」ではなく、普段は巡回し、プレイヤーを見つけたら追跡し、近づいたら攻撃するという状態の切り替えで動いています。ステルスゲームの警備兵、RPGのフィールドモンスター、タワーディフェンスの侵攻ユニット——ジャンルを問わずこの骨格は共通です。

巡回・発見・追跡のステート図。青い警備兵の人形が旗の間を点線ループで巡回し、視界の扇形に隠れたプレイヤーが入ると発見状態になり、追跡へ切り替わる3段階が1枚のジオラマで描かれている

見失った瞬間に即・巡回へ戻る敵は、少し嘘くさく見えます。ここでは「最後に見た場所まで行って、数秒粘ってから戻る」捜索の動きまで入れて、人間くさい警備兵に仕上げます。

using UnityEngine;
using UnityEngine.AI;

[RequireComponent(typeof(NavMeshAgent))]
public class GuardAI : MonoBehaviour
{
    [SerializeField] private Transform[] patrolPoints; // 巡回地点
    [SerializeField] private float sightRange = 8f;    // 発見距離
    [SerializeField] private float attackRange = 1.5f; // 攻撃距離
    [SerializeField] private float searchTime = 3f;    // 見失った現場で粘る秒数

    private enum Mode { Patrol, Chase, Search }
    private Mode mode = Mode.Patrol;

    private NavMeshAgent agent;
    private Transform player;
    private int patrolIndex = 0;
    private Vector3 lastKnownPosition;
    private float searchTimer;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();

        // Playerが未配置でも落ちないように(タグ検索はnullを返し得る)
        GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
        if (playerObj != null) player = playerObj.transform;

        if (patrolPoints.Length > 0)
            agent.SetDestination(patrolPoints[patrolIndex].position);
    }

    void Update()
    {
        if (player == null || patrolPoints.Length == 0) return;

        bool canSee = Vector3.Distance(transform.position, player.position) <= sightRange;
        bool arrived = !agent.pathPending && agent.remainingDistance <= agent.stoppingDistance;

        switch (mode)
        {
            case Mode.Patrol:
                if (canSee) { mode = Mode.Chase; break; }
                if (arrived)
                {
                    patrolIndex = (patrolIndex + 1) % patrolPoints.Length;
                    agent.SetDestination(patrolPoints[patrolIndex].position);
                }
                break;

            case Mode.Chase:
                if (canSee)
                {
                    // 見えている間は「最後に見た場所」を更新し続ける
                    lastKnownPosition = player.position;
                    agent.stoppingDistance = attackRange;
                    agent.SetDestination(player.position);
                }
                else
                {
                    // 見失った → 最後に見た場所へ向かい、捜索モードへ
                    agent.stoppingDistance = 0.5f;
                    agent.SetDestination(lastKnownPosition);
                    searchTimer = searchTime;
                    mode = Mode.Search;
                }
                break;

            case Mode.Search:
                if (canSee) { mode = Mode.Chase; break; }
                if (arrived) searchTimer -= Time.deltaTime;  // 現場に着いてから数える
                if (searchTimer <= 0f)
                {
                    agent.SetDestination(patrolPoints[patrolIndex].position);
                    mode = Mode.Patrol;
                }
                break;
        }
    }
}

ポイントは3つです。

  • 到着判定はremainingDistance: !agent.pathPending && agent.remainingDistance <= agent.stoppingDistanceが「着いた」の定番イディオムです。経路計算が終わる前に判定しないようpathPendingを確認します。
  • 攻撃レンジはstoppingDistanceで作る: 追跡時のStopping Distanceを攻撃距離にしておけば、「密着せず、剣の届く距離で止まって攻撃」が自然に作れます。
  • lastKnownPositionは「見えている間ずっと」更新: 見失った瞬間の値がそのまま捜索の目的地になります。プレイヤーからすると「さっきまでいた場所を調べに来る」ので、ぐっと知能を感じる動きになります。

Playして、追いかけられている途中で壁の裏へ逃げてみてください。警備兵はあなたが最後にいた場所まで来て、3秒ほどそこに留まり、何もいなければ持ち場の巡回へ戻っていきます。「距離だけでなく視界(正面の扇形)で発見したい」場合は、角度チェックと Raycastによる遮蔽判定 を組み合わせます(完成形は 視野・索敵システムの記事 へ)。状態がさらに増えてきたら Stateパターンの記事 の出番です。

NavMeshのAIが動かないとき、コードを疑う前に見る場所が3つあります。

症状見る計器よくある犯人と直し方
そもそも1歩も動かないagent.isOnNavMeshfalseならAgentがNavMeshの外にいます(空中スポーン・ベイク漏れ)。agent.Warp(位置)でメッシュ上に乗せ直す
目的地の手前で止まる/巡回が止まるagent.pathStatusPathPartial(途中までしか行けない)やPathInvalid(到達不能)。孤立した巡回点への部分経路はremainingDistanceが縮まず、到着判定が永遠に来ません。巡回点をNavMesh上に置き直す
なんだかカクカク重いSetDestinationの呼び出し頻度毎フレームの再計算が積もっています。動くプレイヤーの追跡は毎フレームでも、固定の巡回点は到着時に1回だけで十分です

おまけ:先に知っておくと良いこと

  • NavMesh ObstacleのCarve: 動的な障害物にObstacleを付けてCarveを有効にすると、NavMeshに一時的な穴が開き、エージェントが迂回を再計算します。閉じるドア・破壊可能な柵などに。
  • 段差やジャンプはNavMesh Link: 崖から飛び降りる・溝を飛び越えるなど、地続きでない場所の接続はNavMesh Linkで定義します。
  • 体格の違うエージェント: 「人間用」と「巨大ボス用」で通れる幅が違う場合は、Agent Typeを複数定義してNavMeshSurfaceをそれぞれベイクします。
  • エージェントを直接動かしたいとき: ノックバックなど物理的な移動を混ぜる場合、agent.Warp()agent.isStoppedを使います。TransformやRigidbodyで直接動かすとNavMesh上の位置と食い違って暴れる原因になります。

まとめ

NavMeshを使えば、経路探索アルゴリズムを自作せずに「賢く動く敵」が作れます。

  • 3点セット: NavMesh(地図)+NavMesh Agent(旅人)+NavMesh Obstacle(通せんぼ)。
  • ベイクはNavMeshSurface(現行ワークフロー)。ベイク設定はエージェントの「体格」。
  • 移動指示はSetDestination()1行。回避も経路も自動。
  • Stopping Distanceは0にしない。攻撃レンジとしても使える。
  • 実戦の敵AIは「巡回→発見→追跡→捜索」。到着判定はremainingDistanceイディオム、動かないときはisOnNavMeshpathStatusの計器を見る。

敵AIだけでなく、味方NPCの追従、街の住人の巡回、RTSのユニット移動——「キャラクターが自分で歩く」場面すべてで使える基盤技術です。