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 Section3 Shaftの動的作成 学習メモ

Last updated at Posted at 2023-02-27

概要

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

開発環境

IDE:Rider
Unity:2019.4.40
OS:Windows10

gifアニメ

Section3sample.gif

今回実装する機能
newShaftボタンを押すと、新しいshaftが追加される。
MinerオブジェクトをクリックするとMinerが動き出す。

クラス図

Shaft

新しいshaftを追加するためののクラス図です。
image.png

クラス名 説明
Shaft Shaftオブジェクトの子オブジェクトを定義。Shaftの子オブジェクトにMinerとDepositを加える。
ShaftUI ShaftのUI部分に関するクラス。Shaftクラスの変数idを引数にして対応するShaftのUIを操作している。
Singleton シングルトン 他クラスに適用しやすくするためにジェネリック型を使っている
ShaftManager Shaftたちを管理するクラス。Shaftの追加処理のみ

Miner

Minerをクリックしたときに動かせるために実装したクラス メソッド
image.png

クラス名 説明
IClickable 新規作成 インターフェイス
BaseMiner OnClickとMouseDownを追加 OnClickの実装はShaftMinerで行うので宣言のみ
ShaftMiner OnClickを追加 処理を実装している。

※MouseDownメソッドはStartやUpdateのようなUnity Event Function
OnClickは非Unity依存の関数

実装

UnityEditorの設定

起動した時のシーンの画像です。

image.png

オブジェクト名 説明
ShaftManager ShaftManagerスクリプトがアタッチされている。
Shaft 親オブジェクト。Shaft,ShaftUIスクリプトがアタッチされている。
Canvas UI部分、ShaftのID、Depositの金額、Upgradeレベル、追加でShaftを追加するボタンの4点
MiningLocation Minerが動ける右端 到達すると採掘を開始する コンポーネントのアイコンを使っているのでゲーム画面には表示されない
DepositLocation Minerが動ける左端 到達するとMinerの所持金をDepositに移す コンポーネントのアイコンを使っているのでゲーム画面には表示されない
DepositCreationLocation Depositが生成される座標
Miner Prefab。 DepositPostionの座標に生成 ShaftMinerスクリプトがアタッチされている。
Deposit Prefab。 DepositCreationLocationの座標に生成 Depositスクリプトがアタッチされている。

起動させると動的オブジェクト(MinerとDeposit)が生成されます。

Shaftのスクリプト

Shaft

Shaft.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;

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

    [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; }

    private void Awake()
    {
        Debug.Log("<color=yellow>ShaftのAwakeが実行されました。</color>");
        // 使われていない。
        ShaftUI = GetComponent<ShaftUI>();
    }

    private void Start()
    {
        Debug.Log("<color=yellow>ShaftのStartが実行されました。</color>");
        CreateMiner();
        CreateDeposit();
    }

    private void CreateMiner()
    {
        Debug.Log("<color=yellow>ShaftのCreateMinerが実行されました。</color>");
         ShaftMiner newMiner = Instantiate(minerPrefab, depositLocation.position, quaternion.identity);
         newMiner.CurrentShaft = this;
         newMiner.transform.SetParent(transform);
    }

    private void CreateDeposit()
    {
        Debug.Log("<color=yellow>ShaftのCreateDepositが実行されました。</color>");
        ShaftDeposit = Instantiate(depositPrefab, depositCreationLocation.position, quaternion.identity);
        ShaftDeposit.transform.SetParent(transform);
    }
}

ShaftUI

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

public class ShaftUI : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI depositGold;
    [SerializeField] private TextMeshProUGUI shaftID;
    [SerializeField] private TextMeshProUGUI shaftLevel;
    [SerializeField] private TextMeshProUGUI newShaftCost;
    [SerializeField] private GameObject newShaftButton;
    
    private Shaft _shaft;
    
    // Start is called before the first frame update
    private void Awake()
    {
        Debug.Log("<color=green>ShaftUIのAwakeが実行されました。</color>");
        _shaft = GetComponent<Shaft>();
    }

    // Update is called once per frame
    private void Update()
    {
        depositGold.text = _shaft.ShaftDeposit.CurrentGold.ToString();
    }

    // NewShaft-buttonにアタッチされている。
    public void AddShaft()
    {
        Debug.Log("<color=green>ShaftUIのAddShaftが実行されました。</color>");
        ShaftManager.Instance.AddShaft();
        newShaftButton.SetActive(false);
    }

    public void SetShaftUI(int ID)
    {
        Debug.Log("<color=green>ShaftUIのSetShaftUIが実行されました。</color>");
        _shaft.ShaftID = ID;
        shaftID.text = (ID + 1).ToString();
    }

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

ShaftManager

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

public class ShaftManager : Singleton<ShaftManager>
{
    [SerializeField] private Shaft shaftPrefab;
    [SerializeField] private float newShaftYPosition;
    [SerializeField] private float newShaftCost = 5000;
    [SerializeField] private float newShaftCostMultiplier = 10;
    
    [SerializeField] private List<Shaft> shafts;

    public float ShaftCost { get; set; }
    
    private int _currentShaftIndex;

    private void Start()
    {
        Debug.Log("<color=purple>ShaftManagerのStartが実行されました。</color>");
        ShaftCost = newShaftCost;
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.N))
        {
            Debug.Log("<color=purple>ShaftManagerのUpdateが実行されました。</color>");
            AddShaft();
        }
    }

    public void AddShaft()
    {
        Debug.Log("<color=purple>ShaftManagerのAddStartが実行されました。</color>");
        Transform lastShaft = shafts[_currentShaftIndex].transform;
        Vector3 newShaftPos = new Vector3(lastShaft.position.x, lastShaft.position.y - newShaftYPosition);
        Shaft newShaft = Instantiate(shaftPrefab, newShaftPos, Quaternion.identity);

        _currentShaftIndex++;
        ShaftCost *= newShaftCostMultiplier;
        
        newShaft.ShaftUI.SetShaftUI(_currentShaftIndex);
        newShaft.ShaftUI.SetNewShaftCost(ShaftCost);
        
        shafts.Add(newShaft);
    }
}

