0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Learn how create 2D Idle Clicker Game in Unity Section6 学習メモ

Last updated at Posted at 2023-03-05

概要

Learn how create 2D Idle Clicker Game in Unity Section6の学習メモです。
動きを把握するため、クラス図を書いたり実際に動かした際の試行錯誤を記録に残しています

開発環境

IDE:Rider
Unity:2019.4.40
OS:Windows10

実装したい動き

aaaaaaaaaaa.gif

1.Upgrade用のUIを開く。
2.Upgradeボタンを押すとMinerがUpgradeされる。

クラス図

image.png

クラス名 説明
BaseUpgrade Upgradeクラス、今後実装予定のElevator Warehouseの継承元にもなります。
ShaftUpgrade 継承元はBaseUpgrade。オーバライドしている関数はExecuteUpgradeのみ
UpgradePanelInfo UpgradeUIを構成する画像類を束ねているクラス。ScriptableObjectを作成
UpgradeManager UpgradeUIを開閉する際に使われるクラス。
ShaftUI uGui関係のクラス。今回の場合だとUpgradeUIの表示、更新ボタン押した後の画面更新を担当

これで合ってるの?Observerパターンが2つあるから困惑してます。

赤枠のObserverパターン
Upgradeを実行する処理に使われている。
BaseUpgrade(Observale)で現在のレベルの内部処理が行われ、ShaftUI(Observer)でUI側に反映しています。

黄枠のObserverパターン
UpgradeUIを開く処理に使われている。
ボタンを押すと、ShaftUI(Observale)のメソッドが実行され、UpgradeManagerクラス(Observer)で内部処理を行う。

イベント処理(ShaftUI)と内部処理(Upgrade)を分離することが目的と考えられる。
イベント処理と内部処理の依存関係を逆転させるために使っている感じがある。

実装

UnityEditor上の設定

Upgradesボタン押す前

image.png

オブジェクト名 説明
UpgradeManager UpgradeManagerスクリプトがアタッチされている。
Shaft Shaft,ShaftUI,ShaftUpgradeスクリプトがアタッチされる。
Upgarde-Button UpgradeUIを開くボタン。ShaftUI.OpenUpgradeContainerメソッドがアタッチされている。

UPgradesボタン押した後

image.png

オブジェクト名 説明
Close-Button UIを閉じる。UpgradeManager.OpenCloseUpgradeContainerメソッドがアタッチされている。
Upgarde-Button Upgradeを実行するボタン。UpgradeManager.Upgradeがアタッチされている。

コード(とても長いのでスキップ推奨 クラス図を先に見て必要な項目を参照)

UpgradeManager

