12「玉転がし」Roll a Ball – AIナビゲーション

AIナビゲーション

プレイヤーの邪魔をする敵を追加します。

敵の作成

Cubeのゲームオブジェクトを追加し敵を作成します。

1. 空のゲームオブジェクトを作成

Hierarchyビューで「右クリック > Create Empty」の順番で選択し、名前を「Enemy」に変更します。

EnemyゲームオブジェクトのTransformコンポーネントをリセットします。

2. 敵用のゲームオブジェクトを作成

HierarchyビューでEnemyゲームオブジェクトを右クリックし、「3D Object > Cube」を選択し、名前を「EnemyBody」に変更します。

EnemyBodyのTransformコンポーネントのScaleを(X: 0.5, Y: 1, Z: 0.5)に設定します。

EnemyBodyのTransformコンポーネントのPositionを(X: 0, Y: 0.5, Z: 0)に設定します。

3. 適用のマテリアルを作成

ProjectビューのMaterialsフォルダを表示します。

Projectビューで「右クリック > Create > Material」の順番に選択しマテリアルを作成し、名前を「Enemy」に変更します。

Enemyマテリアルの色を赤に設定します。

SceneビューまたはHierarchyビューのEnemyBodyゲームオブジェクトにEnemyマテリアルをドラッグ&ドロップします。

NavMeshをベイクする

GroundゲームオブジェクトにNavMeshをベイクし、NavMeshサーフェスに含める設定をします。

1. GroundゲームオブジェクトにNavMeshをベイクする

HierarchyビューのGroundゲームオブジェクトを選択します。

Inspectorビューで「Add Component > NavMesh Surface」を選択しします。

NavMesh SurfaceコンポーネントのBakeをクリックします。

2. NavMeshサーフェスに含める内容を設定

GroundゲームオブジェクトのInspectorビューのNavMesh SurfaceコンポーネントのObject Collectionをクリックし設定を展開します。

Collect Objectsのドロップダウンメニューを開き、「Current Object Hierarchy」を選択しします。

選択したら再度、NavMesh SurfaceコンポーネントのBakeをクリックします。

PickUpゲームオブジェクト、Playerゲームオブジェクト、EnemyゲームオブジェクトはNavMeshでは無視されるようになりました。

敵にプレイヤーを追いかけさせる

新しいプログラミングを作成し、NavMeshエージェント(敵)がプレイヤーを追跡できるようにします。

1. EnemyゲームオブジェクトにNavMeshAgentを追加

HierarchyビューのEnemyゲームオブジェクトを選択します。

Inspectorビューで「Add Component > NavMesh Agent」を選択しします。

NavMesh AgentのSpeedを2.5に設定します。

2. EnemyMovementスクリプトの追加

EnemyゲームオブジェクトのInspectorビューで「Add Component > New script」を選択し、名前を「EnemyMovement」にします。

ProjectビューのAssetsフォルダからEnemyMovementスクリプトファイルをScriptsフォルダに移動させます。

EnemyMovementを開いてください。

3. NavMeshAgentコンポーネントを参照する変数の追加

UnityEngine.AIをインポートします。

EnemyMovement.cs

using UnityEngine;
+ using UnityEngine.AI;

public class EnemyMovement : MonoBehaviour
{
    void Start()
    {
        
    }

    void Update()
    {
        
    }
}

2つのグローバル変数を追加します。

EnemyMovement.cs

using UnityEngine;
using UnityEngine.AI;

public class EnemyMovement : MonoBehaviour
{
+    public Transform player;
+    private NavMeshAgent _navMeshAgent;
+
    void Start()
    {
        
    }

    void Update()
    {
        
    }
}

Start関数でNavMesh Agentコンポーネントを取得し変数に割り当てます。

EnemyMovement.cs

using UnityEngine;
using UnityEngine.AI;

public class EnemyMovement : MonoBehaviour
{
    public Transform player;
    private NavMeshAgent _navMeshAgent;
    
    void Start()
    {
+        _navMeshAgent = GetComponent<NavMeshAgent>();
    }

    void Update()
    {
        
    }
}

4. Enemyゲームオブジェクトの目的地をPlayerゲームオブジェクトに設定

