タワーデフェンスゲーム　Unity タレットアプデ機能 2/2　通貨システムの実装

# 概要
目次

https://qiita.com/RYA234/private/8985b1fe637bcd6fccac



今回は通貨システムを実装します。
通貨パラメーターを追加して、タレットを強化する時に10減少し敵を倒した時に1増加するようにします。
以下gifアニメは
Dボタンを押してタレットを強化する際に通貨が10減少し敵を倒した時に１増加しています。
尚通貨はprivate変数なのでインスペクターのデバッグモードで確認します。

![currency.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/e9e2135a-ad39-8d7e-013b-b141a6a6b708.gif)



# 開発環境

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

# UnityEditor上の設定
新規オブジェクトCurrencySystemを作成して　CurrencySystemスクリプトをアタッチします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/c5bd2ed0-f201-597f-2348-c35c03a3deef.png)


# 実装のポイント
１．通貨システムを実装
新規クラスにCurrencySysytemクラスを作成し、TurretUpgradeクラスにメソッドを追加します。


下図は敵が死亡した時に通貨が得られるときの処理(Observeパターン)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/71ae07d0-d124-c02b-9195-aafcf95d5526.png)

下図はタレットをアプデした時に通過が減る時の処理

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/b9b57e5d-b86b-9325-0bc9-c1adc5fdad03.png)


通貨の値はPlayerPrefsというクラスを使って操作します。PlayerPrefsはゲームデータのセーブ・ロードを行うクラスのようです。


2.どのEnemyがKillされたか識別するためにActionの引数にEnemyクラスを加える。
既存クラスSpawner LevelManager EnemyHealth Enemyで一部改修を行います。


# コード部分
## Enemy
```diff_c_sharp:Enemy.cs
using System;
using UnityEngine;

public class Enemy : MonoBehaviour
{
-    public static Action OnEndReached;
+    public static Action<Enemy> OnEndReached;

    [SerializeField] private float moveSpeed = 3f;
    public Waypoint Waypoint { get; set; }
    public Vector3 CurrentPointPosition => Waypoint.GetWaypointPosition(_currentWaypointIndex);
    private int _currentWaypointIndex;
    private EnemyHealth _enemyHealth;
    public EnemyHealth EnemyHealth { get; set; }
    
    
    private void Start()
    {
        _currentWaypointIndex = 0;
        _enemyHealth = GetComponent<EnemyHealth>();
        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
        {
            EndPointReached();
            
        }
    }

    
    private void EndPointReached()
    { 
-         OnEndReached?.Invoke();
+        OnEndReached?.Invoke(this);
        _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;
+    public static Action<Enemy> 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;
+    private Enemy _enemy;
    void Start()
    {
        CreateHealthBar();
        CurrentHealth = initialHealth;

+        _enemy = GetComponent<Enemy>();
    }

    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();
+        OnEnemyKilled?.Invoke(_enemy);
        CurrentHealth = initialHealth;
        _healthBar.fillAmount = 1f;
        ObjectPooler.ReturnToPool(gameObject);
    }
    
    
}



```


## CurrencySystem
```diff_c_sharp:CurrencySystem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CurrencySystem : Singleton<CurrencySystem>
{
    [SerializeField] private int coinTest;
    private string CURRENCY_SAVE_KEY = "MYGAME_CURRENCY";
    
    
    public int TotalCoins { get; set; }
    
    void Start()
    {
        PlayerPrefs.DeleteKey(CURRENCY_SAVE_KEY);
        LoadCoins();
    }

    private void LoadCoins()
    {
        TotalCoins = PlayerPrefs.GetInt(CURRENCY_SAVE_KEY, coinTest);
    }

    public void AddCoins(int amount)
    {
        TotalCoins += amount;
        PlayerPrefs.SetInt(CURRENCY_SAVE_KEY,TotalCoins);
        PlayerPrefs.Save();
    }

    public void RemoveCoins(int amount)
    {
        if (TotalCoins >= amount)
        {
            TotalCoins -= amount;
            PlayerPrefs.SetInt(CURRENCY_SAVE_KEY,TotalCoins);
            PlayerPrefs.Save();
        }
    }


    private void AddCoins(Enemy enemy)
    {
        AddCoins(1);
    }

    private void OnEnable()
    {
        EnemyHealth.OnEnemyKilled += AddCoins;
    }

    private void OnDisable()
    {
        EnemyHealth.OnEnemyKilled -= AddCoins;
    }
}



```


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


public class LevelManager : MonoBehaviour
{
    [SerializeField] private int lives = 10;

    public int TotalLives { get; set; }
    private void Start()
    {
        TotalLives = lives;
    }
-    private void ReduceLives()
+    private void ReduceLives(Enemy enemy)
    {
        TotalLives--;
        if (TotalLives <= 0)
        {
            TotalLives = 0;
        }
    }
    private void OnEnable()
    {
        Enemy.OnEndReached += ReduceLives;  
    }

    private void OnDisable()
    {
        Enemy.OnEndReached -= ReduceLives;  
    }
}



```


## 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 RecordEnemy(Enemy enemy)
        {
            _enemiesRemaining--;
            if (_enemiesRemaining <= 0)
            {
                StartCoroutine(NextWave());
            }
        }

        private void OnEnable()
        {
            Enemy.OnEndReached += RecordEnemy;
            EnemyHealth.OnEnemyKilled += RecordEnemy;
        }


        private void OnDisable()
        {
            Enemy.OnEndReached -= RecordEnemy;
            EnemyHealth.OnEnemyKilled -= RecordEnemy;
        }
    }



```


## TurretUpgrade
```diff_c_sharp:TurretUpgrade.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TurretUpgrade : MonoBehaviour
{
    
    [SerializeField] private int upgradeInitialCost;
    [SerializeField] private int upgradeCostIncremental;
    [SerializeField] private float damageIncremental;
    [SerializeField] private float delayReduce;
    private TurretProjectTile _turretProjectTile;
    
+    public int UpgradeCost { get; set;}
    private void Start()
    {
        _turretProjectTile = GetComponent<TurretProjectTile>();
+        UpgradeCost = upgradeInitialCost;
    }

    // Update is called once per frame
   private void Update()
   {
       if(Input.GetKeyDown(KeyCode.D))
       {
           UpgradeTurret();
       }
   }

   private void UpgradeTurret()
   {
+       if (CurrencySystem.Instance.TotalCoins >= UpgradeCost)
+       {
           _turretProjectTile.Damage += damageIncremental;
           _turretProjectTile.DelayPerShot -= delayReduce;
+           UpdateUpgrade();
+       }
   }

+   private void UpdateUpgrade()
+   {
+       CurrencySystem.Instance.RemoveCoins(UpgradeCost);
+       UpgradeCost += upgradeCostIncremental;
+   }
}



```

# 参考

PlayerPrefs

https://docs.unity3d.com/ja/2019.4/ScriptReference/PlayerPrefs.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/d0a26c97-d508-7108-d855-f2cd46c66f8d.png)



Section6 36

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


github コミット分

https://github.com/RYA234/2DTowerDeffenceMyLearning/commit/01602d32f19d7a33b211a46ebf69c6c440cc9511#diff-ba1b2e6833c9b53e7aa9903ebe56f96109b1704cfb2691f214ffca616dff8a1a