Parallels Desktop アースデイセール実施中 25%OFF

【Unity】タワーディフェンス(13) 砲台のアップグレード機能【クソゲー制作】

タワーディフェンスを作る(13)

Unity初心者が2Dタワーディフェンスを作っています。なかなか出来上がりそうにありません。

今回は、砲台のアップグレード機能を実装します。

環境
  • Mac mini (M1, 2020)
  • Unity 2022.3.36f1
目次

砲台データベースの構造を修正

砲台のデータはScriptableObjectで管理しています。レベル別の砲台データを入れられるように構造を修正しましょう。

STEP

TurretSettingスクリプトを修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System; // [Serializable]を使うために必要な宣言

[CreateAssetMenu(fileName = "TurretSetting", menuName = "ScriptableObject/Turret Setting")]
public class TurretSetting : ScriptableObject
{
    public List<TurretData> turretDataList = new List<TurretData>();

    [Serializable]
    public class TurretData
    {
        // ここから
        public string id;   // ID
        public string name; // 名前
        // public int attackPower; // 攻撃力
        // public float attackInterval; // 攻撃間隔
        // public float attackRange; // 攻撃範囲
        // public int buildingCost; // 建設コスト
        public Sprite turretHeadSprite; // 砲身の画像
        public List<TurretLvData> turretLevels = new List<TurretLvData>(); // レベルごとのデータ
        // ここまで
    }

    // ここから
    [Serializable]
    public class TurretLvData
    {
        public int level; // 砲台のレベル
        public int attackPower; // 攻撃力
        public float attackInterval; // 攻撃間隔
        public float attackRange; // 攻撃範囲
        public int cost; // 建設・強化コスト
    }
    // ここまで

    // ここから
    /// <summary>
    /// 指定した砲台のデータを取得する
    /// </summary>
    public TurretLvData GetTurretData(string turretId, int level)
    {
        var turretType = turretDataList.Find(t => t.id == turretId);
        if (turretType != null)
        {
            return turretType.turretLevels.Find(t => t.level == level);
        }
        return null;
    }
    // ここまで
}

TurretDataクラスから攻撃力、攻撃間隔、攻撃範囲、コストを削除して、TurretLvDataリストを追加します。TurretLvDataはレベル別の砲台のデータを管理するリストです。

TurretLvDataクラスを追加します。これに砲台のレベル別のパラメータを入れます。

GetTurretDataは砲台のレベル別データを取得するためのメソッドです。

STEP

TurretSettingスクリプトの修正に対応するように、TrretControllerスクリプトを修正します。

using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor.PackageManager;
using UnityEngine;

public class TurretController : MonoBehaviour
{
    // ...省略...
    // ここから
    private TurretSetting.TurretLvData turretLvData; // 砲台レベル別データ
    // ここまで

    // ...省略...

    /// <summary>
    /// 砲台データを初期化
    /// </summary>
    // ここから
    public void InitializeTurret(TurretSetting.TurretData data)
    {
        turretData = data;
        turretLvData = DBManager.instance.turretSetting.GetTurretData(data.id, 1);
        //attackPower = turretData.attackPower; // 攻撃力を設定
        //attackInterval = turretData.attackInterval; // 攻撃間隔を設定
        //attackRange.radius = turretData.attackRange; // 攻撃範囲を設定
        attackPower = turretLvData.attackPower; // 攻撃力を設定
        attackInterval = turretLvData.attackInterval; // 攻撃間隔を設定
        attackRange.radius = turretLvData.attackRange; // 攻撃範囲を設定
        turretHeadSpriteRenderer.sprite = turretData.turretHeadSprite; // 砲身の画像を設定
        Debug.Log($"砲台を初期化: {turretData.name}");
    }
    // ここまで

    // ...省略...
}

InitializeTurretメソッド内でGetTurretDataを使ってレベル別データを取得し各変数に代入するようにします。今のところ、まだアップグレード機能を実装していないので、常にレベル1のデータを取得するようにしています。