Update関数でEnemyゲームオブジェクトの目的地をPlayerゲームオブジェクトの位置に更新します。

EnemyMovement.cs

using UnityEngine;
using UnityEngine.AI;

public class EnemyMovement : MonoBehaviour
{
    public Transform player;
    private NavMeshAgent _navMeshAgent;
    
    void Start()
    {
        _navMeshAgent = GetComponent<NavMeshAgent>();
    }

    void Update()
    {
+        if (player != null)
+        {
+            _navMeshAgent.SetDestination(player.position);
+        }
    }
}

5. player変数の割当

HierarchyビューでEnemyゲームオブジェクトを選択し、InspectorビューのEnemyMovementコンポーネントのPlayerスロットにHierarchyビューのPlayerゲームオブジェクトをドラッグ&ドロップします。

※この手順は必ず実行してください。ゲームが動作しなくなります。

6. ゲームテスト

Sceneを保存します。

ゲームを実行しEnemyゲームオブジェクトがPlayerを追いかけるようになっているかを確認してください。

静的障害物の作成

NavMeshサーフェスに障害物を組み込みます。

1. さまざまなゲームオブジェクトを作成

Hiearchyビューで「右クリック > 3D Object > Cube」を選択し、オブジェクトの位置、回転、大きさなどを変更してください。

上記手順を繰り返し、さまざまな形やサイズの障害物を作成します。

また、Enemyゲームオブジェクトの斜面登りをテストするので、少なくとも1つのオブジェクトをスロープのようなものにしてください。

2. 障害物をNavMeshサーフェスに含める

Hierarchyビューで、各障害物ゲームオブジェクトをGroundゲームオブジェクトにドラッグ&ドロップし、子ゲームオブジェクトにします。

HierarchyビューでGroundゲームオブジェクトを選択し、InspectorビューのNavMesh SurfaceコンポーネントのBakeをクリックします。障害物を含めた構成で再度NavMeshを生成します。

3. エージェントの設定を変更

GroundゲームオブジェクトのNavMesh SurfaceコンポーネントのAgent Typeドロップダウンから「Open Agent Settings…」を選択します。

Step Heightの値を変更すると、エージェント(敵)がより高い平面に乗ることができます。

Max Slope(最大傾斜)を大きくすると、エージェント(敵)はより急な坂を登ることができます。

4. ゲームテスト

ゲームを実行して、さまざまなエージェントの設定や障害物の構成を試してください。

動的障害物の作成

軽量で移動可能なゲームオブジェクトを作成し、NavMeshの障害物となるようにします。

1. 軽くて移動可能な立方体を作成

Hierarchyビューで「右クリック > 3D Object > Cube」を選択し、名前を「DynamicBox」に変更します。

DynamicBoxの位置と大きさを自由に調整してください。

Projectビューで「右クリック > Create > Material」の順番に選択しマテリアルを作成し、名前を「DynamicObstacle」に変更します。

Enemyマテリアルの色は自由に設定してください。

SceneビューまたはHierarchyビューのDynamicBoxゲームオブジェクトにDynamicObstacleマテリアルをドラッグ&ドロップします。

HierarchyビューでDynamicBoxを選択します。

Inspectorビューで「Add Component > Rigidbody」を選択します。

RigidbodyコンポーネントのMassを0.1に変更します。これにより簡単に推し進めることができます。

ゲームを実行し、EnemyオブジェクトがDynamicBoxをすり抜けることを確認してください。

2. 立方体をNavMeshの障害物に設定

HierarchyビューでDynamicBoxゲームオブジェクトを選択してください。

Inspectorビューで「Add Component > NavMesh Obstacle」を選択してください。

NavMesh ObstacleコンポーネントのCarveを有効にします。

3. 立方体をプレハブ化

HierarchyビューのDynamicBoxゲームオブジェクトをProjectビューのPrefabsフォルダにドラッグ&ドロップします。これによりDynamicBoxゲームオブジェクトのプレハブが作成されます。

必要に応じてDynamicBoxプレハブを使用しSceneのプレイエリア全体に散在させます。

