概要
Learn how create 2D Idle Clicker Game in Unity Section3の学習メモです。
動きを把握するため、クラス図を書いたり実際に動かした時の記録を残しています。
開発環境
IDE:Rider
Unity:2019.4.40
OS:Windows10
図
gifアニメ
今回実装する機能
newShaftボタンを押すと、新しいshaftが追加される。
MinerオブジェクトをクリックするとMinerが動き出す。
クラス図
Shaft
クラス名 | 説明 |
---|---|
Shaft | Shaftオブジェクトの子オブジェクトを定義。Shaftの子オブジェクトにMinerとDepositを加える。 |
ShaftUI | ShaftのUI部分に関するクラス。Shaftクラスの変数idを引数にして対応するShaftのUIを操作している。 |
Singleton | シングルトン 他クラスに適用しやすくするためにジェネリック型を使っている |
ShaftManager | Shaftたちを管理するクラス。Shaftの追加処理のみ |
Miner
Minerをクリックしたときに動かせるために実装したクラス メソッド
クラス名 | 説明 |
---|---|
IClickable | 新規作成 インターフェイス |
BaseMiner | OnClickとMouseDownを追加 OnClickの実装はShaftMinerで行うので宣言のみ |
ShaftMiner | OnClickを追加 処理を実装している。 |
※MouseDownメソッドはStartやUpdateのようなUnity Event Function
OnClickは非Unity依存の関数
実装
UnityEditorの設定
起動した時のシーンの画像です。
オブジェクト名 | 説明 |
---|---|
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
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
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
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
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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IClickable
{
void OnClick();
}
BaseMiner
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
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が変化していることを確認できます。
コンソール上のログ
新しくShaftを追加したとき、以下の順でメソッドが実行されています。
Minerを動かした時の処理
gifアニメ
Minerをクリックしたとき継承元のクラスBaseMinerの変数MinerClickedがfalseからtrueに変化します。
コンソール上のログ
Minerを追加したとき 以下の順でメソッドが実行されています。
尚、Section2で行ったMoveMiner以降の処理は追ってません。
気になったこと
GameObjectとuGuiのクリック処理は実装方法が違う
GameObject OnMouseDownメソッドに処理を追加する
感想
Section2のときと比べてクラスが増えて処理を追うのが難しくなりました。
特にC#の文法的に理解が怪しい部分がでてきました。(ジェネリック型 インターフェイス型)
が、一つ一つ分解して確認していけばギリ把握できます。
uGuiとGameObjectでスクリプトを分ける方法は勉強になりました。
参考
Section3 Shaft
ジェネリッククラス