STEP

SideBarManagerスクリプトも修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro; // TextMeshProを使うために必要な宣言
using UnityEngine.UI; // UIを扱うために必要な宣言

public class SideBarManager : MonoBehaviour
{
    [SerializeField] private GameObject turretInfo; // 砲台情報パネルのオブジェクト
    [SerializeField] private TextMeshProUGUI turretNameText; // 砲台の名前のテキスト
    [SerializeField] private TextMeshProUGUI buildingCostText; // 建設コストのテキスト
    [SerializeField] private TextMeshProUGUI attackPowerText; // 攻撃力のテキスト
    [SerializeField] private TextMeshProUGUI attackIntervalText; // 攻撃速度のテキスト
    [SerializeField] private TextMeshProUGUI attackRangeText; // 攻撃範囲のテキスト
    [SerializeField] private Button sellButton; // 売却ボタン
    // ここから
    private TurretSetting.TurretLvData turretLvData; // 砲台レベル別データ
    // ここまで

    /// <summary>
    /// 砲台情報を表示
    /// </summary>
    public void ShowTurretInfo(TurretSetting.TurretData turretData, bool sellButtonInteractable)
    {
        // ここから
        turretLvData = DBManager.instance.turretSetting.GetTurretData(turretData.id, 1);
        turretNameText.text = turretData.name;
        //buildingCostText.text = turretData.buildingCost.ToString();
        //attackPowerText.text = turretData.attackPower.ToString();
        //attackIntervalText.text = turretData.attackInterval.ToString();
        //attackRangeText.text = turretData.attackRange.ToString();
        buildingCostText.text = turretLvData.cost.ToString();
        attackPowerText.text = turretLvData.attackPower.ToString();
        attackIntervalText.text = turretLvData.attackInterval.ToString();
        attackRangeText.text = turretLvData.attackRange.ToString();
        // ここまで
        // 売却ボタンのinteractableを切り替え
        sellButton.interactable = sellButtonInteractable;
        turretInfo.SetActive(true); // 砲台情報パネルを表示する
    }

    /// <summary>
    /// 砲台情報を非表示
    /// </summary>
    public void HideTurretInfo()
    {
        turretInfo.SetActive(false); // 砲台情報パネルを非表示にする
    }
}

こちらもGetTurretDataを使って砲台のレベル別データを取得し各変数に代入するようにします。

STEP

「Assets > Data > TurretSetting」のインスペクターでデータを設定し直します。

「TurretLevels」の下にレベル別のデータを追加していきます。各砲台につきレベル5までのデータを追加しました。

タワーディフェンス152

砲台強化情報パネルを作る

サイドバーに砲台強化情報パネルを作ります。完成図はこんな感じです。

タワーディフェンス153
STEP

ヒエラルキーの「Canvas > SideBar > TurretInfo」上で右クリックして「Duplicate」を選択します。

タワーディフェンス154
STEP

TurretInfoオブジェクトが複製され「TurretInfo (1)」というオブジェクトができるので名前を「TurretUpgradeInfo」に変更します。

タワーディフェンス155
STEP

Rect Transformを調整してTurretInfoの下に表示されるようにします。

タワーディフェンス156
STEP

TurretUpgradeInfo配下のオブジェクトたちを修正して砲台強化情報パネルを作ります。

タワーディフェンス157
STEP

インスペクター左上のチェックを外してTurretUpgradeInfo非表示にしておきます。

タワーディフェンス158

マップの砲台選択時に砲台強化情報パネルを表示

マップ上の砲台を選択したときに砲台強化情報パネルを表示するようスクリプトを修正します。

STEP

SideBarManagerスクリプトを修正します。

// ...省略...

