【Unity】タワーディフェンス(18) ウェーブの実装【クソゲー制作】

タワーディフェンスを作る(18)

Unity初心者が無謀にも2Dタワーディフェンスゲームを制作しています。今回はウェーブ機能の実装です。

環境
  • Mac mini (M1, 2020)
  • Unity 2022.3.36f1
目次

ウェーブの設定を管理する

ウェーブの設定データをどのように管理しましょうか。ScriptableObject, JSON, CSVなどが思いつきますね。コードにベタ書きすることもできます。今回はScriptableObjectを使うことにしました。

STEP

WaveSettingスクリプトを作成します。

using System;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "WaveSetting", menuName = "ScriptableObject/Wave Setting")]
public class WaveSetting : ScriptableObject
{
    // ウェーブのデータを格納するリスト
    [SerializeField] private List<WaveData> waveDataList = new List<WaveData>();
    public List<WaveData> WaveDataList => waveDataList;

    // ウェーブのデータ構造
    [Serializable]
    public class WaveData
    {
        [SerializeField] private int stageId; // ステージID
        public int StageId => stageId;

        [SerializeField] private int waveId; // ウェーブID
        public int WaveId => waveId;

        [SerializeField] private string enemyId; // 敵の種類
        public string EnemyId => enemyId;

        [SerializeField] private int enemyCount; // 敵の数
        public int EnemyCount => enemyCount;

        [SerializeField] private float spawnInterval; // 出現間隔
        public float SpawnInterval => spawnInterval;

        [SerializeField] private PathData path; // 移動経路
        public PathData Path => path;
    }
}

ウェーブ設定のパラメータは以下のようにしました。

  • ステージID
  • ウェーブID
  • 敵の種類
  • 敵の数
  • 出現間隔
  • 移動経路
STEP

DBManagerスクリプトを修正して、ウェーブの設定データを他のスクリプトから扱えるようにします。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DBManager : MonoBehaviour
{
    // DBManagerのインスタンスを保持する静的変数
    public static DBManager Instance { get; private set; }

    // 敵の設定データを保持する変数
    [SerializeField] private EnemySetting enemySetting;
    public EnemySetting EnemySetting => enemySetting;

    // 砲台の設定データを保持する変数
    [SerializeField] private TurretSetting turretSetting;
    public TurretSetting TurretSetting => turretSetting;

    // ここから 追加
    // ウェーブの設定データを保持する変数
    [SerializeField] private WaveSetting waveSetting;
    public WaveSetting WaveSetting => waveSetting;
    // ここまで

    void Awake()
    {
        // ...省略...
    }
}
STEP

「Assets/Data」フォルダで右クリック「Create > ScriptabelObject > Wave Setting」を選択して、WaveSettingアセットを作成します。

タワーディフェンス194
STEP

WaveSettingのインスペクターでウェーブデータを登録していきます。

タワーディフェンス195

ウェーブを実装する

ウェーブを発生させる

STEP

GameManagerスクリプトを修正します。

using UnityEngine;
using UnityEngine.EventSystems;

public class GameManager : MonoBehaviour
{
    [SerializeField] private SideBarManager sideBarManager;
    [SerializeField] private GameObject tileHighlighter; // ハイライト用オブジェクト
    public GameObject TileHighlighter => tileHighlighter; // tileHighlighterにアクセスするためのプロパティ
    private Vector2 tileSize = new Vector2(1, 1); // マス目のサイズ(必要に応じて変更)
    // ここから 追加
    private int currentStageId = 1; // 現在のステージID
    public int CurrentStageId => currentStageId;
    // ここまで

    // ...省略...
}

現在のステージIDを入れておく変数を追加しました。

今のところ1で固定ですが、あとでプレイするステージに応じて値が変わるようにします。

STEP

EnemySpawnerスクリプトを修正します。

