타일 관리 스크립트를 만들었다
이 부분이 나한테는 가장 어렵고 구현도 오래걸리고 애를 많이 먹었던 부분인 거 같다
여러 방법을 시도해보면서 방법을 찾았는 데 더 좋은 방법이 있었을 거 같기도.. 조금 아쉽긴 하다
이미 작성한적이 있는 유닛 배치 스크립트에서, 타일 내에 유닛을 배치하는 걸 했다
유닛을 구매한 뒤 타일에 배치하는 것은 어렵지않았다
또한, 유닛을 구매한 뒤 바로 삭제하는 것도 어렵지 않았다
가장 어려웠던 부분은 '타일에 배치된 유닛을 다시 집어서 이동시키기' 였다
타일 내에 유닛을 구매하고 배치할 수 있다
배치된 유닛을 다시 집어서 이동할 수 있다
삭제존으로 유닛을 이동 시 유닛을 제거할 수 있다
같은 타일 내에 유닛이 중복으로 배치되는 것은 불가능하다


배치하는 구현은 이미 전 단계에서 해둔 상태이다
https://velog.io/@yangju058/타워디펜스게임-3.-타일-생성-유닛-배치
현재 상태에서는 유닛이 배치되는 것만 가능하다
Tile스크립트를 만들고 해당 타일에 유닛이 배치되었다면, 그 유닛의 스프라이트와 객체의 정보를 저장한다-> 유닛이 타일에 배치되면 이 함수가 호출되고 유닛의 정보가 저장된다
// 유닛(이미지, 오브젝트)를 타일에 저장하는 함수
public void PlaceUnit(GameObject unit, Sprite UnitSprite)
{
StartCoroutine(HasUnitActive());
placedUnit = unit;
PlacedUnitSprite = UnitSprite;
if (unit == null && UnitSprite == null)
{
hasUnits = false;
}
}
// 유닛 가져오기
public GameObject GetplacedUnit()
{
return placedUnit;
}
public Sprite GetplacedUnitSprite()
{
return PlacedUnitSprite;
}
savezone 영역을 생성한다savezone 영역에 생성된 상태public void BuyUnit(GameObject unitPrefab, Sprite sprite)
{
// 유닛을 구매할 때만 Instantiate 호출
if (currentUnit == null)
{
currentUnit = Instantiate(unitPrefab, saveTile.position, Quaternion.identity);
currentUnit.SetActive(false); // 구매 후 대기 상태
}
currentUnitSprite = sprite; // 스프라이트 설정
currentUnit.SetActive(true); // 유닛 활성화
}
각 타일마다 유닛이 들어오면 어떤 유닛인지 정보가 저장된다
저장해주는 Tile스크립트가 모든 타일에 들어있기 때문에 각각의 정보가 각각의 타일에 저장된다
클릭한 타일에 유닛이 배치되어 있는 상태이고, 마우스가 현재 스프라이트를 가지지 않은 상태일때, 이 타일에 저장된 객체를 savezone 으로 이동시키고, 저장된 스프라이트를 활성화 시킨다
// 타일에 유닛이 있고, 클릭했고, 스프라이트 없을 때
if (hit.collider != null && hit.collider.GetComponent<Tile>() != null
&& hit.collider.GetComponent<Tile>().hasUnits && currentUnitSprite == null)
{
if (Input.GetMouseButtonDown(0))
{
Tile tile = hit.collider.GetComponent<Tile>();
if (tile.placedUnit != null)
{
unitCounting.DelCounting();
currentUnit = tile.GetplacedUnit(); // 타일에서 유닛 가져오기
currentUnitSprite = tile.GetplacedUnitSprite();
tile.MoveTileUnit(null, null); // 타일에서 유닛 제거 & 타일 hasUnit false
currentUnit.transform.position = saveTile.position; // 세이브존으로 이동
}
}
}
유닛을 들고있는 상태에서 삭제 존으로 이동하고 클릭하면 유닛이 삭제되도록 해야한다
근데 문제는 유닛목록창과 삭제영역이 겹친다
-> 그 의미는 유닛목록에 다시 가져다가 클릭하면 유닛이 삭제되게 하려는 거임

