Learn how create 2D Idle Clicker Game in Unity Section9（完） 学習メモ

# 概要
Udemyの講座 Learn how create 2D Idle Clicker Game in Unity Section9の学習メモです。
動きを把握するため、クラス図を書いたり実際に動かした際の試行錯誤を記録に残しています。
この講座に関しては本投稿が最後になります。

内容はソースコード中心になります。


# 開発環境

IDE:Rider
Unity:2019.4.40
OS:Windows10

# 実装するもの
Manager機能を追加します。
Managerは全体にバスを掛ける機能があります。
今回はShaftのManagerにフォーカスを当てます。

![section920.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/a346a0b4-12a7-0846-3c95-476f2af2e5f1.gif)




# クラス図
全体図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/62f3bd1a-b8b1-fe33-54d7-f49ea365b15d.png)



クラス名| 説明
-------|----
WorkManagerController|メイン処理部分。具体的にはManagerの雇用、Assign,Unassign,Managerがクリックされた時など。Actionを使ってイベントメソッドがあるクラス（BaseWorkMangerとWorkManagerCard）の実処理部分を実装している。（UIとロジックの分離ができてる）
WorkManagerInfo.cs|Managerのパラメーターを定義するクラス。継承元はScriptableObject
WorkManagerCard|WorkManagerInfoのScriptableObjectをuGuiに渡している。Assignのボタンを押した時のイベントメソッドがある。
BaseWorkManager| Managerの上の描画関係の処理、Managerのクリックイベントを定義
ShaftWorkManager| BaseWorkManagerの継承先のクラス。クラスの宣言のみ定義されているだけで中身の処理が何もない。GameObjectにアタッチする用のクラスの印象を受ける。
Shaft|既存のクラス。MineLocationインターフェイスで定義したメソッドの処理内容とcreateManagerメソッドが追加されている。
MinerLocation|インターフェイス。BaseWorkManagerと同じファイルにある（何でファイルを分けないんだ？）

# 実装
## UnityEditor上の設定
### Managerを押す前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/782d6ad9-52e4-1b58-031b-8d97ed10437a.png)
オブジェクト名|説明
--------|-------
WorkManagerController|WorkManagerControllerスクリプトがアタッチされている。
ShaftManagerLocation|Minerが動ける左端　コンポーネントのアイコンを使っているのでゲーム画面には表示されない
ShaftManager(Clone)| ShaftWorkManagerスクリプトがアタッチされている。


### Managerを押した後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/d5119da9-d873-8cab-5437-675a3ab2694f.png)



オブジェクト名|説明
--------|-------
Close-Button|WorkManagerController.OpenCloseMa がアタッチされている。
Assign-Button|WorkManagerCard.AssignManagerがアタッチされている。
Hire-Button|WorkManagerController.HireManagerがアタッチされている。
## コード部分

