タワーデフェンスゲーム　Unity　敵の機能　10/10　敵オブジェクトがKillされた場合でも、次ウェイブで湧くようにする

# 概要

前回の続きです。

https://qiita.com/RYA234/items/34c1a2eaa069691f58c6

今回は1Wave目で敵がKillされた場合、2Wave以降沸かなくなるバグを修正します。
以下gifアニメは、改善後の様子です。
敵がkillされても、次のWaveで湧いていることを確認できます。

![NextWave.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/d3515a51-79e6-0d37-32e2-465608d78e32.gif)




# 開発環境

IDE:Rider
Unity:2020.3.42(LTS)
OS:Windows10

# UnityEditor上の設定
前回と変化なし

# 実装のポイント

EnemyHealthクラスについて
デザインパターンのObserveパターンを使っています。役割はObservaleです。
HPが0になった時にObserverに通知するようになってます。


Spawnerクラスについて
デザインパターンのObserveパターンを使っています。役割はObserverです。
ObservaleのSpawnerから通知後、次Waveで湧く処理を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/0790ebb0-547e-0edb-2dec-7a2bfcebab93.png)


Enemyクラスについて
メソッド名の変更とHPの初期化処理を追加するぐらい

# コード部分

## Enemy
```diff_c_sharp:Enemy.cs
using System;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public static Action OnEndReached;
    
    [SerializeField] private float moveSpeed = 3f;
    public Waypoint Waypoint { get; set; }
    public Vector3 CurrentPointPosition => Waypoint.GetWaypointPosition(_currentWaypointIndex);
    private int _currentWaypointIndex;
+    private EnemyHealth _enemyHealth;
    
    private void Start()
    {
        _currentWaypointIndex = 0;
+        _enemyHealth = GetComponent<EnemyHealth>();
    }

    private void Update()
    {
        Move();
        if (CurrentPointPositionReached())
        {
            UpdateCurrentPointIndex();
        }
    }

    private void Move()
    {
        Vector3 currentPosition = Waypoint.GetWaypointPosition(_currentWaypointIndex);
        transform.position = Vector3.MoveTowards(transform.position, currentPosition, moveSpeed * Time.deltaTime);
    }
    
    private bool CurrentPointPositionReached()
    {
        return Vector3.Distance(transform.position, CurrentPointPosition) < 0.1f;
    }

    private void UpdateCurrentPointIndex()
    {
        int lastWaypointIndex = Waypoint.Points.Length - 1;
        if (_currentWaypointIndex < lastWaypointIndex)
        {
            _currentWaypointIndex++;
        }
        else
        {
-             ReturnEnemyToPool();
+            EndPointReached();
            
        }
    }

-    private void ReturnEnemyToPool()
+    private void EndPointReached()
    { 
        OnEndReached?.Invoke();
+        _enemyHealth.ResetHealth();
        ObjectPooler.ReturnToPool(gameObject);
    }

    public void ResetEnemy()
    {
        _currentWaypointIndex = 0;
    }
}


```

## EnemyHealth

```diff_c_sharp:EnemyHealth.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine;
using UnityEngine.UI;

public class EnemyHealth : MonoBehaviour
{
+    public static Action OnEnemyKilled;
    
    // Start is called before the first frame update
    [SerializeField] private GameObject healthBarPrefab;
    [SerializeField] private Transform barPosition;

    [SerializeField] private float initialHealth = 10f;
    [SerializeField] private float maxHealth = 10f;

    public float CurrentHealth { get; set; }
    private Image _healthBar;
    void Start()
    {
        CreateHealthBar();
        CurrentHealth = initialHealth;
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            DealDamage(5f);
        }

        _healthBar.fillAmount = Mathf.Lerp(_healthBar.fillAmount, CurrentHealth / maxHealth, Time.deltaTime * 10f);
    }

    private void CreateHealthBar()
    {
        GameObject newBar = Instantiate(healthBarPrefab,barPosition.position,Quaternion.identity);
        newBar.transform.SetParent(transform);
        
        EnemyHealthContainer container = newBar.GetComponent<EnemyHealthContainer>();
        _healthBar = container.FillAmountImage;
    }

    public void DealDamage(float damageReceived)
    {
        CurrentHealth -= damageReceived;
        if (CurrentHealth <= 0)
        {
            CurrentHealth = 0;
            Die();
        }
    }

+    public void ResetHealth()
+    {
+        CurrentHealth = initialHealth;
+        _healthBar.fillAmount = 1f;
+    }
    private void Die()
    {
+        OnEnemyKilled?.Invoke();
        CurrentHealth = initialHealth;
        _healthBar.fillAmount = 1f;
        ObjectPooler.ReturnToPool(gameObject);
    }
    
    
}



```


## Spawner

```diff_c_sharp:Spawner.cs
using System;
using System.Collections;
using UnityEngine;


    public class Spawner : MonoBehaviour
    {
        [SerializeField]  private  int enemyCount = 10;
        // Btw = Between
        [SerializeField] private float delayBtwSpawns;
        [SerializeField] private float delayBtwWaves = 1f;

        private int _enemiesRemaining;
        
        private int _enemiesSpawned;
        private float _spawnTimer;
        private ObjectPooler _objectPooler;
        private Waypoint _waypoint;

        private void Start()
        {
            _objectPooler = GetComponent<ObjectPooler>();
            _waypoint = GetComponent<Waypoint>();
            
            _enemiesRemaining = enemyCount;
        }

        private void Update()
        {
            _spawnTimer -= Time.deltaTime;
            if (_spawnTimer < 0)
            {
                _spawnTimer = delayBtwSpawns;
                if (_enemiesSpawned < enemyCount)
                {
                    SpawnEnemy();
                    _enemiesSpawned++;
                }
            }
        }
        
        // ReSharper disable Unity.PerformanceAnalysis
        private void SpawnEnemy()
        {
            GameObject newInstance = _objectPooler.GetInstanceFromPool();
            Enemy enemy = newInstance.GetComponent<Enemy>();
            enemy.Waypoint = _waypoint;
            enemy.transform.localPosition = transform.position;
            enemy.ResetEnemy();
            newInstance.SetActive(true);
        }

        private IEnumerator NextWave()
        {
            yield return new WaitForSeconds(delayBtwWaves);
            _enemiesRemaining = enemyCount;
            _spawnTimer = 0f;
            _enemiesSpawned = 0;

        }
 
-        private void RecordEnemyEndReached()
+        private void RecordEnemy()
        {
            _enemiesRemaining--;
            if (_enemiesRemaining <= 0)
            {
                StartCoroutine(NextWave());
            }
        }

        private void OnEnable()
        {
-            Enemy.OnEndReached += RecordEnemyEndReached;
+            Enemy.OnEndReached += RecordEnemy;

+            EnemyHealth.OnEnemyKilled += RecordEnemy;
        }


        private void OnDisable()
        {
-             Enemy.OnEndReached -= RecordEnemyEndReached;
+            Enemy.OnEndReached -= RecordEnemy;

+            EnemyHealth.OnEnemyKilled -= RecordEnemy;
        }
    }



```


# 参考

Section4 18

https://www.udemy.com/course/learn-how-to-create-a-2d-tower-defense-game-in-unity-2020/learn/lecture/23020844#content

github コミット分

https://github.com/RYA234/2DTowerDeffenceMyLearning/commit/0546e7aa117079232960fc5d7259a509c656662a


章まとめ

https://qiita.com/RYA234/private/6d878a7958a3c760c8af