유닛을 구매한 뒤 바로 삭제 영역에 가져가서 삭제하는 경우
-> 1. 유닛 목록창에 가져다 대서 삭제
-> 2. 유닛 삭제 활성화 영역 중 빈 영역에 가져다 대서 삭제
1번의 경우를 해결하기 위해서 유닛 목록창에서 유닛을 클릭해서 구매했을 경우, 유닛 목록창의 버튼 컴포넌트 Button UI 요소의 상호작용을 비활성화
-> button.interactable = false;
// 마우스 콜라이더와 (유닛목록에서 구매한) 유닛이 충돌한다면
if (hit.collider && currentUnit)
{
foreach (Button button in unitButtons)
{
button.interactable = false;
}
이제 유닛 목록창의 버튼의 상호작용이 가능하지 않기 때문에 클릭해도 새로운 유닛 스프라이트가 활성화 되지 않는다
-> 이제 삭제존에서 삭제가 가능하고, 유닛 목록창에 클릭해도 삭제가 가능해진다
-> 원래 유닛 목록창에서 유닛을 구매하면 코스트도 차감되는 현상이 있는데, 버튼의 상호작용을 막았기 때문에 코스트 차감되지 않는다!
삭제된 후, 유닛 목록 버튼은 coroutine을 사용하여 1초뒤에 상호작용이 활성화 되게 하였다
-> 1초의 텀이 필요한 이유 : 유닛 목록창을 눌러서 삭제 할 경우, 텀이 없으면 삭제되자마자 바로 클릭한 유닛이 생성된다
// 삭제존 클릭 시
if (Input.GetMouseButtonDown(0) && hit.collider.gameObject.layer == LayerMask.NameToLayer("DeleteZone"))
{
Debug.Log("Delete Unit");
currentUnit.transform.position = saveTile.position; // 세이브존으로 이동
currentUnit.SetActive(false); // 유닛 비활성화
currentUnitSprite = null;
currentUnit = null;
SettingText.gameObject.SetActive(false); // 메시지 비활성화
Destroy(currentUnit);
StartCoroutine(UnitButtonActive());
return;
}


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Tile : MonoBehaviour
{
public bool hasUnits;
public static GameObject[,] grid;
static int gridRows = 5; // 세로
static int gridColumns = 10; // 가로
// 배치된 유닛 오브젝트를 저장하는 변수
public GameObject placedUnit;
public Sprite PlacedUnitSprite;
private void Awake()
{
grid = new GameObject[gridRows,gridColumns];
// 각 칸의 Square 오브젝트를 배열에 저장
for (int row = 0; row < gridRows; row++)
{
for (int col = 0; col < gridColumns; col++)
{
// 오브젝트를 직접 찾거나 생성
GameObject square = GameObject.Find($"Square_{row}_{col}");
if (square != null)
{
grid[row, col] = square;
}
}
}
}
// 코루틴 사용해서 hasUnit이 1초뒤에 true가 되게
IEnumerator HasUnitActive()
{
yield return new WaitForSeconds(0.5f);
hasUnits = true;
}
// 유닛(이미지, 오브젝트)를 타일에 저장하는 함수
public void PlaceUnit(GameObject unit, Sprite UnitSprite)
{
StartCoroutine(HasUnitActive());
placedUnit = unit;
PlacedUnitSprite = UnitSprite;
if (unit == null && UnitSprite == null)
{
hasUnits = false;
}
}
public void MoveTileUnit(GameObject unit,Sprite UnitSprite)
{
placedUnit = unit;
PlacedUnitSprite = UnitSprite;
if (unit == null && UnitSprite == null)
{
hasUnits = false;
}
}
// 유닛 가져오기
public GameObject GetplacedUnit()
{
return placedUnit;
}
public Sprite GetplacedUnitSprite()
{
return PlacedUnitSprite;
}
public void MoveUnitToHiddenTile(Transform hiddenTile)
{
if (placedUnit != null)
{
placedUnit.transform.position = hiddenTile.position; // 대기 타일로 이동
}
}
}
당시에 대부분의 컴포넌트들을 [SerializeField]로 만든것이 실수인 거 같다
-> 메모리를 너무 많이 쓴다
-> 나중에 수정하자
그리고 Update문에서 대부분의 작업을 다 수행한다
-> 너무 비효율적. 함수단위로 나눠서 작업하는 게 좋은데,, 아직은 실력이 모자라다
-> 다시 수정하자
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
public class TileManager : MonoBehaviour
{
// 저장한 유닛정보
public GameObject selectUnit;
public Sprite selectUnitSprite;
// 구입한 유닛정보
public GameObject currentUnit;
public Sprite currentUnitSprite;
public Transform tiles;
// 레이어
public LayerMask tileMask;
public LayerMask DeleteZone;
// 삭제 존
public GameObject Deletezone;
[SerializeField] GameObject UnitPoint;
[SerializeField] Text SettingText;
[SerializeField] Transform canvas; // UI를 표시할 캔버스
// 버튼 배열
[SerializeField] Button[] unitButtons;
private UnitCount unitCounting;
// 배치된 유닛 리스트
private List<int> units = new List<int>();
[SerializeField] UnitCount unitCount;
// 숨겨둔 저장 타일 위치
[SerializeField] Transform saveTile;
Unit1Arrow Unit1Arrow;
private void Start()
{
unitCounting = gameObject.AddComponent<UnitCount>();
Unit1Arrow = GetComponent<Unit1Arrow>();
UnitOver();
}
public void BuyUnit(GameObject unitPrefab, Sprite sprite)
{
// 유닛을 구매할 때만 Instantiate 호출
if (currentUnit == null)
{
currentUnit = Instantiate(unitPrefab, saveTile.position, Quaternion.identity);
currentUnit.SetActive(false); // 구매 후 대기 상태
}
currentUnitSprite = sprite; // 스프라이트 설정
currentUnit.SetActive(true); // 유닛 활성화
}
private void UnitOver()
{
// 유닛의 갯수가 한계 갯수와 같아지면 더이상 생성불가
if (unitCount.GetUnitTotalCount() == unitCount.GetUnitCount())
{
foreach (Button button in unitButtons)
{
button.interactable = false;
}
}
else
{
return;
}
}
// 코루틴 사용해서 버튼이 1초뒤에 활성화되게
IEnumerator UnitButtonActive()
{
yield return new WaitForSeconds(1.0f);
// 유닛 목록 버튼 활성화
foreach (Button button in unitButtons)
{
button.interactable = true;
}
}
private void Update()
{
int mask = tileMask | DeleteZone;
RaycastHit2D hit =
Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition),
Vector2.zero, Mathf.Infinity, mask);
// 반복문으로 타일 가져오고 비활성화
foreach (Transform tile in tiles)
{
tile.GetComponent<SpriteRenderer>().enabled = false;
}
// 마우스 콜라이더와 (유닛목록에서 구매한) 유닛이 충돌한다면
if (hit.collider && currentUnit)
{
foreach (Button button in unitButtons)
{
button.interactable = false;
}
// 삭제존 클릭 시
if (Input.GetMouseButtonDown(0) && hit.collider.gameObject.layer == LayerMask.NameToLayer("DeleteZone"))
{
Debug.Log("Delete Unit");
currentUnit.transform.position = saveTile.position; // 세이브존으로 이동
currentUnit.SetActive(false); // 유닛 비활성화
currentUnitSprite = null;
currentUnit = null;
SettingText.gameObject.SetActive(false); // 메시지 비활성화
Destroy(currentUnit);
StartCoroutine(UnitButtonActive());
return;
}
// 유닛 스프라이트 활성화
if (currentUnitSprite != null)
{
hit.collider.GetComponent<SpriteRenderer>().sprite = currentUnitSprite;
hit.collider.GetComponent<SpriteRenderer>().enabled = true;
}
// 메세지 활성화
SettingText.gameObject.SetActive(true);
// 타일 마우스로 클릭 시
if (Input.GetMouseButtonDown(0))
{
// 유닛이 없으면 생성
if (!hit.collider.GetComponent<Tile>().hasUnits)
{
Tile tile = hit.collider.GetComponent<Tile>();
// 저장된 곳에서 충돌된 타일로 이동
currentUnit.transform.position = hit.collider.transform.position; // 타일로 이동
tile.PlaceUnit(currentUnit, currentUnitSprite); // 타일에 유닛 저장
unitCount.Counting(); // 유닛 카운트 증가
// 타일에 유닛이 있으면 메세지 비활성화
if (tile != null)
{
SettingText.gameObject.SetActive(false); // 메시지 비활성화
}
currentUnit = null;
currentUnitSprite = null;
StartCoroutine(UnitButtonActive());
}
}
}
// 타일에 유닛이 있고, 클릭했고, 스프라이트 없을 때
if (hit.collider != null && hit.collider.GetComponent<Tile>() != null
&& hit.collider.GetComponent<Tile>().hasUnits && currentUnitSprite == null)
{
if (Input.GetMouseButtonDown(0))
{
Tile tile = hit.collider.GetComponent<Tile>();
if (tile.placedUnit != null)
{
unitCounting.DelCounting();
currentUnit = tile.GetplacedUnit(); // 타일에서 유닛 가져오기
currentUnitSprite = tile.GetplacedUnitSprite();
tile.MoveTileUnit(null, null); // 타일에서 유닛 제거 & 타일 hasUnit false
currentUnit.transform.position = saveTile.position; // 세이브존으로 이동
}
}
}
}
}











제일 오래걸렸고, 정리가 제일 안되어 스파게티 코드를 만들어낸 거 같다,,
함수단위로 좀 쪼개서 정리하고 싶었는데 계속 한 곳에서 작업하게 되었다
클린 코드 연습이 필요할 거 같다 ..