public class SideBarManager : MonoBehaviour
{
    // ...省略...
    // ここから
    [SerializeField] private GameObject turretUpgradeInfo; // 砲台強化情報パネルのオブジェクト
    [SerializeField] private TextMeshProUGUI levelUpText; // レベルアップのタイトルテキスト
    [SerializeField] private TextMeshProUGUI upgradeCostText; // 強化コストのテキスト
    [SerializeField] private TextMeshProUGUI upgradeAttackPowerText; // 攻撃力強化のテキスト
    [SerializeField] private TextMeshProUGUI upgradeAttackIntervalText; // 攻撃速度強化のテキスト
    [SerializeField] private TextMeshProUGUI upgradeAttackRangeText; // 攻撃範囲強化のテキスト
    [SerializeField] private Button upgradeButton; // 強化ボタン
    [SerializeField] private TextMeshProUGUI upgradeButtonText; // 強化ボタンのテキスト
    // ここまで
    private TurretSetting.TurretLvData turretLvData; // 砲台レベル別データ

    // ...省略...

    // ここから
    /// <summary>
    /// 砲台強化情報パネルを表示
    /// </summary>
    public void ShowTurretUpgradeInfo(TurretSetting.TurretData turretData, TurretSetting.TurretLvData turretLvData, bool upgradeButtonInteractable)
    {
        TurretSetting.TurretLvData turretNextLvData = DBManager.instance.turretSetting.GetTurretData(turretData.id, turretLvData.level + 1);
        levelUpText.text = $"Lv.{turretNextLvData.level}へ強化";
        upgradeCostText.text = turretNextLvData.cost.ToString();
        upgradeAttackPowerText.text = $"{turretLvData.attackPower} >>> {turretNextLvData.attackPower}".ToString();
        upgradeAttackIntervalText.text = $"{turretLvData.attackInterval} >>> {turretNextLvData.attackInterval}".ToString();
        upgradeAttackRangeText.text = $"{turretLvData.attackRange} >>> {turretNextLvData.attackRange}".ToString();
        upgradeButtonText.text = $"{turretNextLvData.cost}Gで強化".ToString();
        // 強化ボタンのinteractableを切り替え
        upgradeButton.interactable = upgradeButtonInteractable;
        turretUpgradeInfo.SetActive(true); // 砲台強化情報パネルを表示する
    }
    // ここまで

    // ここから
    /// <summary>
    /// 砲台強化情報パネルを非表示
    /// </summary>
    public void HideTurretUpgradeInfo()
    {
        turretUpgradeInfo.SetActive(false); // 砲台強化情報パネルを非表示にする
    }
    // ここまで
}

6〜15行目で、強化情報パネルの各オブジェクトを参照する変数を定義しています。

そして、強化情報パネルを表示するメソッドShowTurretUpgradeInfoと非表示にするメソッドHideTurretUpgradeInfoを追加しました。難しいことは何もしていません。

STEP

TurretControllerスクリプトを修正します。

// ...省略...

public class TurretController : MonoBehaviour
{
    // ...省略...

    /// <summary>
    /// 砲台がクリックされたときの処理
    /// </summary>
    public void OnTurretClicked()
    {
        SideBarManager sideBarManager = FindObjectOfType<SideBarManager>();
        sideBarManager.ShowTurretInfo(turretData, true); // 砲台情報パネルを表示
        // ここから
        sideBarManager.ShowTurretUpgradeInfo(turretData, turretLvData, true); // 砲台強化情報パネルを表示
        // ここまで
        Debug.Log($"クリックした砲台: {turretData.name}");
        // ...省略...
    }

    // ...省略...
}

マップ上の砲台がクリックされたらShowTurretUpgradeInfoメソッドを実行して強化情報パネルを表示します。

STEP

GameManagerスクリプトを修正します。

// ...省略...

public class GameManager : MonoBehaviour
{
    // ...省略...