※必要に応じてDyanamicBoxをまとめる空のオブジェクトを作成してください。

4. ゲームテスト

ゲームを実行して、さまざまな障害物の構成を試してください。

勝敗の条件を設定

EnemyオブジェクトがPlayerオブジェクトに触れたらPlayerゲームオブジェクトを破壊して「You Lose!」と表示される敗北条件を追加します。

1. Playerゲームオブジェクトを破壊し「You Lose!」と表示させる条件を追加

PlayerController.csを開きます。

OnCollisionEnter関数を追加します。

PlayerController.cs

...(省略)
public class PlayerContrller : MonoBehaviour
{
    ...(省略)
    void SetCountText()
    {
        ...(省略)
    }
+
+    void OnCollisionEnter(Collision collision)
+    {
+        if (collision.gameObject.CompareTag("Enemy"))
+        {
+            Destroy(gameObject);
+            winTextObject.gameObject.SetActive(true);
+            winTextObject.GetComponent<TextMeshProUGUI>().text = "You Lose!";
+        }
+    }
}

2. EnemyBodyゲームオブジェクトにEnemyタグを追加

HierarchyビューでEnemyBodyゲームオブジェクトを選択します。

Inspectorビューで「Tag」ドロップダウンメニューの「Add Tag…」を選択します。

追加(+)ボタンを選択し、新しいタグ「Enemy」を作成します。

※先程コードで記述したタグ名と大文字/小文字が一致しているか確認してください。

Hierarchyビューで再度EnemyBodyゲームオブジェクトを選択し、InspectorビューでEnemyタグを設定します。

※EnemyゲームオブジェクトではなくEnemyBodyゲームオブジェクトに適用することに注意してください。

ゲームを実行して、EnemyゲームオブジェクトがPlayerゲームオブジェクトに衝突(触れる)とPlayerゲームオブジェクトが破壊され「You Lose!」と表示されることを確認してください。

※必要に応じてEnemyゲームオブジェクトの位置をPlayerゲームオブジェクトから離してください。

3. Playerが勝利したらEnemyゲームオブジェクトを破壊

PlayerController.csを開きます。

全てのPickUpゲームオブジェクトを収集したら、Enemyゲームオブジェクトを破壊する処理を追加します。

PlayerController.cs

...(省略)
public class PlayerContrller : MonoBehaviour
{
    ...(省略)
    void SetCountText()
    {
        countText.text = "Count: " + _count.ToString();

        if (_count >= 12)
        {
            winTextObject.SetActive(true);
+            Destroy(GameObject.FindGameObjectWithTag("Enemy"));
        }
    }
    ...(省略)
}

4. ゲームテスト

ゲームを実行して勝利条件をテストしてくだい。PickUpゲームオブジェクトをすべて収集するとEnemyゲームオブジェクト(EnemyBodyゲームオブジェクト)が破壊されます。

最終スクリプト

PlayerController.cs

using UnityEngine;
// PlayerゲームオブジェクトにアタッチしたPlayerInputからの入力を受け付けるために必要な名前空間
// キーボード/ゲームパッド → Actions(ProjectビューにあるInputSystem_Actionsファイル) →
// PlayerゲームオブジェクトにアタッチしたPlyaerInput → このスクリプト
using UnityEngine.InputSystem;
using TMPro;

public class PlayerContrller : MonoBehaviour
{
    /// <summary>
    /// Playerの移動速度を保存する変数
    /// </summary>
    public float speed = 5f;

    // private(プライベート)で定義する変数の名前は最初に_(アンダーバー)を付けます。
    // private変数は他のスクリプトファイルから値を取得・変更させることができない変数です。

    /// <summary>
    /// PlayerにアタッチされたRigidbodyを保存する変数
    /// </summary>
    private Rigidbody _rb;

    /// <summary>
    /// X方向の移動量を保存する変数
    /// </summary>
    private float _movementX;

    /// <summary>
    /// Y方向の移動量を保存する変数
    /// </summary>
    private float _movementY;

    /// <summary>
    /// 収集されたPickUpゲームオブジェクトの数を保存する変数
    /// </summary>
    private int _count;