#### WorkManagerController
```diff_c_sharp:WorkManagerController.cs
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
using Random = UnityEngine.Random;

public class WorkManagerController : Singleton<WorkManagerController>
{
    [SerializeField] private GameObject managerCardPrefab;
    [SerializeField] private Transform managerPanelContainer;
    [SerializeField] private List<WorkManagerInfo> availableManagers;
    [SerializeField] private GameObject managerPanel;
    
    [Header("Assign Manager Card")]
    [SerializeField] private GameObject assignManagerCard;
    [SerializeField] private Image boostIcon;
    [SerializeField] private TextMeshProUGUI managerType;
    [SerializeField] private TextMeshProUGUI boostDuration;
    [SerializeField] private TextMeshProUGUI boostDescription;

    [SerializeField] private float initialManagerCost = 200;
    [SerializeField] private float managerCostMultiplier = 200;
    [SerializeField] private TextMeshProUGUI hireCost;
    
    public BaseWorkManager CurrentWorkManagerSelected { get; set; }
    public float CurrentManagerCost { get; set; }
    
    private List<WorkManagerCard> _workManagerCardsAssigned = new List<WorkManagerCard>();

    private void Start()
    {
        CurrentManagerCost = initialManagerCost;
    }

    private void Update()
    {
        hireCost.text = CurrentManagerCost.ToString();
    }

    public void OpenCloseManagerPanel(bool status)
    {
        Debug.Log("<color=green>WorkManagerControllerクラスのOpenCLoseManagerPanelが実行されました。</color>");
        managerPanel.SetActive(status);
    }

    #region Boost

    public void RunMovementBoost(BaseMiner miner, float duration, float value)
    {
        StartCoroutine(IEMovementBoost(miner, duration, value));
    }

    public void RunLoadingBoost(BaseMiner miner, float duration, float value)
    {
        StartCoroutine(IELoadingBoost(miner, duration, value));
    }
    
    private IEnumerator IEMovementBoost(BaseMiner miner, float duration, float value)
    {
        Debug.Log("<color=green>WorkManagerControllerクラスのIEMovementBoostが実行されました。</color>");
        float startMoveSpeed = miner.MoveSpeed;
        miner.MoveSpeed /= value;
        yield return new WaitForSeconds(duration * 60);
        miner.MoveSpeed = startMoveSpeed;
    }

    private IEnumerator IELoadingBoost(BaseMiner miner, float duration, float value)
    {
        Debug.Log("<color=green>WorkManagerControllerクラスのIELoadingBoostが実行されました。</color>");
        float startCollectPerSecond = miner.CollectPerSecond;
        miner.CollectPerSecond *= value;
        yield return new WaitForSeconds(duration * 60);
        miner.CollectPerSecond = startCollectPerSecond;
    }
    
    #endregion
    
    public void HireManager()
    {
        Debug.Log("<color=green>WorkManagerControllerクラスのHireManagerが実行されました。</color>");
        if (availableManagers.Count > 0 && GoldManager.Instance.CurrentGold >= CurrentManagerCost)
        {
            GameObject managerCardGO = Instantiate(managerCardPrefab, managerPanelContainer);
            WorkManagerCard managerCard = managerCardGO.GetComponent<WorkManagerCard>();
            int randomManagerIndex = Random.Range(0, availableManagers.Count);
            WorkManagerInfo managerInfo = availableManagers[randomManagerIndex];
            managerCard.SetupWorkManagerCard(managerInfo);
            availableManagers.RemoveAt(randomManagerIndex);
            
            GoldManager.Instance.RemoveGold(CurrentManagerCost);
            CurrentManagerCost *= managerCostMultiplier;
        }
    }
    
    public void UnassignManager()
    {
        Debug.Log("<color=green>WorkManagerControllerクラスのUnassignManagerが実行されました。</color>");
        RestoreManagerCardAssigned();
        UpdateAssignManagerCard();
    }
    
    private void RestoreManagerCardAssigned()
    {
        Debug.Log("<color=green>WorkManagerControllerクラスのRestoreManagerCardAssignedが実行されました。</color>");
        WorkManagerCard managerCardAssigned = null;
        for (int i = 0; i < _workManagerCardsAssigned.Count; i++)
        {
            if (CurrentWorkManagerSelected.ManagerAssigned == _workManagerCardsAssigned[i].ManagerInfoAssigned)
            {
                managerCardAssigned = _workManagerCardsAssigned[i];
            }
        }

        if (managerCardAssigned != null)
        {
            managerCardAssigned.gameObject.SetActive(true);
            _workManagerCardsAssigned.Remove(managerCardAssigned);
            CurrentWorkManagerSelected.ManagerAssigned = null;
        }
    }
    
    private void UpdateAssignManagerCard()
    {
        Debug.Log("<color=green>WorkManagerControllerクラスのUpdateAssignManagerCardが実行されました。</color>");
        if (CurrentWorkManagerSelected.ManagerAssigned != null)
        {
            assignManagerCard.SetActive(true);
            boostIcon.sprite = CurrentWorkManagerSelected.ManagerAssigned.BoostIcon;
            managerType.text = CurrentWorkManagerSelected.ManagerAssigned.ManagerType.ToString();
            managerType.color = CurrentWorkManagerSelected.ManagerAssigned.LevelColor;
            boostDuration.text = $"Duration: {CurrentWorkManagerSelected.ManagerAssigned.BoostDuration.ToString()}";
            boostDescription.text = CurrentWorkManagerSelected.ManagerAssigned.BoostDescription;
        }
        else
        {
            assignManagerCard.SetActive(false);
        }
    }
    
    private void ManagerClicked(MineLocation location)
    {
        Debug.Log("<color=green>WorkManagerControllerクラスのManagerClickedが実行されました。</color>");
        if (location is Shaft shaft)
        {
            CurrentWorkManagerSelected = shaft.WorkManager;
        }
        else if (location is Elevator elevator)
        {
            CurrentWorkManagerSelected = elevator.WorkManager;
        }
        else if (location is Warehouse warehouse)
        {
            CurrentWorkManagerSelected = warehouse.WorkManager;
        }

        if (CurrentWorkManagerSelected.ManagerAssigned == null)
        {
            assignManagerCard.SetActive(false);
        }
        else
        {
            assignManagerCard.SetActive(true);
        }
        
        UpdateAssignManagerCard();
        OpenCloseManagerPanel(true);
    }

    private void WorkManagerCardAssigned(WorkManagerCard managerCard)
    {
        Debug.Log("<color=green>WorkManagerControllerクラスのWorkManagerCardAssignedが実行されました。</color>");
        _workManagerCardsAssigned.Add(managerCard);
        CurrentWorkManagerSelected.ManagerAssigned = managerCard.ManagerInfoAssigned;
        CurrentWorkManagerSelected.SetupBoostButton();
        UpdateAssignManagerCard();
    }
    
    private void OnEnable()
    {
        BaseWorkManager.OnManagerClicked += ManagerClicked;
        WorkManagerCard.OnAssignRequest += WorkManagerCardAssigned;
    }

    private void OnDisable()
    {
        BaseWorkManager.OnManagerClicked -= ManagerClicked;
        WorkManagerCard.OnAssignRequest -= WorkManagerCardAssigned;
    }
}

```

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