UpgradeManager.cs
using System;
using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class UpgradeManager : MonoBehaviour
{
    [SerializeField] private GameObject upgradeContainer;
    [SerializeField] private Image panelMinerImage;
    [SerializeField] private TextMeshProUGUI panelTitle;
    [SerializeField] private TextMeshProUGUI level;
    [SerializeField] private TextMeshProUGUI nextBoost;
    [SerializeField] private TextMeshProUGUI upgradeCost;
    [SerializeField] private Image progressBar;

    [Header("Upgrade Buttons")]
    [SerializeField] private GameObject[] upgradeButtons;
    [SerializeField] private Color buttonDisableColor;
    [SerializeField] private Color buttonEnableColor;
    
    [Header("Stat Title")] 
    [SerializeField] private TextMeshProUGUI stat1Title;
    [SerializeField] private TextMeshProUGUI stat2Title;
    [SerializeField] private TextMeshProUGUI stat3Title;
    [SerializeField] private TextMeshProUGUI stat4Title;
    
    [Header("Stat Values")] 
    [SerializeField] private TextMeshProUGUI stat1CurrentValue;
    [SerializeField] private TextMeshProUGUI stat2CurrentValue;
    [SerializeField] private TextMeshProUGUI stat3CurrentValue;
    [SerializeField] private TextMeshProUGUI stat4CurrentValue;

    [Header("Stat Upgrade Values")] 
    [SerializeField] private TextMeshProUGUI stat1UpgradeValue;
    [SerializeField] private TextMeshProUGUI stat2UpgradeValue;
    [SerializeField] private TextMeshProUGUI stat3UpgradeValue;
    [SerializeField] private TextMeshProUGUI stat4UpgradeValue;
    
    [Header("Stat Icon")] 
    [SerializeField] private Image stat1Icon;
    [SerializeField] private Image stat2Icon;
    [SerializeField] private Image stat3Icon;
    [SerializeField] private Image stat4Icon;

    [Header("Panel Info")] 
    [SerializeField] private UpgradePanelInfo shaftMinerInfo;

    public int UpgradeAmount { get; set; }
    
    private Shaft _currentShaft;
    private ShaftUpgrade _currentShaftUpgrade;
    private int _currentActiveButton;
    
    public void OpenCloseUpgradeContainer(bool status)
    {
        Debug.Log("<color=green>UpgradeManagerクラスのOpenCloseUpgradeContainerが実行されました。</color>");
        UpgradeX1();
        upgradeContainer.SetActive(status);
    }

    public void Upgrade()
    {
        if (GoldManager.Instance.CurrentGold >= _currentShaftUpgrade.UpgradeCost)
        {
            _currentShaftUpgrade.Upgrade(UpgradeAmount);
            UpdateShaftPanelValues();
            RefreshUpgradeAmount();
        }
    }

    #region Upgrade Buttons

    public void UpgradeX1()
    {
        ActivateButton(0);
        UpgradeAmount = CanUpgradeManyTimes(1, _currentShaftUpgrade) ? 1 : 0;
        upgradeCost.text = GetUpgradeCost(1, _currentShaftUpgrade).ToString();
    }

    public void UpgradeX10()
    {
        ActivateButton(1);
        UpgradeAmount = CanUpgradeManyTimes(10, _currentShaftUpgrade) ? 10 : 0;
        upgradeCost.text = GetUpgradeCost(10, _currentShaftUpgrade).ToString();
    }
    
    public void UpgradeX50()
    {
        ActivateButton(2);
        UpgradeAmount = CanUpgradeManyTimes(50, _currentShaftUpgrade) ? 50 : 0;
        upgradeCost.text = GetUpgradeCost(50, _currentShaftUpgrade).ToString();
    }
    
    public void UpgradeMax()
    {
        ActivateButton(3);
        UpgradeAmount = CalculateUpgradeCount(_currentShaftUpgrade);
        upgradeCost.text = GetUpgradeCost(UpgradeAmount, _currentShaftUpgrade).ToString();
    }

    private int CalculateUpgradeCount(BaseUpgrade upgrade)
    {
        int count = 0;
        float currentGold = GoldManager.Instance.CurrentGold;
        float currentUpgradeCost = upgrade.UpgradeCost;

        if (GoldManager.Instance.CurrentGold >= currentUpgradeCost)
        {
            for (float i = currentGold; i >= 0; i -= currentUpgradeCost)
            {
                count++;
                currentUpgradeCost *= upgrade.UpgradeCostMultiplier;
            }
        }

        return count;
    }

    private bool CanUpgradeManyTimes(int upgradeAmount, BaseUpgrade upgrade)
    {
        int count = CalculateUpgradeCount(upgrade);
        if (count >= upgradeAmount)
        {
            return true;
        }

        return false;
    }

    private float GetUpgradeCost(int amount, BaseUpgrade upgrade)
    {
        float cost = 0f;
        float currentUpgradeCost = upgrade.UpgradeCost;

        for (int i = 0; i < amount; i++)
        {
            cost += currentUpgradeCost;
            currentUpgradeCost *= upgrade.UpgradeCostMultiplier;
        }

        return cost;
    }
    
    private void ActivateButton(int buttonIndex)
    {
        for (int i = 0; i < upgradeButtons.Length; i++)
        {
            upgradeButtons[i].GetComponent<Image>().color = buttonDisableColor;
        }

        _currentActiveButton = buttonIndex;
        upgradeButtons[buttonIndex].GetComponent<Image>().color = buttonEnableColor;
        upgradeButtons[buttonIndex].transform.DOPunchPosition(transform.localPosition +
                                                              new Vector3(0f, -5f, 0f), 0.5f).Play();
    }

    private void RefreshUpgradeAmount()
    {
        switch (_currentActiveButton)
        {
            case 0:
                UpgradeX1();
                break;
            case 1:
                UpgradeX10();
                break;
            case 2:
                UpgradeX50();
                break;
            case 3:
                UpgradeMax();
                break;
        }
    }
    
    #endregion
    
    private void UpdateUpgradeInfo()
    {
        Debug.Log("<color=green>UpgradeManagerクラスのUpdateUpgradeInfoが実行されました。</color>");
        panelTitle.text = shaftMinerInfo.PanelTitle;
        panelMinerImage.sprite = shaftMinerInfo.PanelMinerIcon;

        stat1Title.text = shaftMinerInfo.Stat1Title;
        stat2Title.text = shaftMinerInfo.Stat2Title;
        stat3Title.text = shaftMinerInfo.Stat3Title;
        stat4Title.text = shaftMinerInfo.Stat4Title;

        stat1Icon.sprite = shaftMinerInfo.Stat1Icon;
        stat2Icon.sprite = shaftMinerInfo.Stat2Icon;
        stat3Icon.sprite = shaftMinerInfo.Stat3Icon;
        stat4Icon.sprite = shaftMinerInfo.Stat4Icon;
    }

    //uGuiの処理を値を更新
    private void UpdateShaftPanelValues()
    {
        Debug.Log("<color=green>UpgradeManagerクラスのUpdateShaftPanelValuesが実行されました。</color>");
        upgradeCost.text = _currentShaftUpgrade.UpgradeCost.ToString();
        level.text = $"Level {_currentShaftUpgrade.CurrentLevel}";
        progressBar.DOFillAmount(_currentShaftUpgrade.GetNextBoostProgress(), 0.5f).Play();
        nextBoost.text = $"Next Boost at Level {_currentShaftUpgrade.BoostLevel}";
        
        // Current Values
        stat1CurrentValue.text = $"{_currentShaft.Miners.Count}";
        stat2CurrentValue.text = $"{_currentShaft.Miners[0].MoveSpeed}";
        stat3CurrentValue.text = $"{_currentShaft.Miners[0].CollectPerSecond}";
        stat4CurrentValue.text = $"{_currentShaft.Miners[0].CollectCapacity}";
        
        // Miner stat
        stat1UpgradeValue.text = (_currentShaftUpgrade.CurrentLevel + 1) % 10 == 0 ? "+1" : "+0";

        // Move Speed
        float minerMoveSpeed = _currentShaft.Miners[0].MoveSpeed;
        float walkSpeedUpgraded = Mathf.Abs(minerMoveSpeed - (minerMoveSpeed * _currentShaftUpgrade.MoveSpeedMultiplier));
        stat2UpgradeValue.text = (_currentShaftUpgrade.CurrentLevel + 1) % 10 == 0 ? $"+{walkSpeedUpgraded}/s" : "+0";

        // Collect Per Second
        float minerCollectPerSecond = _currentShaft.Miners[0].CollectPerSecond;
        float collectPerSecondUpgraded = Mathf.Abs(minerCollectPerSecond - (minerCollectPerSecond * _currentShaftUpgrade.CollectPerSecondMultiplier));
        stat3UpgradeValue.text = $"+{collectPerSecondUpgraded}";
        
        // Collect Capacity
        float minerCollectCapacity = _currentShaft.Miners[0].CollectCapacity;
        float collectCapacityUpgraded = Mathf.Abs(minerCollectCapacity - (minerCollectCapacity * _currentShaftUpgrade.CollectCapacityMultiplier));
        stat4UpgradeValue.text = $"+{collectCapacityUpgraded}";
    }
    
    private void ShaftUpgradeRequest(Shaft selectedShaft, ShaftUpgrade selectedUpgrade)
    {
        Debug.Log("<color=green>UpgradeManagerクラスのShaftUpgradeRequestが実行されました。</color>");
        _currentShaft = selectedShaft;
        _currentShaftUpgrade = selectedUpgrade;
        
        UpdateUpgradeInfo();
        UpdateShaftPanelValues();   
        OpenCloseUpgradeContainer(true);
    }
    
    private void OnEnable()
    {
        Debug.Log("<color=green>UpgradeManagerクラスのOnEnableが実行されました。</color>");
        // 
        ShaftUI.OnUpgradeRequest += ShaftUpgradeRequest;
    }

    private void OnDisable()
    {
        Debug.Log("<color=green>UpgradeManagerクラスのOnDisableが実行されました。</color>");
        ShaftUI.OnUpgradeRequest -= ShaftUpgradeRequest;
    }
}

