Unity 6|NavMesh Agentで2D風の追いかけっこゲームを作ろう!公式機能だけで障害物を避ける敵AI

広告

2Dの見下ろし型ゲームのような画面で、敵がプレイヤーを追いかける。間に壁があっても、敵は通れる道を探して回り込む。今回はUnity公式のAI NavigationとNavMesh Agentだけで、その仕組みを作ります。非公式のNavMeshPlusは使いません。

1. なぜ3DのNavMeshで「2D風」が作れるの?

Unityの標準NavMeshは、主に3D空間の経路探索を想定した機能です。ただし、ゲームの内部構造と画面上の見え方は別物。XZ平面に3Dの地面と壁を置き、Orthographic(平行投影)カメラで真上から見下ろすと、2Dゲームのような画面にできます。

3DのXZ平面をOrthographicカメラで真上から撮影し、2D風に見せる概念図
図1:内部は3DのXZ平面。カメラが上から映すと、見た目は2D風になる。概念図であり実際のUnity Editor画面ではありません。
通常の2Dゲーム今回の2D風ゲーム
画面の左右XX
画面の上下YZ
高さZY
地面XY平面XZ平面
経路探索別の設計・実装が必要標準NavMeshを利用

2. 地面と壁を配置する

Unity 6で新規3Dプロジェクトを作ります。プロジェクト名の例は NavMeshChaseGame。

Hierarchyで 3D Object → Plane を作成して Ground に改名します。TransformはPosition (0,0,0)、Rotation (0,0,0)、Scale (1,1,1)。PlaneはXZ平面に広がり、初期状態で約10×10の広さです。

続いて 3D Object → Cube を作り Wall に改名。TransformはPosition (0,0.5,0)、Rotation (0,0,0)、Scale (1,1,5) にします。壁の両端には迂回できる隙間が残ります。CubeのBox Colliderはそのままにします。

3. プレイヤーと敵を作る

空のGameObjectを Player と名付け、Positionを (3,0,-3) にします。子にCapsuleを作って Visual と名付け、子のローカルPositionを (0,1,0) にします。

同様に空の Enemy を作成し、Positionを (-3,0,3) にします。子のCapsule Visual のローカルPositionも (0,1,0)。識別しやすいように敵の見た目を赤色にしてもよいでしょう。

SampleScene
├─ Main Camera
├─ Directional Light
├─ Ground (Plane)
├─ Wall (Cube)
├─ Player
│  └─ Visual (Capsule)
└─ Enemy
   └─ Visual (Capsule)

親のPositionは地面上の位置です。子のVisualを使うことで、見た目だけあとからSpriteに交換できます。

4. AI Navigationをインストールする

NavMeshを作る(Bakeする)には、公式のAI Navigationパッケージが必要です。このパッケージには、NavMesh Surfaceなど、歩ける場所を作る公式コンポーネントが含まれています。

まず、すでに入っているか確認しましょう。

  1. Window → Package Management → Package Manager を開く
  2. 左上の表示を In Project にする
  3. AI Navigation が一覧にあれば、インストールは不要

一覧になければ、次の手順で入れます。

  1. 表示を Unity Registry に切り替える
  2. AI Navigation を選び、Install を押す

ポイント: 次の章で使うNavMesh Surfaceは、このパッケージの機能です。入っていないと、Add Componentで検索しても出てきません。Enemyに付けるNavMesh Agentは、Unity本体の機能なので、パッケージがなくても使えます。ただし、Bakeしていないと、敵は動きません。

5. NavMesh Surfaceで歩ける場所を作る

空のGameObject Navigation を作成し、NavMesh Surface コンポーネントを追加します。初期の小規模サンプルでは次を目安にします。

設定値
Agent TypeHumanoid(既定のAgent Type)
Collect ObjectsAll(表示名はパッケージ版で異なる場合あり)
Use GeometryRender Meshes
Include LayersEverything

Bake を押すと、地面を移動可能なNavMeshとして生成します。Sceneビューで移動可能領域を確認しましょう。壁の位置には通れない部分ができ、両端には迂回できる領域が残るはずです。

直進では壁に阻まれ、NavMesh経路なら壁を迂回して追跡する比較図
図2:目的地への直線移動とNavMeshによる迂回経路の違い。青い経路は動作イメージで、実際の経路はBake結果により異なります。

ポイント: Bakeは現在の静的な形状をもとに経路探索用の領域を作ります。後から壁の配置を変えたときは、必要に応じてBakeし直してください。移動する障害物は別途NavMesh Obstacleなどを検討します。

6. 敵にNavMesh Agentを設定する

Enemy に NavMesh Agent を追加します。最初は次の設定で試せます。

設定値
Agent TypeNavMesh Surfaceと同じHumanoid
Speed3
Angular Speed360
Acceleration8
Stopping Distance1
Radius0.3
Height2

Stopping Distance は目標にどれだけ近づいて停止するかの目安です。Agent TypeがSurfaceと一致することが重要です。

7. 追いかけるスクリプト

