Learn how to create a 2D Tower Defense Game in Unity Section3 学習メモ

# 概要
Udemyの講座「Learn how to create a 2D Tower Defense Game in Unity」Section3の学習メモです。
動きを把握するため、クラス図を書いたり実際に動かした際の試行錯誤を記録に残しています。

この講座を選択した理由としては
本講座は英語ですが、Section毎にUnityプロジェクトフォルダが貼られています。なので実際に動かしやすいというメリットがあります。実際に動かせば大抵のことはわかります。

# 開発環境
IDE:Rider
Unity:2020.3.42(LTS)
OS:Windows10

# 実装する動き
タワーデフェンスでは敵が特定の経路に沿って移動します。
今回はその経路と敵スポーン機能を実装します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/5dd51951-ff86-d71b-4cbd-d420a8d780c2.png)


１．敵の経路を作成する→Gizmoクラス
２．Editorのみ見れる自作UI→Editorクラスを作る(赤丸部分と黄色数字の部分)
３．敵がスポーンする機能

# クラス図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/216ce014-3000-8b9a-2a68-89d09d1ddd40.png)


クラス名| 説明
-------|-------
Waypoint|Enemyの移動経路を定義しているクラス。Gizmosクラスを使っている。
WaypointEditor|WaypointクラスのUnityEditor上で表示できるuiを追加。（開発しやすくするために作っている）
Spawner|敵が発生することを定義するクラス。ランダムのタイミングでスポーンするメソッドがある。
ObjectPooler|敵の発生を管理するクラス

# 実装
## UnityEditor上の設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/e99e9659-c0ee-db4e-e43a-4e154321da2d.png)


オブジェクト名|説明
-----------|-----
Spawner|Spawner、Waypointスクリプトがアタッチされている。
Pool-Quad|敵のスポーン状態を管理するオブジェクト。敵の数がスポーンするたびにQuad(Clone)オブジェクトをonにする。
## コード部分

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

public class Waypoint : MonoBehaviour
{
    [SerializeField] private Vector3[] points;

    public Vector3[] Points => points;
    public Vector3 CurrentPosition => _currentPosition;
    
    private Vector3 _currentPosition;
    private bool _gameStarted;
    
    // Start is called before the first frame update
    private void Start()
    {
        _gameStarted = true;
        _currentPosition = transform.position;
    }

    // Update is called once per frame
    private void Update()
    {
        
    }

    private void OnDrawGizmos()
    {
        Debug.Log("<color=blue>WaypointクラスのOnDrawGizmosが実行されました。</color>");
        if (!_gameStarted && transform.hasChanged)
        {
            _currentPosition = transform.position;
        }
        
        for (int i = 0; i < points.Length; i++)
        {
            Gizmos.color = Color.green;
            Gizmos.DrawWireSphere(points[i] + _currentPosition, 0.5f);

            if (i < points.Length - 1)
            {
                Gizmos.color = Color.gray;
                Gizmos.DrawLine(points[i] + _currentPosition, points[i + 1] + _currentPosition);
            }
        }
    }
}

```

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

[CustomEditor(typeof(Waypoint))]
public class WaypointEditor : Editor
{
    Waypoint Waypoint => target as Waypoint;
    private void OnSceneGUI()
    {
        Debug.Log("<color=purple>WaypointEditorクラスのWaypointEditorが実行されました。</color>");
        Handles.color = Color.red;
        for (int i = 0; i < Waypoint.Points.Length; i++)
        {
            EditorGUI.BeginChangeCheck();
            
            // Create Handles
            Vector3 currentWaypointPoint = Waypoint.CurrentPosition + Waypoint.Points[i];
            Vector3 newWaypointPoint = Handles.FreeMoveHandle(currentWaypointPoint,
                Quaternion.identity, 0.7f, 
                new Vector3(0.3f, 0.3f, 0.3f), Handles.SphereHandleCap);
            
            // Create text
            GUIStyle textStyle = new GUIStyle();
            textStyle.fontStyle = FontStyle.Bold;
            textStyle.fontSize = 16;
            textStyle.normal.textColor = Color.yellow;
            Vector3 textAlligment = Vector3.down * 0.35f + Vector3.right * 0.35f;
            Handles.Label(Waypoint.CurrentPosition + Waypoint.Points[i] + textAlligment, 
                $"{i + 1}", textStyle);
            EditorGUI.EndChangeCheck();

            if (EditorGUI.EndChangeCheck())
            {
                Undo.RecordObject(target, "Free Move Handle");
                Waypoint.Points[i] = newWaypointPoint - Waypoint.CurrentPosition;
            }
        }
        
    }
}
```