UpgradePanelInfo

UpgradePanelInfo.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "Upgrade Info")]
public class UpgradePanelInfo : ScriptableObject
{
    public string PanelTitle;
    public Sprite PanelMinerIcon;

    [Header("Stat Title")] 
    public string Stat1Title;
    public string Stat2Title;
    public string Stat3Title;
    public string Stat4Title;
    
    [Header("Stat Icon")] 
    public Sprite Stat1Icon;
    public Sprite Stat2Icon;
    public Sprite Stat3Icon;
    public Sprite Stat4Icon;
}

ShaftUI

ShaftUI.cs
using System;
using TMPro;
using UnityEngine;

public class ShaftUI : MonoBehaviour
{
    public static Action<Shaft, ShaftUpgrade> OnUpgradeRequest;
    
    [SerializeField] private TextMeshProUGUI depositGold;
    [SerializeField] private TextMeshProUGUI shaftID;
    [SerializeField] private TextMeshProUGUI shaftLevel;
    [SerializeField] private TextMeshProUGUI newShaftCost;
    [SerializeField] private GameObject newShaftButton;
    
    private Shaft _shaft;
+    private ShaftUpgrade _shaftUpgrade;
    
    private void Awake()
    {
        _shaft = GetComponent<Shaft>();
        _shaftUpgrade = GetComponent<ShaftUpgrade>();
    }

