판매
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이 발생합니다. 판매하면 아무것도 선택되지 않으므로 패널이 숨겨집니다.
챌린지
중급Tower에서 비공개 intspent를 유지합니다.Start에서 설정되는 타워의cost에 구매한 각 업그레이드의 가격을 더한 값입니다.public int SellValue => spent / 2;를 추가합니다.BuildManager에서 패널이 채워질 때마다 직렬화된 TMP_TextsellLabel에Sell 25, 즉 선택한 타워의 판매 가격을 표시합니다.- 판매 버튼이 호출할
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();
}
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.