public enum ManagerType
{
    Junior,
    Senior,
    Executive
}

public enum BoostType
{
    Movement,
    Loading
}

[CreateAssetMenu]
public class WorkManagerInfo : ScriptableObject
{
    [Header("Manager Info")]
    public ManagerType ManagerType;
    public Color LevelColor;

    public BoostType BoostType;
    public Sprite BoostIcon;
    public float BoostDuration;
    public string BoostDescription;
    public float BoostValue;
}

```

#### WorkManagerCard
```diff_c_sharp:WorkManagerCard.cs
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class WorkManagerCard : MonoBehaviour
{
    public static Action<WorkManagerCard> OnAssignRequest;
    
    [SerializeField] private Image boostIcon;
    [SerializeField] private TextMeshProUGUI managerType;
    [SerializeField] private TextMeshProUGUI boostDuration;
    [SerializeField] private TextMeshProUGUI boostDescription;

    public WorkManagerInfo ManagerInfoAssigned { get; set; }
    
    public void SetupWorkManagerCard(WorkManagerInfo managerInfo)
    {
        ManagerInfoAssigned = managerInfo;
        boostIcon.sprite = managerInfo.BoostIcon;
        managerType.text = managerInfo.ManagerType.ToString();
        managerType.color = managerInfo.LevelColor;
        boostDuration.text = $"Duration: {managerInfo.BoostDuration}";
        boostDescription.text = managerInfo.BoostDescription;
    }

    public void AssignManager()
    {
        OnAssignRequest?.Invoke(this);
        gameObject.SetActive(false);
    }
}

```

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

public interface MineLocation
{
    void ApplyManagerBoost();
}

public class BaseWorkManager : MonoBehaviour
{
    [SerializeField] private GameObject boostButton;
    [SerializeField] private Image boostIcon;
    
    public MineLocation CurrentMineLocation { get; set; }
    public WorkManagerInfo ManagerAssigned { get; set; }
    public static Action<MineLocation> OnManagerClicked;

    private void Start()
    {
        Debug.Log("<color=red>BaseWorkManagerクラスのStartが実行されました。</color>");
        HideBoostButton();
    }

    public void RunBoost()
    {
        Debug.Log("<color=red>BaseWorkManagerクラスのRunBoostが実行されました。</color>");
        CurrentMineLocation?.ApplyManagerBoost();
    }
    
    private void OnMouseDown()
    {
        Debug.Log("<color=red>BaseWorkManagerクラスのOnMouseDownが実行されました。</color>");
        OnManagerClicked?.Invoke(CurrentMineLocation);
    }

    private void HideBoostButton()
    {
        Debug.Log("<color=red>BaseWorkManagerクラスのHideBoostButtonが実行されました。</color>");
        boostButton.SetActive(false);
    }

    public void SetupBoostButton()
    {
        Debug.Log("<color=red>BaseWorkManagerクラスのSetupBoostButtonが実行されました。</color>");
        if (ManagerAssigned != null)
        {
            boostButton.SetActive(true);
            boostIcon.sprite = ManagerAssigned.BoostIcon;
        }
    }
}

```
#### ShaftWorkManager
```diff_c_sharp:ShaftWorkManager.cs
using UnityEngine;

public class ShaftWorkManager : BaseWorkManager
{
    
}
```