### Spawner
```diff_c_sharp:Spawner.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public enum SpawnModes
{
    Fixed,
    Random
}

public class Spawner : MonoBehaviour
{
    [Header("Settings")]
    [SerializeField] private SpawnModes spawnMode = SpawnModes.Fixed;
    [SerializeField] private int enemyCount = 10;
    [SerializeField] private GameObject testGO;
    
    [Header("Fixed Delay")]
    [SerializeField] private float delayBtwSpawns;
    
    [Header("Random Delay")]
    [SerializeField] private float minRandomDelay;
    [SerializeField] private float maxRandomDelay;

    private float _spawnTimer;
    private int _enemiesSpawned;

    private ObjectPooler _pooler;

    private void Start()
    {
        _pooler = GetComponent<ObjectPooler>();
    }

    private void Update()
    {
        _spawnTimer -= Time.deltaTime;
        if (_spawnTimer < 0)
        {
            _spawnTimer = GetSpawnDelay();
            if (_enemiesSpawned < enemyCount)
            {
                _enemiesSpawned++;
                SpawnEnemy();
            }
        }
    }

    private void SpawnEnemy()
    {
        Debug.Log("<color=yellow>SpawnerクラスのSpawnEnemyが実行されました。</color>");
        GameObject newInstance = _pooler.GetInstanceFromPool();
        newInstance.SetActive(true);
    }

    private float GetSpawnDelay()
    {
        Debug.Log("<color=yellow>SpawnerクラスのGetSpawnDelayが実行されました。</color>");
        float delay = 0f;
        if (spawnMode == SpawnModes.Fixed)
        {
            delay = delayBtwSpawns;
        }
        else
        {
            delay = GetRandomDelay();
        }

        return delay;
    }
    
    private float GetRandomDelay()
    {
        Debug.Log("<color=yellow>SpawnerクラスのGetRandomDelayが実行されました。</color>");
        float randomTimer = Random.Range(minRandomDelay, maxRandomDelay);
        return randomTimer;
    }
}

```

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

public class ObjectPooler : MonoBehaviour
{
    [SerializeField] private GameObject prefab;
    [SerializeField] private int poolSize;

    private List<GameObject> _pool;
    private GameObject _poolContainer;

    private void Awake()
    {
        _pool = new List<GameObject>();
        _poolContainer = new GameObject($"Pool - {prefab.name}");
        
        CreatePooler();
    }

    private void CreatePooler()
    {
        Debug.Log("<color=green>ObjectPoolerクラスのCreatePoolerが実行されました。</color>");
        for (int i = 0; i < poolSize; i++)
        {
            _pool.Add(CreateInstance());
        }
    }
    
    private GameObject CreateInstance()
    {
        Debug.Log("<color=green>ObjectPoolerクラスのCreateInstanceが実行されました。</color>");
        GameObject newInstance = Instantiate(prefab);
        newInstance.transform.SetParent(_poolContainer.transform);
        newInstance.SetActive(false);
        return newInstance;
    }

    public GameObject GetInstanceFromPool()
    {
        Debug.Log("<color=green>ObjectPoolerクラスのGetInstanceFromPoolが実行されました。</color>");
        for (int i = 0; i < _pool.Count; i++)
        {
            if (!_pool[i].activeInHierarchy)
            {
                return _pool[i];
            }
        }
        
        return CreateInstance();
    }
}

```

# 動かして確認
## gifアニメ
ゲームウィンドウのときはOnDrawGizmosメソッドは実行されませんが、
シーンに切り替えると実行されます。
![towerSection345.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/920cb189-083a-69f6-a46e-3435ca3fb300.gif)

## ログ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/81aef7ad-a0b7-7d69-5802-a7ce5c2fc2b4.png)

上のログから以下のことがわかります。
ObjectPooloer.CreatePoolerとObjectPooler.CreateInstance :ゲーム開始時に実行Poolオブジェクトを作成

Spawner.GetSpawnDelay：Poolerがオンオフ関わらず実行される
Spawner.SpawnEnemy、ObjectPooler.GetInstanceFromPool:Poolerをオンにする。上限値が10回なので呼び出しも10回
Waypoint.OnDrawGizmos：Gizmosを描画するメソッド


# 参考

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