    /// <summary>
    /// マウスのクリックを検出
    /// </summary>
    void DetectClick()
    {
        // UIがクリックされた場合は何もしない
        if (EventSystem.current.IsPointerOverGameObject())
        {
            return;
        }
        // クリック位置のスクリーン座標をワールド座標に変換
        Vector2 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        // クリック位置から見えるオブジェクトに対してレイキャスト(Ignore Raycastレイヤーを除外)
        RaycastHit2D hit = Physics2D.Raycast(worldPoint, Vector2.zero, Mathf.Infinity, ~LayerMask.GetMask("Ignore Raycast"));
        if (hit.collider != null) // クリックしたオブジェクトにコライダーがあれば
        {
            TurretController turret = hit.collider.GetComponent<TurretController>(); // TurretControllerを取得
            if (turret != null) // クリックしたオブジェクトが砲台ならば
            {
                turret.OnTurretClicked(); // 砲台情報を表示
                return;
            }
        }
        // 砲台以外をクリックした場合
        sideBarManager.HideTurretInfo(); // 砲台情報パネルを非表示
        // ここから
        sideBarManager.HideTurretUpgradeInfo(); // 砲台強化情報パネルを非表示
        // ここまで
    }
}

マップ上の砲台以外の場所をクリックしたときに強化パネルを非表示にするようにします。

STEP

TurretGeneratorスクリプトを修正します。

// ...省略...

public class TurretGenerator : MonoBehaviour
{
    // ...省略...

    /// <summary>
    /// 砲台を選択する
    /// </summary>
    public void SelectTurret(int index)
    {
        selectedTurretData = DBManager.instance.turretSetting.turretDataList[index];
        Debug.Log($"{selectedTurretData.name} を選択");
        // アイコンを表示
        selectedTurretIcon.SetActive(true);
        // 砲身のスプライトを設定
        SpriteRenderer iconRenderer = selectedTurretHead.GetComponent<SpriteRenderer>();
        iconRenderer.sprite = selectedTurretData.turretHeadSprite;
        sideBarManager.ShowTurretInfo(selectedTurretData, false); // 砲台情報を表示、売却ボタンを押せないように
        // ここから
        sideBarManager.HideTurretUpgradeInfo(); // 砲台強化情報を非表示
        // ここまで
    }
}

サイドバーの砲台アイコンを選択した場合は、強化情報パネルを非表示にします。

STEP

SideBarオブジェクトのインスペクターで「Side Bar Manager (Script)」下の各変数にオブジェクトをドラッグ&ドロップしてアサインします。

タワーディフェンス159

以上で、マップ上に設置された砲台をクリックしたら強化情報パネルが表示され、それ以外の場所をクリックしたら表示が消えるようになりました。いえい。

強化ボタンを押したら砲台をレベルアップする

今回のメインディッシュですね。

STEP

SideBarManagerスクリプトを修正します。

// ...省略...

public class SideBarManager : MonoBehaviour
{
    // ...省略...
    // ここから
    //private TurretSetting.TurretLvData turretLvData; // 砲台レベル別データ
    private TurretController selectedTurret; // 現在選択されている砲台
    // ここまで

    // ここから
    private void Start()
    {
        upgradeButton.onClick.AddListener(UpgradeSelectedTurret);
    }
    // ここまで

    /// <summary>
    /// 砲台情報パネルを表示
    /// </summary>
    // ここから
    //public void ShowTurretInfo(TurretSetting.TurretData turretData, bool sellButtonInteractable)
    public void ShowTurretInfo(TurretSetting.TurretData turretData, TurretSetting.TurretLvData turretLvData, bool sellButtonInteractable, TurretController turret)
    // ここまで
    {
        // ここから
        selectedTurret = turret; // 現在選択された砲台を保存
        //turretLvData = DBManager.instance.turretSetting.GetTurretData(turretData.id, 1);
        //turretNameText.text = turretData.name;
        turretNameText.text = $"{turretData.name} Lv.{turretLvData.level}";
        // ここまで
        buildingCostText.text = turretLvData.cost.ToString();
        attackPowerText.text = turretLvData.attackPower.ToString();
        attackIntervalText.text = turretLvData.attackInterval.ToString();
        attackRangeText.text = turretLvData.attackRange.ToString();
        // 売却ボタンのinteractableを切り替え
        sellButton.interactable = sellButtonInteractable;
        turretInfo.SetActive(true); // 砲台情報パネルを表示する
    }