using System.Collections;
// ここから 追加
using System.Collections.Generic; // Listを使うために必要
// ここまで
using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] private EnemyController enemyPrefab; // 敵のプレハブ
    // ここから 削除
    //[SerializeField] private PathData[] pathDataArray; // 移動経路情報の配列
    //[SerializeField] private int maxSpawnCount; // 敵の最大生成数
    //[SerializeField] private float spawnInterval; // 敵を生成する間隔(単位は秒)
    // ここまで
    [SerializeField] private GameManager gameManager; // GameManagerへの参照
    // ここから 削除
    //private int spawnedEnemyCount = 0; // これまでに生成された敵の数
    //private bool isSpawning = false; // 敵を生成するかどうかを制御するフラグ
    // ここまで
    // ここから 追加
    [SerializeField] private WaveSetting waveSetting; // ウェーブ設定
    [SerializeField] private EnemySetting enemySetting; // 敵の設定
    private int currentWaveId = 0; // 現在のウェーブID
    private List<WaveSetting.WaveData> currentStageWaves; // 現在のステージのウェーブリスト
    // ここまで

    private void Start()
    {
        // ここから 削除
        //isSpawning = true; // 敵を生成可能にする
        //StartCoroutine(ManageEnemySpawning());
        // ここまで
        // ここから 追加
        // 現在のステージに対応するウェーブをフィルタリング
        currentStageWaves = waveSetting.WaveDataList.FindAll(wave => wave.StageId == gameManager.CurrentStageId);

        if (currentStageWaves.Count == 0)
        {
            Debug.LogError($"ステージID {gameManager.CurrentStageId} に対応するウェーブが見つかりません。");
            return;
        }

        StartCoroutine(ManageWaves());
        // ここまで
    }

    // ここから 削除
    /// <summary>
    /// 敵の生成管理
    /// </summary>
    //private IEnumerator ManageEnemySpawning()
    //{
    //    while (isSpawning) // 敵を生成可能ならば
    //    {
    //        yield return new WaitForSeconds(spawnInterval); // 指定した秒数だけ待機
    //        SpawnEnemy(); // 敵生成
    //        AddEnemyToList(); // 敵の情報をListに追加
    //        CheckSpawnLimit();  // 最大生成数を超えたら敵の生成停止
    //    }
    //}
    // ここまで

    // ここから 追加
    /// <summary>
    /// ウェーブを管理するコルーチン
    /// </summary>
    private IEnumerator ManageWaves()
    {
        while (currentWaveId < currentStageWaves.Count)
        {
            // 現在のウェーブデータを取得
            WaveSetting.WaveData currentWave = currentStageWaves[currentWaveId];

            // ウェーブ開始
            Debug.Log($"Wave {currentWave.WaveId} 開始");
            yield return StartCoroutine(SpawnWave(currentWave));

            // 次のウェーブまで30秒待機
            yield return new WaitForSeconds(30f);

            // 次のウェーブに進む
            currentWaveId++;
        }

        Debug.Log("すべてのウェーブが終了しました!");
    }

    /// <summary>
    /// 指定されたウェーブの敵を生成する
    /// </summary>
    /// <param name="waveData">ウェーブデータ</param>
    private IEnumerator SpawnWave(WaveSetting.WaveData waveData)
    {
        for (int i = 0; i < waveData.EnemyCount; i++)
        {
            SpawnEnemy(waveData);
            yield return new WaitForSeconds(waveData.SpawnInterval);
        }
    }
    // ここまで

    /// <summary>
    /// 敵を生成する
    /// </summary>
    // ここから 追加
    /// <param name="waveData">ウェーブデータ</param>
    // ここまで
    // ここから 修正
    //private void SpawnEnemy()
    private void SpawnEnemy(WaveSetting.WaveData waveData)
    // ここまで
    {
        // ここから 追加
        // EnemyId に基づいて敵データを取得
        EnemySetting.EnemyData enemyData = enemySetting.EnemyDataList.Find(e => e.EnemyId == waveData.EnemyId);
        if (enemyData == null)
        {
            Debug.LogError($"EnemyId '{waveData.EnemyId}' に対応する敵データが見つかりません。");
            return;
        }
        // ここまで
        // ここから 削除
        // ランダムな経路を選択
        //PathData selectedPath = pathDataArray[Random.Range(0, pathDataArray.Length)];
        // ここまで
        // 経路のスタート地点にプレハブから敵を生成
        // ここから 修正
        //EnemyController enemyController = Instantiate(enemyPrefab, selectedPath.StartPosition.position, Quaternion.identity);
        EnemyController enemyController = Instantiate(enemyPrefab, waveData.Path.StartPosition.position, Quaternion.identity);
        // ここまで
        // ここから 削除
        // ランダムな敵を選択
        //int enemyId = Random.Range(0, DBManager.Instance.EnemySetting.EnemyDataList.Count);
        // ここまで
        // 敵データの初期化
        // ここから 修正
        //enemyController.InitializeEnemy(selectedPath, gameManager, DBManager.Instance.EnemySetting.EnemyDataList[enemyId]);
        enemyController.InitializeEnemy(waveData.Path, gameManager, enemyData);
        // ここまで
    }

    // ここから 削除
    /// <summary>
    /// 敵の情報をListに追加
    /// </summary>
    //private void AddEnemyToList()
    //{
    //    spawnedEnemyCount++; // 生成した敵の数を増やす
    //}

    /// <summary>
    /// 敵の生成が上限に達したかを確認
    /// </summary>
    //private void CheckSpawnLimit()
    //{
    //    if (spawnedEnemyCount >= maxSpawnCount) // 敵の最大生成数を超えたら
    //    {
    //        isSpawning = false; // 敵を生成不可にする
    //    }
    //}
    // ここまで
}
  • 以下の変数は不要になるので削除しました。
    • pathDataArray: 移動経路情報の配列
    • maxSpawnCount: 敵の最大生成数
    • spawnInterval: 敵を生成する間隔
    • spawnedEnemyCount: これまでに生成された敵の数
    • isSpawning: 敵を生成するかどうかを制御するフラグ
  • 以下の変数を追加しました。
    • waveSetting: ウェーブ設定
    • enemySetting: 敵の設定
    • currentWaveId: 現在のウェーブID
    • currentStageWaves: 現在のステージのウェーブリスト
  • Start()ManageEnemySpawning()を呼び出すのをやめて、ManageWaves()を呼び出しました。また、現在のステージのウェーブリストをcurrentStageWavesに代入しました。
  • ManageEnemySpawning()コルーチンは使わなくなったので削除します。
  • 代わりにManageWaves()コルーチンを追加しました。
    • このコルーチンは、currentStageWavesリストに入っているウェーブ情報に従って30秒おきにウェーブを生成します。
    • ウェーブの生成はSpawnWave()コルーチンに任せています。
  • SpawnWave()コルーチンを追加しました。
    • このコルーチンは、引数として渡されたウェーブ情報に従ってSpawnInterval秒おきに敵を生成します。
    • 敵の生成はSpawnEnemy()メソッドが行います。
  • SpawnEnemy()メソッドを修正しました。
    • 引数としてwaveDataを取るようにしました。
    • waveDataを元に敵のデータを取得して敵を生成するようにしました。
  • AddEnemyToList()メソッドは使わなくなったので削除しました。
