Upgrade
Lesson 12 of 18 in Coddy's Unity Game: Tower Defense course.
Clicking a tower opens a panel with its level and an Upgrade button. Each level adds to the tower's damage and range, and each upgrade costs more than the last. The levels follow a rule, so the asset holds the rule rather than a list of numbers: the stats at level 1, what each level adds, and the price of an upgrade. Methods on the ScriptableObject work out the rest, so every script that asks gets the same answer. A farm game's crop:
public int YieldAt(int level)
{
return baseYield + yieldPerLevel * (level - 1);
}The tower keeps only its own level, and the panel shows the next level's numbers next to the current ones, so the player sees what the money buys. A click on a tower hits one of its parts, the base or the head, which are children of the tower, so hit.collider.GetComponentInParent<Tower>() finds the tower they belong to: it looks on the object hit, then up its parents.
The panel is UI over the 3D view, and a click on its Upgrade button is also a mouse press that the BuildManager sees. Its ray goes through the button to the ground behind it, and the BuildManager closes the panel before the button is released. EventSystem.current.IsPointerOverGameObject() is true while the pointer is over UI, so the BuildManager returns early and leaves those clicks to the buttons.
Challenge
Medium- In
TowerData, add the public intsmaxLevel,upgradeCostanddamagePerLeveland the public floatrangePerLevel. Addint DamageAt(int level)andfloat RangeAt(int level): level 1 has the asset's own damage and range, and each level above it adds the per-level amount. Addint UpgradeCost(int level), the price of going up from that level:upgradeCosttimes the level. - In
Tower, keep a private intlevelthat starts at 1, readable aspublic int Level => level;. Fire withDamageAt(level)and target withinRangeAt(level). Addpublic void Upgrade(), which adds 1 to the level. - In
BuildManager, a click on a tower selects it, and a click on anything else selects nothing (a click on a free pad still builds on it). The serializedtowerPanelshows only while a tower is selected, withArrow level 1intowerNameText,Damage 2 > 3andRange 4.5 > 5on two lines intowerStatsText, andUpgrade 40inupgradeLabel. AtmaxLevel, the stats show the current values only, the label saysMax levelandupgradeButtonis not interactable. - Write
public void UpgradeSelected(), which the Upgrade button calls: below the top level, when the GameManager can pay the upgrade, upgrade the tower and refresh the panel. Ignore clicks over UI inUpdate.
Press Play, build a tower, click it and upgrade it, then Check.
Try it yourself
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);
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.