Singleton

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

public class Singleton<T> : MonoBehaviour where T : Component
{
    private static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<T>();
                if (instance == null)
                {
                    GameObject newGO = new GameObject();
                    instance = newGO.AddComponent<T>();
                }
            }

            return instance;
        }
    }

    protected virtual void Awake()
    {
        instance = this as T;
    }
}

Minerのスクリプト

IClickable

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

public interface IClickable
{
    void OnClick();
}

BaseMiner

BaseMiner.cs
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;

public class BaseMiner : MonoBehaviour, IClickable
{
    [Header("Initial Values")] 
    [SerializeField] private float initialCollectCapacity;
    [SerializeField] private float initialCollectPerSecond;
    [SerializeField] protected float moveSpeed;
    
    public float CurrentGold { get; set; }
    public float CollectCapacity { get; set; }
    public float CollectPerSecond { get; set; }
    public bool IsTimeToCollect { get; set; }
+    public bool MinerClicked { get; set; }
    
    protected Animator _animator;
    
    private void Start()
    {
        _animator = GetComponent<Animator>();
        IsTimeToCollect = true;

        CollectCapacity = initialCollectCapacity;
        CollectPerSecond = initialCollectPerSecond;
    }

+    private void OnMouseDown()
+    {
+        if (!MinerClicked)
+        {
+            OnClick();
+            MinerClicked = true;
+        }
+    }

+    public virtual void OnClick()
+    {
+        
+    }
    
    protected virtual void MoveMiner(Vector3 newPosition)
    {
        transform.DOMove(newPosition, moveSpeed).SetEase(Ease.Linear).OnComplete((() =>
        {
            if (IsTimeToCollect)
            {
                CollectGold();
            }
            else
            {
                DepositGold();
            }

        })).Play();
    }
    
    protected virtual void CollectGold()
    {
        
    }

    protected virtual IEnumerator IECollect(float gold, float collecTime)
    {
        yield return null;
    }
    
    protected virtual void DepositGold()
    {
        
    }

    protected void ChangeGoal()
    {
        IsTimeToCollect = !IsTimeToCollect;
    }

    protected void RotateMiner(int direction)
    {
        if (direction == 1)
        {
            transform.localScale = new Vector3(1, 1, 1);
        }
        else
        {
            transform.localScale = new Vector3(-1, 1, 1);
        }
    }
}

ShaftMiner

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

public class ShaftMiner : BaseMiner
{
    public Shaft CurrentShaft { get; set; }

    public Vector3 DepositLocation => new Vector3(CurrentShaft.DepositLocation.position.x, transform.position.y);
    public Vector3 MiningLocation => new Vector3(CurrentShaft.MiningLocation.position.x, transform.position.y);
    
    private int walkAnimation = Animator.StringToHash("Walk");
    private int miningAnimation = Animator.StringToHash("Mining");

+    public override void OnClick()
+    {
+        MoveMiner(MiningLocation);
+    }

    protected override void MoveMiner(Vector3 newPosition)
    {
        base.MoveMiner(newPosition);
        _animator.SetTrigger(walkAnimation);
    }

    protected override void CollectGold()
    {
        _animator.SetTrigger(miningAnimation);
        float collectTime = CollectCapacity / CollectPerSecond;
        StartCoroutine(IECollect(CollectCapacity, collectTime));
    }

    protected override IEnumerator IECollect(float gold, float collectTime)
    {
        yield return new WaitForSeconds(collectTime);

        CurrentGold = gold;
        ChangeGoal();
        RotateMiner(-1);
        MoveMiner(DepositLocation);
    }

    protected override void DepositGold()
    {
        CurrentShaft.ShaftDeposit.DepositGold(CurrentGold);
        
        CurrentGold = 0;
        ChangeGoal();
        RotateMiner(1);
        MoveMiner(MiningLocation);
    }
}

動かして確認

Shaftを追加した時の動き

gifアニメ

shaftを追加後、StaffManagerの変数shaftsが変化していることを確認できます。
次に各Shaftをインスペクター上で確認し、Shaftクラスの変数ShaftIDが変化していることを確認できます。

Section3newShaft.gif

コンソール上のログ

新しくShaftを追加したとき、以下の順でメソッドが実行されています。
image.png

Minerを動かした時の処理

gifアニメ

Minerをクリックしたとき継承元のクラスBaseMinerの変数MinerClickedがfalseからtrueに変化します。

Section3WokerClickf.gif

コンソール上のログ

Minerを追加したとき 以下の順でメソッドが実行されています。
尚、Section2で行ったMoveMiner以降の処理は追ってません。
image.png

気になったこと

GameObjectとuGuiのクリック処理は実装方法が違う
GameObject OnMouseDownメソッドに処理を追加する

感想

Section2のときと比べてクラスが増えて処理を追うのが難しくなりました。
特にC#の文法的に理解が怪しい部分がでてきました。(ジェネリック型 インターフェイス型)
が、一つ一つ分解して確認していけばギリ把握できます。

uGuiとGameObjectでスクリプトを分ける方法は勉強になりました。

参考

Section3 Shaft

ジェネリッククラス

0
0
0

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?