Menu
CoddyTech

판매

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

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

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에서 비공개 int spent를 유지합니다. Start에서 설정되는 타워의 cost에 구매한 각 업그레이드의 가격을 더한 값입니다. public int SellValue => spent / 2;를 추가합니다.
  2. BuildManager에서 패널이 채워질 때마다 직렬화된 TMP_Text sellLabel에 Sell 25, 즉 선택한 타워의 판매 가격을 표시합니다.
  3. 판매 버튼이 호출할 public void SellSelected()를 작성합니다. GameManager를 통해 판매 가격을 돌려받고, 타워를 제거한 뒤, 아무것도 선택되지 않도록 합니다.

Play를 누른 다음 타워를 몇 개 건설하고 업그레이드한 뒤 판매하고, Check를 누릅니다.

직접 해보기

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();
        }
    }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

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