    private void Update()
    {
        depositGold.text = _shaft.ShaftDeposit.CurrentGold.ToString();
    }

    public void AddShaft()
    {
        if (GoldManager.Instance.CurrentGold >= ShaftManager.Instance.ShaftCost)
        {
            GoldManager.Instance.RemoveGold(ShaftManager.Instance.ShaftCost);
            ShaftManager.Instance.AddShaft();
            newShaftButton.SetActive(false);
        }
    }

+    public void OpenUpgradeContainer()
+    {
+        Debug.Log("<color=yellow>ShaftUIクラスのOpenUpgradeContainerが実行されました。</color>");
+        OnUpgradeRequest?.Invoke(_shaft, _shaftUpgrade);
+    }
    
    public void SetShaftUI(int ID)
    {
        Debug.Log("<color=purple>ShaftUIクラスのOpenUpgradeContainerが実行されました。</color>");
        _shaft.ShaftID = ID;
        shaftID.text = (ID + 1).ToString();
    }

    public void SetNewShaftCost(float newCost)
    {
        Debug.Log("<color=yellow>ShaftUIクラスのOpenUpgradeContainerが実行されました。</color>");
        newShaftCost.text = newCost.ToString();
    }

+    private void UpgradeCompleted(BaseUpgrade upgrade)
+    {
+        Debug.Log("<color=yellow>ShaftUIクラスのUpgradeCompletedが実行されました。</color>");
+        if (_shaftUpgrade == upgrade)
+        {
+            shaftLevel.text = upgrade.CurrentLevel.ToString();
+        }
+    }
    
+    private void OnEnable()
+    {
+        Debug.Log("<color=yellow>ShaftUIクラスのOnEnableが実行されました。</color>");
+        ShaftUpgrade.OnUpgradeCompleted += UpgradeCompleted;
+    }

+    private void OnDisable()
+    {
+        Debug.Log("<color=yellow>ShaftUIクラスのOnDisableが実行されました。</color>");
+        ShaftUpgrade.OnUpgradeCompleted -= UpgradeCompleted;
+    }
}

ShaftUpgrade

ShaftUpgrade.cs
using UnityEngine;

public class ShaftUpgrade : BaseUpgrade
{
    protected override void ExecuteUpgrade()
    {
        Debug.Log("<color=red>ShaftUpgradeクラスのExecuteUpgradeが実行されました。</color>");
        if (CurrentLevel % 10 == 0)
        {
            // Levelの数が増えると新しいShaftMinerが1人増える。
            _shaft.CreateMiner();
        }

        foreach (ShaftMiner miner in _shaft.Miners)
        {
            
            miner.CollectCapacity *= CollectCapacityMultiplier;
            miner.CollectPerSecond *= CollectPerSecondMultiplier;
            
            Debug.Log(CollectCapacityMultiplier);
            if (CurrentLevel % 10 == 0)
            {
                miner.MoveSpeed *= MoveSpeedMultiplier;
            }
        }
    }
}

BaseUpgrade

BaseUpgrade.cs
using System;
using UnityEngine;

public class BaseUpgrade : MonoBehaviour
{
    public static Action<BaseUpgrade> OnUpgradeCompleted;
    
    [Header("Upgrade")]
    [SerializeField] private float collectCapacityMultiplier = 2;
    [SerializeField] private float collectPerSecondMultiplier = 2;
    [SerializeField] private float moveSpeedMultiplier = 2;

    [Header("Cost")]
    [SerializeField] private float initialUpgradeCost = 600;
    [SerializeField] private float upgradeCostMultiplier = 2;

    public int CurrentLevel { get; set; }
    public float UpgradeCost { get; set; }
    public int BoostLevel { get; set; }
    