#### Shaft
```diff_c_sharp:Shaft.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;

public class Shaft : MonoBehaviour, MineLocation
{
    [Header("Prefab")] 
    [SerializeField] private ShaftMiner minerPrefab;
    [SerializeField] private Deposit depositPrefab;

    [Header("Manager")]
    [SerializeField] private ShaftWorkManager shaftManagerPrefab;
    [SerializeField] private Transform shaftManagerPosition;
    
    [Header("Locations")] 
    [SerializeField] private Transform miningLocation;
    [SerializeField] private Transform depositLocation;
    [SerializeField] private Transform depositCreationLocation;

    public int ShaftID { get; set; }
    public Transform MiningLocation => miningLocation;
    public Transform DepositLocation => depositLocation;
    public Deposit ShaftDeposit { get; set; }
    public ShaftUI ShaftUI { get; set; }
    public List<ShaftMiner> Miners => _miners;
    public ShaftWorkManager WorkManager { get; set; }
    
    private List<ShaftMiner> _miners = new List<ShaftMiner>();
    
    private void Awake()
    {
        ShaftUI = GetComponent<ShaftUI>();
    }

    private void Start()
    {
        CreateMiner();
        CreateDeposit();
        CreateManager();
    }

    public void CreateMiner()
    {
         ShaftMiner newMiner = Instantiate(minerPrefab, depositLocation.position, quaternion.identity);
         newMiner.CurrentShaft = this;
         newMiner.transform.SetParent(transform);

         if (_miners.Count > 0)
         {
             newMiner.CollectCapacity = _miners[0].CollectCapacity;
             newMiner.CollectPerSecond = _miners[0].CollectPerSecond;
             newMiner.MoveSpeed = _miners[0].MoveSpeed;
         }
         
         _miners.Add(newMiner);
    }

    private void CreateDeposit()
    {
        
        ShaftDeposit = Instantiate(depositPrefab, depositCreationLocation.position, quaternion.identity);
        ShaftDeposit.transform.SetParent(transform);
    }

+    private void CreateManager()
+    {
+        Debug.Log("<color=yellow>ShaftクラスのCreateManagerが実行されました。</color>");
+        WorkManager = Instantiate(shaftManagerPrefab, shaftManagerPosition.position, +quaternion.identity);
+        WorkManager.transform.SetParent(transform);
+        WorkManager.CurrentMineLocation = this;
+    }

+    public void ApplyManagerBoost()
+    {
+        Debug.Log("<color=yellow>ShaftクラスのApplyManagerBoostが実行されました。</color>");
+        switch (WorkManager.ManagerAssigned.BoostType)
+        {
+            case BoostType.Movement:
+                foreach (ShaftMiner miner in _miners)
+                {
+                    WorkManagerController.Instance.RunMovementBoost(miner, 
+                        WorkManager.ManagerAssigned.BoostDuration,WorkManager.ManagerAssigned.BoostValue);
+                }
+                break;
+            
+            case BoostType.Loading:
+                foreach (ShaftMiner miner in _miners)
+                {
+                    WorkManagerController.Instance.RunLoadingBoost(miner, 
+                        WorkManager.ManagerAssigned.BoostDuration, +WorkManager.ManagerAssigned.BoostValue);
+                }
+                break;
+        }
+    }
+ }
```




# 動きを確認
## Managerをクリックする

### gifアニメ
![section91.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/d2916919-79a4-ad60-6c6b-88a8e0b0ebfa.gif)

### コンソールから見たデバッグ画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/ccd72f9c-3b36-0ca8-7cf8-5a37c208a7dd.png)

OnMouseDown→Managerを押した時に実行される関数。ObserverパターンでWorkManagerCntroller.ManagerClickedが実行される。（イベントと実処理部分を分離したいからか？）
ManagerClicked→押されたManagerの種類を識別する。(Shaft or Elevator or Warehouse)
AssignManagerCard→ Manager関係のUIの値、画像を更新する。
OpenCloseManagerPanel→ ManagerUIを表示する

## Hireボタンを押してAssignを押す


### gifアニメ
![section92.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/d64d8dc5-5d40-88ba-98c9-ec5cd0969a3c.gif)

### コンソールから見たデバッグ画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/c48e4daf-cd5f-2da5-fc5e-f5017a924ecd.png)

雇用する
HireManager→UI上では新しいマネージャーのカードを表示、所持金を引いています

雇用したManagerをAssignする。
WorkManagerCardAssinged→選択したManagerCardをAssign側に追加している。
SetupBoostButton→Managerの上のアイコンを更新する
AssignManagerCard→ManagerCardを非表示にする



# 参考

Section 9

https://www.udemy.com/course/learn-how-to-create-a-2d-idle-clicker-game-in-unity-2021/