アップグレード
Coddyの「Unityゲーム:タワーディフェンス」コースのレッスン 12/18。
タワーをクリックすると、レベルと「アップグレード」ボタンがあるパネルが開きます。レベルが1つ上がるごとにタワーのダメージと射程が増え、アップグレードの費用はそのたびに前回より高くなります。レベルは一定のルールに従うため、アセットには数値のリストではなく、そのルールを保持させます。レベル1のステータス、各レベルで増える量、アップグレードの価格です。ScriptableObjectのメソッドが残りを計算するので、どのスクリプトから尋ねても同じ答えが返ってきます。農場ゲームの作物なら、次のようになります。
public int YieldAt(int level)
{
return baseYield + yieldPerLevel * (level - 1);
}タワーが保持するのは自身のlevelだけです。パネルには現在の数値と並べて次のレベルの数値が表示されるので、プレイヤーはお金を使うと何が得られるのかを確認できます。タワーをクリックすると、タワーの子オブジェクトであるパーツの1つ、土台か頭部に当たります。そのため、hit.collider.GetComponentInParent<Tower>()を使うと、それらが属するタワーを見つけられます。ヒットしたオブジェクトを確認し、次にその親オブジェクトをたどります。
パネルは3Dビュー上に重ねて表示されるUIで、アップグレードボタンのクリックも、BuildManagerが認識するマウス押下です。レイはボタンを通り抜けて背後の地面に届き、BuildManagerはボタンが離される前にパネルを閉じてしまいます。ポインターがUI上にある間はEventSystem.current.IsPointerOverGameObject()がtrueになるため、BuildManagerは早期に処理を終了し、そのクリックをボタンに任せます。
チャレンジ
中級TowerDataに、public intのmaxLevel、upgradeCost、damagePerLevelと、public floatのrangePerLevelを追加します。int DamageAt(int level)とfloat RangeAt(int level)を追加します。レベル1ではアセット固有のダメージと射程を使い、それより上の各レベルではレベルごとの値を加算します。int UpgradeCost(int level)を追加します。これはそのレベルから次のレベルに上がるための価格で、upgradeCostにレベルを掛けた値です。Towerでは、1から始まるprivate intのlevelを保持し、public int Level => level;として読み取り可能にします。DamageAt(level)で攻撃し、RangeAt(level)の範囲内にいる敵をターゲットにします。レベルを1上げるpublic void Upgrade()を追加します。BuildManagerでは、タワーをクリックすると選択し、それ以外をクリックすると何も選択しないようにします(空いている建設パッドをクリックした場合は、引き続きそこに建設します)。シリアライズされたtowerPanelはタワーが選択されている間だけ表示し、towerNameTextにはArrow level 1、towerStatsTextには2行でDamage 2 > 3とRange 4.5 > 5、upgradeLabelにはUpgrade 40を表示します。maxLevelでは、ステータスには現在の値のみを表示し、ラベルにはMax levelと表示し、upgradeButtonを操作できないようにします。- Upgradeボタンから呼び出される
public void UpgradeSelected()を作成します。最大レベル未満で、GameManagerがアップグレード費用を支払える場合は、タワーをアップグレードしてパネルを更新します。UpdateではUI上のクリックを無視します。
Playを押し、タワーを建設してクリックし、アップグレードしてから、Checkを押します。
自分で試してみよう
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
using TMPro;
public class BuildManager : MonoBehaviour
{
[SerializeField] private GameManager game;
[SerializeField] private Tower[] shop;
[SerializeField] private Button[] shopButtons;
[SerializeField] private Color chosenColor = new Color(1f, 0.85f, 0.3f);
private Tower chosen;
void Start()
{
for (int i = 0; i < shop.Length; i++)
{
Tower prefab = shop[i];
shopButtons[i].GetComponentInChildren<TMP_Text>().text = prefab.data.towerName + "\n" + prefab.data.cost;
shopButtons[i].onClick.AddListener(() => Choose(prefab));
}
Choose(shop[0]);
}
void Choose(Tower prefab)
{
chosen = prefab;
for (int i = 0; i < shop.Length; i++)
{
shopButtons[i].image.color = shop[i] == chosen ? chosenColor : Color.white;
}
}
void Update()
{
if (!Mouse.current.leftButton.wasPressedThisFrame)
{
return;
}
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
if (Physics.Raycast(ray, out RaycastHit hit) && hit.collider.TryGetComponent(out BuildPad pad))
{
Build(pad);
}
}
void Build(BuildPad pad)
{
if (pad.tower != null)
{
return;
}
if (!game.TrySpend(chosen.data.cost))
{
Debug.Log("Not enough money");
return;
}
pad.tower = Instantiate(chosen, pad.transform.position, Quaternion.identity);
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。