    // ...省略...

    // ここから
    /// <summary>
    /// 選択された砲台をアップグレードする
    /// </summary>
    private void UpgradeSelectedTurret()
    {
        if (selectedTurret != null)
        {
            selectedTurret.UpgradeTurret();
        }
    }
    // ここまで
}

7行目、turretLvData変数の宣言は不要になったので削除します。

8行目、現在選択されている砲台の情報を入れておく変数selectedTurretを追加します。

Startメソッド内で、強化ボタンがクリックされたときにUpgradeSelectedTurretメソッドを実行するよう設定しておきます。

ShowTurretInfoメソッドの引数としてturretLvData(砲台のレベルごとのデータ)とturret(現在選択されている砲台のTurretController)を追加しました。

27行目、selectedTurretに現在選択されている砲台を保存します。これはアップグレード時にこの砲台を操作できるようにするためです。

28行目、turretLvDataは引数で渡されるようになったので削除します。

30行目、砲台の名前に加えてレベルも表示します。

43〜54行目、selectedTurretnullでなければUpgradeTurretメソッドを実行します。UpgradeTurretTurretController内で定義されている砲台をアップグレードするメソッドです。

STEP

TurretControllerスクリプトを修正します。

// ...省略...

public class TurretController : MonoBehaviour
{
    // ...省略...

    /// <summary>
    /// 砲台データを初期化
    /// </summary>
    // ここから
    //public void InitializeTurret(TurretSetting.TurretData data)
    public void InitializeTurret(TurretSetting.TurretData data, int level)
    // ここまで
    {
        turretData = data;
        // ここから
        //turretLvData = DBManager.instance.turretSetting.GetTurretData(data.id, 1);
        turretLvData = DBManager.instance.turretSetting.GetTurretData(data.id, level);
        // ここまで
        attackPower = turretLvData.attackPower; // 攻撃力を設定
        attackInterval = turretLvData.attackInterval; // 攻撃間隔を設定
        attackRange.radius = turretLvData.attackRange; // 攻撃範囲を設定
        turretHeadSpriteRenderer.sprite = turretData.turretHeadSprite; // 砲身の画像を設定
        Debug.Log($"砲台を初期化: {turretData.name}");
    }

    // ...省略...

    /// <summary>
    /// 砲台がクリックされたときの処理
    /// </summary>
    public void OnTurretClicked()
    {
        SideBarManager sideBarManager = FindObjectOfType<SideBarManager>(); // SideBarManagerを取得
        // ここから
        //sideBarManager.ShowTurretInfo(turretData, true); // 砲台情報パネルを表示
        sideBarManager.ShowTurretInfo(turretData, turretLvData, true, this); // 砲台情報パネルを表示、自分自身を渡す
        //sideBarManager.ShowTurretUpgradeInfo(turretData, turretLvData, true); // 砲台強化情報パネルを表示
        if (turretLvData.level < 5) // 砲台のレベルが5未満ならば
        {
            sideBarManager.ShowTurretUpgradeInfo(turretData, turretLvData, true); // 砲台強化情報パネルを表示
        }
        // ここまで
        Debug.Log($"クリックした砲台: {turretData.name}");
        // ...省略...
    }