STEP

EnemySpawnerオブジェクトのインスペクターで「Enemy Spawner (Script) > Wave Setting」「Data > WaveSetting」を、「Enemy Setting」「EnemySetting」をドラッグ&ドロップしてアサインします。

タワーディフェンス196

以上で、30秒おきにウェーブが発生するようになりました。いえい。

UIの表示

今のところ、ウェーブが発生してもウェーブ関連のUIは動いていません。今、何ウェーブ中の何ウェーブ目なのか、次のウェーブまであと何秒なのかの表示を実装します。

STEP

EnemySpawnerスクリプトを修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// ここから 追加
using TMPro; // TextMeshProを使うために必要
// ここまで


public class EnemySpawner : MonoBehaviour
{
    [SerializeField] private EnemyController enemyPrefab; // 敵のプレハブ
    [SerializeField] private GameManager gameManager; // GameManagerへの参照
    [SerializeField] private WaveSetting waveSetting; // ウェーブ設定
    [SerializeField] private EnemySetting enemySetting; // 敵の設定
    private int currentWaveId = 0; // 現在のウェーブID
    private List<WaveSetting.WaveData> currentStageWaves; // 現在のステージのウェーブリスト
    // ここから 追加
    [SerializeField] private TextMeshProUGUI waveText; // ウェーブテキスト
    [SerializeField] private TextMeshProUGUI timeToNextWaveText; // 次のウェーブまでの時間テキスト
    private float timeRemaining; // 残り時間を管理するフィールド
    // ここまで

    private void Start()
    {
        // ...省略...
    }

    /// <summary>
    /// ウェーブを管理するコルーチン
    /// </summary>
    private IEnumerator ManageWaves()
    {
        while (currentWaveId < currentStageWaves.Count)
        {
            // 現在のウェーブデータを取得
            WaveSetting.WaveData currentWave = currentStageWaves[currentWaveId];

            // ここから 追加
            // ウェーブテキストを更新
            waveText.text = $"{currentWaveId + 1}/{currentStageWaves.Count}";
            // ここまで

            // ウェーブ開始
            Debug.Log($"Wave {currentWave.WaveId} 開始");
            // ここから 修正
            //yield return StartCoroutine(SpawnWave(currentWave));

            // 次のウェーブまで30秒待機
            //yield return new WaitForSeconds(30f);

            // 敵の生成を開始
            StartCoroutine(SpawnWave(currentWave));

            // カウントダウンを開始
            yield return StartCoroutine(StartCountdown(30f));
            // ここまで

            // 次のウェーブに進む
            currentWaveId++;
        }

        Debug.Log("すべてのウェーブが終了しました!");
    }