    public float CollectCapacityMultiplier => collectCapacityMultiplier;
    public float CollectPerSecondMultiplier => collectPerSecondMultiplier;
    public float MoveSpeedMultiplier => moveSpeedMultiplier;
    public float UpgradeCostMultiplier => upgradeCostMultiplier;
    
    protected Shaft _shaft;
    private int _currentNextBoostLevel = 1;
    private int _nextBoostResetValue = 1;

    private void Start()
    {
        _shaft = GetComponent<Shaft>();
        
        CurrentLevel = 1;
        UpgradeCost = initialUpgradeCost;
        BoostLevel = 10;
    }

    public void Upgrade(int amount)
    {
        if (amount > 0)
        {
            for (int i = 0; i < amount; i++)
            {
                UpgradeCompleted();
                ExecuteUpgrade();
            }
        }
    }

    private void UpgradeCompleted()
    {
        Debug.Log("<color=purple>BaseUpgradeクラスのUpgradeCompletedが実行されました。</color>");
        GoldManager.Instance.RemoveGold(UpgradeCost);
        UpgradeCost *= upgradeCostMultiplier;
        CurrentLevel++;
        UpdateNextBoostLevel();
        OnUpgradeCompleted?.Invoke(this);
    }

    protected virtual void ExecuteUpgrade()
    {
        
    }

    protected void UpdateNextBoostLevel()
    {
        Debug.Log("<color=purple>BaseUpgradeクラスのUpgradeNextBoostLevelが実行されました。</color>");
        _currentNextBoostLevel++;
        _nextBoostResetValue++;
        if (_currentNextBoostLevel == BoostLevel)
        {
            _nextBoostResetValue = 1;
            BoostLevel += 10;
        }
    }

    public float GetNextBoostProgress()
    {
        // Levelが10の倍数になったときに実行される
        // progressバーを初期値に戻す
        Debug.Log("<color=purple>BaseUpgradeクラスのGetNextBoostProgressが実行されました。</color>");
        return (float) _nextBoostResetValue / 10;
    }
}

動かして確認

Upgradeメニューを開く

動き

upgradeMenuOpen1.gif

ログ

image.png

各処理の説明
1.ShaftUIクラスのOpenUpgradeContainer
デリデートを使ってOnUpgradeRequestを実行する。(引数も渡している。)
OnUpgradeRequestにはUpgradeManager.ShaftUpgradeRequestが登録されている。

2.UpgradeManageクラスのUpdateUpgradeInfo
デリケートから渡された変数selectedShaftとselectedUpgradeをprivate変数に渡している。

3.UpgradeManageクラスのUpdateUpgradeInfo
shaftMinerInfoからの情報(アイコン アイコンのテキスト)を取得。

4.UpgradeManageクラスのUpdateShaftPanelValues
Shaft、ShaftUpgradeクラスの情報(パラメーターの数値など)を取得する。

5.BaseUpgradeクラスのGetNextBoostProgress
ProgressBarの現在値を算出する。

6.UpgradeManageクラスのOpenCloseUpgradeContainer
UpgradeUIをactiveにする。

Upgradeする

動き

upgradeMenuUpgradeOne.gif

ログ

image.png

1.BaseUpgradeクラスのUpgradeCompleted
所持金の処理
2.BaseUpgradeクラスのUpgradeNextBoostLevel

3.ShaftUpgradeのExecuteUpgrade
Shaft特有のUpgrade処理を実装する。
具体的には採掘スピード、運搬量の改善。
Upgrade数が10の倍数となると従業員の追加、移動速度が改善される。

4.UpgradeManagerのUpdateShaftPanelValues
UIの数値更新

5.BaseUpgradeクラスのGetNextBoostProgress
プログレスバーのUIを更新

Upgradeする(10回)

動き

upgradeMenuUpgradeTen.gif

ログ

image.png

上のUpgrade処理と類似した処理。
1-3を10回繰り返した後、4,5を実行する

感想

クラス図が増えて処理を追うのが難しいですね。ElevatorやWareHouseの場合でも実際に手を動かして習熟する必要があると思います。(Section7, Section8)

UpgradeManagerクラスの責務にuGui要素が含まれているのが気になりましたね。
ShaftUpgradeUI的なクラス設けて分けた方が良いのではと思った。

C#の文法でネックになっている点はActionとコルーチンだと気づきました。
他のUnityの講座でもよく使われている事例が多いので使いこなせたほうがいいですね。

参考

Section6 ShaftUpgrade

C# [Design Pattern] C# による Observer パターンの実装

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?