    // ここから
    /// <summary>
    /// 砲台をアップグレードする
    /// </summary>
    public void UpgradeTurret()
    {
        int newLevel = turretLvData.level + 1; // アップグレード後のレベル
        SideBarManager sideBarManager = FindObjectOfType<SideBarManager>(); // SideBarManagerを取得
        // アップグレード後の砲台情報を取得
        TurretSetting.TurretLvData newTurretLvData = DBManager.instance.turretSetting.GetTurretData(turretData.id, newLevel);
        if (newTurretLvData == null)
        {
            Debug.LogWarning($"砲台 {turretData.id} のレベル {newLevel} のデータが見つかりません");
            return;
        }
        turretLvData = newTurretLvData; // データを更新
        InitializeTurret(turretData, newLevel); // 砲台を初期化
        // UIの更新
        if (sideBarManager != null)
        {
            sideBarManager.ShowTurretInfo(turretData, turretLvData, true, this); // 砲台情報パネルを表示
            if (newLevel < 5) // レベルが5未満なら
            {
                sideBarManager.ShowTurretUpgradeInfo(turretData, turretLvData, true); // 強化パネルを表示
            }
            else // レベルが5以上なら
            {
                sideBarManager.HideTurretUpgradeInfo(); // 強化パネルを非表示
            }
        }
        else
        {
            Debug.LogError("SideBarManager が見つかりません");
        }
    }
    // ここまで
}

12行目、InitializeTurretメソッドに引数levelを追加します。これでレベルを指定して砲台を初期化できるようになります。

18行目、GetTurretDataの第2引数にlevelを渡して、指定したレベルの砲台情報を取得するようにします。

37行目、ShowTurretInfoは引数を4つ取るようになったので、turretLvDatathisを追加します。this自分自身をあらわします。マップ上に複数ある砲台のうち、今クリックしたこの砲台のTurretControllerを指します。

39-42行目、砲台のレベルが5未満のときだけ強化パネルを表示するようにします。砲台の最大レベルが5なので。

49-82行目、UpgradeTurretメソッドを定義します。アップグレードした直後に砲台情報パネルと強化パネルの内容を更新するようにしています。

STEP

TurretGeneratorスクリプトを修正します。

// ...省略...

public class TurretGenerator : MonoBehaviour
{
    // ...省略...

    /// <summary>
    /// 砲台生成
    /// </summary>
    /// <param name="gridPos"></param>
    private void GenerateTurret(Vector3Int gridPos)
    {
        // ...省略...
        // 砲台データの初期化
        // ここから
        //turretController.InitializeTurret(selectedTurretData);
        turretController.InitializeTurret(selectedTurretData, 1);
        // ここまで
        // ...省略...
    }

    /// <summary>
    /// 砲台を選択する
    /// </summary>
    public void SelectTurret(int index)
    {
        // ...省略...
        // ここから
        //sideBarManager.ShowTurretInfo(selectedTurretData, false); // 砲台情報を表示、売却ボタンを押せないように
        TurretSetting.TurretLvData turretLvData = DBManager.instance.turretSetting.GetTurretData(selectedTurretData.id, 1);
        sideBarManager.ShowTurretInfo(selectedTurretData, turretLvData, false, null); // 砲台情報を表示
        // ここまで
        sideBarManager.HideTurretUpgradeInfo(); // 砲台強化情報を非表示
    }
}

17行目、砲台生成時の砲台のレベルは1なので、InitializeTurretの第2引数に1を渡します。

31行目、ShowTurretInfoの引数が4つに増えたので、turretLvDatanullを追加します。30行目でレベル1のturretLvDataを取得しています。第4引数はマップ上の砲台を特定するためのものなので、砲台生成時はnullでOKです。

さいごに

砲台のアップグレードができるようになりました。いえい。

とはいえ、まだ細かい修正や機能の追加をしたいので、次回は「砲台のアップグレード機能 その2」をお届けしたいと思います。

でわでわ

タワーディフェンスを作る(13)

この記事が気に入ったら
いいね または フォローしてね!

シェアしてね

コメント

コメントする

目次