    // ここから 追加
    /// <summary>
    /// 次のウェーブまでのカウントダウンを開始する
    /// </summary>
    /// <param name="duration">カウントダウンの秒数</param>
    private IEnumerator StartCountdown(float duration)
    {
        timeRemaining = duration;
        while (timeRemaining > 0)
        {
            timeToNextWaveText.text = $": {Mathf.CeilToInt(timeRemaining)}s";
            yield return new WaitForSeconds(1f);
            timeRemaining -= 1f;
        }
    }
    // ここまで

    // ...省略...
}
  • 以下の変数を追加しました。
    • waveText: 現在のウェーブ番号を表示するテキスト
    • timeToNextWaveText: 次のウェーブまでの時間を表示するテキスト
    • timeRemaining: 残り時間を管理するフィールド
  • ManageWaves()waveTextを更新します。
  • 30秒の待機をStartCountdown()コルーチンに任せることにしました。
  • StartCountdown()コルーチンを追加しました。
    • 残り時間(timeRemaining)を減少させながら、次のウェーブまでの時間(timeToNextWaveText)を更新します。
STEP

EnemySpawnerオブジェクトのインスペクターで「Enemy Spawner (Script) > Wave Text」「WaveText」オブジェクトを、「Time To Next Wave Text」「TimeToNextWaveText」オブジェクトをドラッグ&ドロップしてアサインします。

タワーディフェンス197

次のウェーブボタン

「次のWAVE」ボタンを押したら、30秒を待たずに次のウェーブが開始するようにします。

STEP

EnemySpawnerスクリプトを修正します。

using System.Collections;
using System.Collections.Generic; // Listを使うために必要
using UnityEngine;
// ここから 追加
using UnityEngine.EventSystems;
using UnityEngine.UI;
// ここまで
using TMPro; // TextMeshProを使うために必要


public class EnemySpawner : MonoBehaviour
{
    [SerializeField] private EnemyController enemyPrefab; // 敵のプレハブ
    [SerializeField] private GameManager gameManager; // GameManagerへの参照
    [SerializeField] private WaveSetting waveSetting; // ウェーブ設定
    [SerializeField] private EnemySetting enemySetting; // 敵の設定
    private int currentWaveId = 0; // 現在のウェーブID
    private List<WaveSetting.WaveData> currentStageWaves; // 現在のステージのウェーブリスト
    [SerializeField] private TextMeshProUGUI waveText; // ウェーブテキスト
    [SerializeField] private TextMeshProUGUI timeToNextWaveText; // 次のウェーブまでの時間テキスト
    private float timeRemaining; // 残り時間を管理するフィールド
    // ここから 追加
    [SerializeField] private Button nextWaveButton; // 次のウェーブボタン
    private Coroutine countdownCoroutine; // カウントダウンコルーチンの参照
    private bool isWaveSkipped = false; // ウェーブがスキップされたかどうかを判定
    // ここまで

    private void Start()
    {
        // 現在のステージに対応するウェーブをフィルタリング
        currentStageWaves = waveSetting.WaveDataList.FindAll(wave => wave.StageId == gameManager.CurrentStageId);

        if (currentStageWaves.Count == 0)
        {
            Debug.LogError($"ステージID {gameManager.CurrentStageId} に対応するウェーブが見つかりません。");
            return;
        }

        // ここから 追加
        // ボタンにクリックイベントを登録
        nextWaveButton.onClick.AddListener(SkipToNextWave);
        // ここまで

        StartCoroutine(ManageWaves());
    }