EnemyChase.cs を作ります。

using UnityEngine;
using UnityEngine.AI;

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

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

    void Update()
    {
        if (player == null || !agent.isOnNavMesh)
        {
            return;
        }

        agent.SetDestination(player.position);
    }
}

Enemyにアタッチし、Inspectorの Player 欄にHierarchyのPlayerをドラッグ&ドロップします。

GetComponent<NavMeshAgent>() は同じGameObjectのNavMesh Agentを取得します。SetDestination(player.position) でプレイヤーの現在位置を目的地に指定します。実際の経路計算には数フレームかかる場合があります。isOnNavMesh はAgentがNavMesh上に配置されているか確認するプロパティです。

ここでは初学者向けに毎フレーム目標を更新しています。大規模なゲームでは更新頻度を下げる設計も検討してください。

8. Input Systemでプレイヤーを動かす

Input Systemパッケージを導入し、必要に応じてProject SettingsのActive Input Handlingを Input System Package (New) または Both にします。Playerに PlayerMove.cs を追加します。

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMove : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 3f;

    void Update()
    {
        if (Keyboard.current == null) return;

        Vector3 direction = Vector3.zero;
        if (Keyboard.current.leftArrowKey.isPressed) direction.x -= 1;
        if (Keyboard.current.rightArrowKey.isPressed) direction.x += 1;
        if (Keyboard.current.upArrowKey.isPressed) direction.z += 1;
        if (Keyboard.current.downArrowKey.isPressed) direction.z -= 1;

        transform.position += direction.normalized * moveSpeed * Time.deltaTime;
    }
}

上下移動に使っているのはYではなくZです。これはゲームの地面がXZ平面だからです。normalized で斜め移動時の速度が過度に大きくならないようにします。

注意: このコードは移動の最小例で、壁との衝突や地面から出ないための制限を実装していません。実際に遊べるゲームではCharacterControllerなどで壁の通り抜けを防ぐ必要があります。

9. 真上から映すカメラを設定する

Main Cameraを選び、次を設定します。

設定値
Position(0,15,0)
Rotation(90,0,0)
ProjectionOrthographic
Size7

Orthographic は平行投影です。遠い物体ほど小さく見えるPerspectiveとは違い、距離によって見かけの大きさが変化しません。

Unity 6のGround、Wall、Player、Enemy、Navigation、Main Cameraの構成
図3:今回の構成例。Hierarchyの部品関係と上方のカメラを示す概念図で、Unity Editorの実際のスクリーンショットではありません。

Playを押し、矢印キーでPlayerを壁の向こう側へ動かしてみましょう。Bakeが正しくできていれば、EnemyはNavMesh上の通れる道を探して追跡します。

10. CapsuleをSpriteに変えるには?

Enemy/Visual のCapsuleの代わりにSpriteRendererを持つ子GameObjectを配置することもできます。通常のSpriteはXY平面に表示されるため、子VisualのRotationを (90,0,0) などに調整して真上から見えるようにします(画像の向きに応じて調整)。NavMesh Agentは親Enemyに付けたままです。

SpriteRendererは見た目を描画するコンポーネントであり、2D Colliderを付けたからといってNavMeshが2D物理演算と連動するわけではありません。 今回の方式では3Dの地面、3D用Collider、3D空間の移動を基本として考えます。

11. うまく動かないとき

  • Surfaceが追加できない: AI Navigationパッケージが入っているか、Package Managerの「In Project」で確認する。
  • 敵が動かない: SurfaceをBakeしたか、EnemyがNavMesh上にいるか、EnemyChaseのPlayer欄に参照があるかを確認。
  • 壁を突き抜ける: Bake後に壁を移動していないか、壁の形状がBake対象か確認し、Bakeし直す。
  • 壁を迂回できない: 通路幅とAgent Radius、SurfaceとAgentのAgent Typeの一致、NavMeshが両側で連続しているか確認。
  • Spriteが見えない: Visualの回転とSpriteRendererの描画面を確認。
  • プレイヤーが壁を抜ける: サンプルのPlayerMoveは衝突未対応。追加の移動制御が必要。

12. NavMeshPlusとの違い

比較今回の3Dを2D風に見せる方法NavMeshPlus
Unity公式機能のみ○×(外部ライブラリ)
主な移動平面XZXY
既存2D Tilemapとの相性そのままでは使いにくい2Dに合わせやすい
得意な用途新規の見下ろし型ゲーム既存の2D構成を活用するゲーム

横スクロールゲームのジャンプや足場の移動は別設計が必要です。目的に合わせて選びましょう。

13. まとめ

3Dの機能だから、3Dにしか使えないとは限りません。 内部はXZ平面の3D、表示はOrthographicで真上から。この切り分けにより、Unity公式NavMesh Agentを2D風ゲームに活用できます。「見せ方」と「動作の仕組み」を分けて考えることが、ゲーム制作の設計力につながります。

参考資料

訪問数 9 回, 今日の訪問数 9回

広告

C#

Posted by hidepon