Unity初心者が2Dタワーディフェンスを作っています。なかなか出来上がりそうにありません。
今回は、砲台のアップグレード機能を実装します。
- Mac mini (M1, 2020)
- Unity 2022.3.36f1
砲台データベースの構造を修正
砲台のデータはScriptableObjectで管理しています。レベル別の砲台データを入れられるように構造を修正しましょう。
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
は砲台のレベル別データを取得するためのメソッドです。
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のデータを取得するようにしています。
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
を使って砲台のレベル別データを取得し各変数に代入するようにします。
「Assets > Data > TurretSetting」のインスペクターでデータを設定し直します。
「TurretLevels」の下にレベル別のデータを追加していきます。各砲台につきレベル5までのデータを追加しました。

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

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

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

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

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

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

マップの砲台選択時に砲台強化情報パネルを表示
マップ上の砲台を選択したときに砲台強化情報パネルを表示するようスクリプトを修正します。
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
を追加しました。難しいことは何もしていません。
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
メソッドを実行して強化情報パネルを表示します。
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(); // 砲台強化情報パネルを非表示
// ここまで
}
}
マップ上の砲台以外の場所をクリックしたときに強化パネルを非表示にするようにします。
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(); // 砲台強化情報を非表示
// ここまで
}
}
サイドバーの砲台アイコンを選択した場合は、強化情報パネルを非表示にします。
SideBarオブジェクトのインスペクターで「Side Bar Manager (Script)」下の各変数にオブジェクトをドラッグ&ドロップしてアサインします。

以上で、マップ上に設置された砲台をクリックしたら強化情報パネルが表示され、それ以外の場所をクリックしたら表示が消えるようになりました。いえい。
強化ボタンを押したら砲台をレベルアップする
今回のメインディッシュですね。
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行目、selectedTurret
がnull
でなければUpgradeTurret
メソッドを実行します。UpgradeTurret
はTurretController内で定義されている砲台をアップグレードするメソッドです。
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つ取るようになったので、turretLvData
とthis
を追加します。this
は自分自身をあらわします。マップ上に複数ある砲台のうち、今クリックしたこの砲台のTurretController
を指します。
39-42行目、砲台のレベルが5未満のときだけ強化パネルを表示するようにします。砲台の最大レベルが5なので。
49-82行目、UpgradeTurret
メソッドを定義します。アップグレードした直後に砲台情報パネルと強化パネルの内容を更新するようにしています。
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つに増えたので、turretLvData
とnull
を追加します。30行目でレベル1のturretLvData
を取得しています。第4引数はマップ上の砲台を特定するためのものなので、砲台生成時はnull
でOKです。
さいごに
砲台のアップグレードができるようになりました。いえい。
とはいえ、まだ細かい修正や機能の追加をしたいので、次回は「砲台のアップグレード機能 その2」をお届けしたいと思います。
でわでわ
コメント