    /// <summary>
    /// ウェーブを管理するコルーチン
    /// </summary>
    private IEnumerator ManageWaves()
    {
        while (currentWaveId < currentStageWaves.Count)
        {
            // 現在のウェーブデータを取得
            WaveSetting.WaveData currentWave = currentStageWaves[currentWaveId];

            // ウェーブテキストを更新
            waveText.text = $"{currentWaveId + 1}/{currentStageWaves.Count}";

            // ここから 追加
            // 最後のウェーブの場合、ボタンを無効化
            if (currentWaveId == currentStageWaves.Count - 1)
            {
                nextWaveButton.interactable = false;
            }
            // ここまで

            // ウェーブ開始
            Debug.Log($"Wave {currentWave.WaveId} 開始");

            // 敵の生成を開始
            StartCoroutine(SpawnWave(currentWave));

            // カウントダウンを開始
            // ここから 修正
            //yield return StartCoroutine(StartCountdown(30f));
            countdownCoroutine = StartCoroutine(StartCountdown(30f));
            // ここまで

            // ここから 追加
            // カウントダウンが終了するか、ボタンでスキップされるまで待機
            while (countdownCoroutine != null && !isWaveSkipped)
            {
                yield return null;
            }
            // ここまで

            // 次のウェーブに進む
            currentWaveId++;
            // ここから 追加
            isWaveSkipped = false; // スキップ状態をリセット
            // ここまで
        }

        Debug.Log("すべてのウェーブが終了しました!");
    }

    /// <summary>
    /// 次のウェーブまでのカウントダウンを開始する
    /// </summary>
    /// <param name="duration">カウントダウンの秒数</param>
    private IEnumerator StartCountdown(float duration)
    {
        timeRemaining = duration;
        while (timeRemaining > 0)
        {
            // ここから 追加
            if (isWaveSkipped) yield break; // スキップされた場合、カウントダウンを終了
            // ここまで
            timeToNextWaveText.text = $": {Mathf.CeilToInt(timeRemaining)}s";
            yield return new WaitForSeconds(1f);
            timeRemaining -= 1f;
        }
        // ここから 追加
        countdownCoroutine = null; // カウントダウンを終了
        // ここまで
    }

    // ...省略...

    // ここから 追加
    /// <summary>
    /// 次のウェーブへスキップする
    /// </summary>
    private void SkipToNextWave()
    {
        if (countdownCoroutine != null)
        {
            StopCoroutine(countdownCoroutine); // カウントダウンを停止
            countdownCoroutine = null;
        }
        isWaveSkipped = true; // スキップフラグを設定

        // ボタンの選択状態を解除
        EventSystem.current.SetSelectedGameObject(null);
    }
    // ここまで
}
  • 以下の変数を追加しました。
    • nextWaveButton: 次のウェーブボタン
    • countdownCoroutine: カウントダウンコルーチンの参照
    • isWaveSkipped : ウェーブがスキップされたかどうかを判定するフラグ
  • Start()nextWaveButtonにクリックイベントを登録しました。
  • ManageWaves()を修正しました。
    • 最後のウェーブの場合、ボタンを無効化する処理の追加。
    • StartCountdown()コルーチンをcountdownCoroutine変数に代入。
    • カウントダウンが終了するか、ボタンでスキップされるまで待機する処理を追加。
    • isWaveSkippedをリセット。
  • StartCountdown()を修正しました。
    • スキップされた場合、カウントダウンを終了する処理を追加。
    • カウントダウンが終了したときにcountdownCoroutinenullを代入。
  • SkipToNextWave()メソッドを追加しました。
    • ボタンが押されたらカウントダウンを停止して、countdownCoroutinenullを代入。
    • isWaveSkippedtrueを代入。
    • ボタンの選択状態を解除する。
STEP

EnemySpawnerオブジェクトのインスペクターで「Enemy Spawner (Script) > Next Wave Button」NextWaveButtonオブジェクトをドラッグ&ドロップしてアサインします。

タワーディフェンス198

ウェーブ関連の報酬

ウェーブの実装ができたので、ウェーブ関連の報酬を獲得する処理を追加します。

  • ウェーブをスキップしたら、次のウェーブまでの残り時間に応じてゴールドを獲得。
  • ウェーブ開始時に、現在の城のHPに応じてスコアを獲得。

ウェーブをスキップしたときの報酬

EnemySpawnerスクリプトを修正します。

// ...省略...

public class EnemySpawner : MonoBehaviour
{
    // ...省略...

    /// <summary>
    /// 次のウェーブへスキップする
    /// </summary>
    private void SkipToNextWave()
    {
        if (countdownCoroutine != null)
        {
            StopCoroutine(countdownCoroutine); // カウントダウンを停止
            countdownCoroutine = null;
        }
        isWaveSkipped = true; // スキップフラグを設定

        // ここから 追加
        // 残り時間に応じてゴールドを獲得
        float goldMultiplier = 1f; // 1秒あたりのゴールド獲得量
        int goldEarned = Mathf.CeilToInt(timeRemaining * goldMultiplier);
        GoldManager.Instance.AddGold(goldEarned);
        Debug.Log($"次のウェーブをスキップしました。獲得ゴールド: {goldEarned}");
        // ここまで

        // ボタンの選択状態を解除
        EventSystem.current.SetSelectedGameObject(null);
    }
}

