Menu
CoddyTech

판매

Coddy의 Unity 게임: 타워 디펜스 코스 레슨. 25개 중 19번째.

타워를 판매하면 업그레이드 비용을 포함해 들인 돈의 절반을 돌려받으므로, 방어 시설을 옮기는 데는 비용이 들지만 전부를 잃지는 않습니다. 타워에는 지금까지 지출한 금액이 누적됩니다. 타워를 지을 때의 가격에 각 업그레이드 비용을 지불할 때마다 더해집니다. 환불액은 그 누적 금액의 절반이며, 정수 나눗셈으로 내림 처리됩니다. 자전거 대여도 같은 방식으로 누적 금액을 유지합니다:

public void ExtendRide(int extraHours)
{
    paid += extraHours * pricePerHour;
}

public int Deposit => paid / 4;

판매하면 타워가 파괴되지만, 패드의 tower 필드는 여전히 그 타워를 가리킵니다. Unity의 ==는 파괴된 객체를 null로 취급하므로, 다음 프레임부터 pad.tower == null은 true가 되고 추가 코드 없이 패드를 다시 사용할 수 있습니다. 패널은 다른 문제입니다. 여전히 판매된 타워를 표시하고 있으며, 업그레이드를 클릭하면 파괴된 객체에 접근해 MissingReferenceException이 발생합니다. 판매하면 아무것도 선택되지 않으므로 패널이 숨겨집니다.

challenge icon

챌린지

중급

타워를 비용의 절반 가격에 판매하세요.

  1. Tower에서 private int spent;를 유지하세요. Start에서 data.cost를 저장하고, Upgrade()에서 각 업그레이드의 가격을 더하세요. public int SellValue => spent / 2;를 추가하세요.
  2. BuildManager에서 패널이 표시될 때 직렬화된 TMP_Text sellLabel에 Sell 25(판매 가격)를 표시하세요.
  3. public void SellSelected()를 작성하세요. 판매 가격을 돌려받고, 타워를 파괴한 뒤 아무것도 선택하지 않도록 하세요.

‘플레이’를 누른 다음 타워를 건설하고, 업그레이드한 뒤 판매하세요. 그런 다음 ‘확인’을 누르세요.

직접 해보기

using UnityEngine;
using UnityEngine.EventSystems;
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);
    [SerializeField] private GameObject towerPanel;
    [SerializeField] private TMP_Text towerNameText;
    [SerializeField] private TMP_Text towerStatsText;
    [SerializeField] private Button upgradeButton;
    [SerializeField] private TMP_Text upgradeLabel;

    private Tower chosen;
    private Tower selected;

    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]);
        Select(null);
    }

    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 || EventSystem.current.IsPointerOverGameObject())
        {
            return;
        }

        Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
        if (!Physics.Raycast(ray, out RaycastHit hit))
        {
            Select(null);
            return;
        }

        Tower tower = hit.collider.GetComponentInParent<Tower>();
        if (tower != null)
        {
            Select(tower);
            return;
        }

        Select(null);
        if (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);
    }

    void Select(Tower tower)
    {
        selected = tower;
        towerPanel.SetActive(tower != null);
        if (tower != null)
        {
            ShowPanel();
        }
    }

    void ShowPanel()
    {
        TowerData data = selected.data;
        int level = selected.Level;
        towerNameText.text = data.towerName + " level " + level;
        if (level < data.maxLevel)
        {
            towerStatsText.text = "Damage " + data.DamageAt(level) + " > " + data.DamageAt(level + 1)
                + "\nRange " + data.RangeAt(level) + " > " + data.RangeAt(level + 1);
            upgradeLabel.text = "Upgrade " + data.UpgradeCost(level);
            upgradeButton.interactable = true;
        }
        else
        {
            towerStatsText.text = "Damage " + data.DamageAt(level) + "\nRange " + data.RangeAt(level);
            upgradeLabel.text = "Max level";
            upgradeButton.interactable = false;
        }
    }

    public void UpgradeSelected()
    {
        if (selected.Level >= selected.data.maxLevel)
        {
            return;
        }

        if (game.TrySpend(selected.data.UpgradeCost(selected.Level)))
        {
            selected.Upgrade();
            ShowPanel();
        }
    }
}

Unity 게임: 타워 디펜스의 모든 레슨

직접 연습해 보세요: Unity 플레이그라운드