    /// <summary>
    /// 収集されたPickUpゲームオブジェクトの数を表示するUIテキストコンポーネント
    /// </summary>
    public TextMeshProUGUI countText;

    /// <summary>
    /// WinTextを表示するゲームオブジェクト
    /// </summary>
    public GameObject winTextObject;

    void Start()
    {
        // PlayerにアタッチされたRigidbodyを取得し変数_rbに保存します。
        _rb = GetComponent<Rigidbody>();
        // ゲーム開始時に_countを0に初期化します。
        _count = 0;
        // カウント表示を更新します。
        SetCountText();
        // ゲーム開始時にWinTextを非アクティブに設定します。
        winTextObject.SetActive(false);
    }

    /// <summary>
    /// プレイヤーのキーボード(WASD)/ゲームパッド(Lスティック)の入力があった際に呼び出される関数です。
    /// </summary>
    /// <param name="movementValue">入力デバイスのX方向とY方向の値を持っています。</param>
    void OnMove(InputValue movementValue)
    {
        // Vecotr2(2次元座標:X座標/Y座標)型の変数movementVecotrにmovementValueをVector2(2次元座標)に変換し保存します。
        Vector2 movementVector = movementValue.Get<Vector2>();

        // movementVectorのX座標・Y座標をそれぞれに変数に保存します。
        _movementX = movementVector.x;
        _movementY = movementVector.y;
    }

    void FixedUpdate()
    {
        // _movmentXをX軸の値に_movmentYをZ軸の値にした3次元座標に変換し変数momventに保存します。
        Vector3 movement = new Vector3(x: _movementX, y: 0.0f, z: _movementY);

        // RigidbodyのAddFrouce関数に3次元座標を渡し、指定した方向に力を加えます。
        // movment(力)にspeed(移動速度)掛け合わせます。
        _rb.AddForce(movement * speed);
    }

    void OnTriggerEnter(Collider other)
    {
        // プレイヤーが衝突したオブジェクトにPickUpタグが付いているか確認します。
        if (other.gameObject.CompareTag("PickUp"))
        {
            // 衝突したオブジェクトを非アクティブ化します。(非表示になる)
            other.gameObject.SetActive(false);
            // _countの数を1増やします。
            _count = _count + 1;
            // カウント表示を更新します。
            SetCountText();
        }
    }

    /// <summary>
    /// 収集されたPickUpゲームオブジェクトの表示数を更新する関数です。
    /// </summary>
    void SetCountText()
    {
        // 現在の数でCountTextのテキストを更新します。
        countText.text = "Count: " + _count.ToString();

        // カウント数が勝利条件に達したかを確認します。
        if (_count >= 12)
        {
            // WinTextゲームオブジェクトを表示します。
            winTextObject.SetActive(true);
            // Enemyゲームオブジェクト(EnemyBodyゲームオブジェクト)を破壊します。
            Destroy(GameObject.FindGameObjectWithTag("Enemy"));
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Enemy"))
        {
            // このゲームオブジェクト(Playerゲームオブジェクト)を破壊します。
            Destroy(gameObject);
            // winTextのテキストを「You Lose!」に書き換え表示します。
            winTextObject.gameObject.SetActive(true);
            winTextObject.GetComponent<TextMeshProUGUI>().text = "You Lose!";
        }
    }
}

EnemyMovement.cs

using UnityEngine;
using UnityEngine.AI;

public class EnemyMovement : MonoBehaviour
{
    /// <summary>
    /// PlayerゲームオブジェクトのTransformコンポーネントを保持する変数
    /// </summary>
    public Transform player;

    /// <summary>
    /// NavMeshAgentコンポーネントを保持する変数
    /// </summary>
    private NavMeshAgent _navMeshAgent;
    
    void Start()
    {
        // このオブジェクトにアタッチされたNavMeshAgentコンポーネントを取得して変数に割当する
        _navMeshAgent = GetComponent<NavMeshAgent>();
    }

    void Update()
    {
        // プレイヤーへの参照がある場合に処理をする
        if (player != null)
        {
            // このオブジェクト(敵)の目的地をプレイヤーの現在位置に設定する
            _navMeshAgent.SetDestination(player.position);
        }
    }
}