ウェーブをスキップしたときに、残り時間1秒につき1ゴールドを獲得する処理を追加しました。

次のウェーブが始まったときの報酬

STEP

EnemySpawnerスクリプトを修正します。

// ...省略...

public class EnemySpawner : MonoBehaviour
{
    // ...省略...

    /// <summary>
    /// ウェーブを管理するコルーチン
    /// </summary>
    private IEnumerator ManageWaves()
    {
        while (currentWaveId < currentStageWaves.Count)
        {
            // 現在のウェーブデータを取得
            WaveSetting.WaveData currentWave = currentStageWaves[currentWaveId];

            // ウェーブテキストを更新
            waveText.text = $"{currentWaveId + 1}/{currentStageWaves.Count}";

            // 最後のウェーブの場合、ボタンを無効化
            if (currentWaveId == currentStageWaves.Count - 1)
            {
                nextWaveButton.interactable = false;
            }

            // ここから 追加
            // 第1ウェーブ以外の場合、城のHPに応じてスコアを加算
            if (currentWaveId > 0)
            {
                int scoreEarned = FortressController.Instance.CurrentHp * 10; // HP × 10 のスコアを加算
                ScoreManager.Instance.AddScore(scoreEarned);
                Debug.Log($"ウェーブ {currentWaveId + 1} 開始時にスコア {scoreEarned} を獲得しました。");
            }
            // ここまで

            // ウェーブ開始
            Debug.Log($"Wave {currentWave.WaveId} 開始");

            // 敵の生成を開始
            StartCoroutine(SpawnWave(currentWave));

            // カウントダウンを開始
            countdownCoroutine = StartCoroutine(StartCountdown(30f));

            // カウントダウンが終了するか、ボタンでスキップされるまで待機
            while (countdownCoroutine != null && !isWaveSkipped)
            {
                yield return null;
            }

            // 次のウェーブに進む
            currentWaveId++;
            isWaveSkipped = false; // スキップ状態をリセット
        }

        Debug.Log("すべてのウェーブが終了しました!");
    }

    // ...省略...
}

第1ウェーブ以外のウェーブが開始したときに、城のHP×10のスコアを獲得する処理を追加しました。

STEP

FortressControllerスクリプトを修正します。

using UnityEngine;
using UnityEngine.UI;

public class FortressController : MonoBehaviour
{
    // ここから 追加
    // シングルトンインスタンス
    public static FortressController Instance { get; private set; }
    // ここまで
    [SerializeField, Header("最大HP")] private int maxHp = 20; // 防衛拠点の最大HP
    [SerializeField, Header("現在のHP")] private int currentHp; // 防衛拠点の現在のHP
    // ここから 追加
    public int CurrentHp => currentHp; // 現在のHPを取得するプロパティ
    // ここまで
    [SerializeField, Header("HP表示スライダー")] private Slider hpSlider; // HPを表示するスライダー

    // ここから 追加
    private void Awake()
    {
        // シングルトンパターンの実装
        if (Instance == null)
        {
            Instance = this;
        }
        else if (Instance != this)
        {
            Destroy(gameObject);
        }
    }
    // ここまで

    // ...省略...
}

城の現在のHP(currentHp)をEnemySpawnerから参照するために、シングルトンパターンを実装しました。

動作確認

ゲームを実行して動作確認をします。以下の機能が実装されていることがわかります。

  • トップバーに現在のウェーブ番号と次のウェーブまでのカウントダウンが表示される。
  • カウントダウンが終わると次のウェーブが始まる。
  • 「次のWAVE」ボタンが押されるとカウントダウンの途中でも次のウェーブが始まる。
  • ウェーブをスキップしたらゴールドを獲得。
  • ウェーブ開始時にスコアを獲得(第1ウェーブを除く)。

さいごに

正直なところ、このウェーブの実装が難関だと思っていたので、うまくいってホッとしております。完成が見えてきましたね。

でわでわ

タワーディフェンスを作る(18)

この記事が気に入ったら
いいね または フォローしてね!

シェアしてね

コメント

コメントする

目次