반응형
오늘의 목표
- 기초 UI 추가 (Gold, Life, Wave)
- 타워 건설 기능 구현
- 기본 타워 구현
- 기초 UI를 만들고 관리하기 위한 UIManager 생성
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
public static UIManager Instance;
[Header("UI Elements")]
[SerializeField] private TextMeshProUGUI goldText;
[SerializeField] private TextMeshProUGUI lifeText;
[SerializeField] private TextMeshProUGUI waveText;
private void Awake()
{
Instance = this;
}
public void UpdateGold(int value)
{
goldText.text = $"Gold : {value}";
}
public void UpdateLife(int value)
{
lifeText.text = $"Life : {value}";
}
public void UpdateWave(int curr, int max)
{
waveText.text = $"Wave : {curr}/{max}";
}
}
|
cs |


하이라키에 캔버스와 텍스트등을 생성하고 위치값을 조절하여 좌측 상단에 텍스트가 보이도록 설정
- 타워에 대한 관리를 위한 타워 매니저를 생성
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
using UnityEngine;
using UnityEngine.InputSystem;
public class TowerManager : MonoBehaviour
{
public static TowerManager Instance { get; private set; }
[Header("Placement")]
[SerializeField] private Camera mainCamera;
[SerializeField] private GameObject towerPrefab;
[SerializeField] private int towerCost = 10;
[SerializeField] private LayerMask tileLayerMask;
private TowerBase _selectedTower;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
if (mainCamera == null)
{
mainCamera = Camera.main;
}
}
private void Update()
{
if (Mouse.current == null)
return;
if (Mouse.current.leftButton.wasPressedThisFrame)
{
HandleClick();
}
}
private void HandleClick()
{
Vector2 mousePos = Mouse.current.position.ReadValue();
Ray ray = mainCamera.ScreenPointToRay(mousePos);
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
{
TowerBase tower = hit.collider.GetComponentInParent<TowerBase>();
if (tower != null)
{
SelectTower(tower);
return;
}
GridTile tile = hit.collider.GetComponent<GridTile>();
if (tile != null)
{
OnTileClicked(tile);
return;
}
ClearSelection();
}
else
{
ClearSelection();
}
}
private void SelectTower(TowerBase tower)
{
if (_selectedTower == tower)
return;
if (_selectedTower != null)
_selectedTower.SetSelected(false);
_selectedTower = tower;
_selectedTower.SetSelected(true);
}
private void ClearSelection()
{
if (_selectedTower != null)
_selectedTower.SetSelected(false);
_selectedTower = null;
}
public void OnTileClicked(GridTile tile)
{
TryPlaceTower(tile);
}
private void TryPlaceTower(GridTile tile)
{
if (!tile.IsEmpty)
{
Debug.Log("설치 불가 타일이거나 이미 타워가 있습니다.");
return;
}
if (GameManager.Instance == null)
{
Debug.LogError("GameManager 인스턴스가 없습니다.");
return;
}
if (GameManager.Instance.Gold < towerCost)
{
Debug.Log("골드가 부족합니다.");
return;
}
Vector3 spawnPos = tile.transform.position;
GameObject towerObj = Instantiate(towerPrefab, spawnPos, Quaternion.identity);
TowerBase tower = towerObj.GetComponent<TowerBase>();
if (tower == null)
{
Debug.LogError("towerPrefab에 Tower 컴포넌트가 없습니다.");
Destroy(towerObj);
return;
}
tile.SetTower(tower);
GameManager.Instance.AddGold(-towerCost);
}
}
|
cs |
- 타워 제작을 위한 타워 코드 구현
- 확장성을 위하여 추상화 된 기본 타워 베이스 코드를 구현하고 해당 코드를 상속받아서 여러 종류의 타워를 만들 수 있도록 구현
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
using UnityEngine;
public abstract class TowerBase : MonoBehaviour
{
[Header("Stats")]
public float range = 3f;
public float attackInterval = 1f;
public int damage = 5;
[Header("Visual")]
[SerializeField] private GameObject rangeVisual;
protected float _attackTimer;
protected virtual void Start()
{
if (rangeVisual != null)
{
Vector3 s = rangeVisual.transform.localScale;
rangeVisual.transform.localScale = new Vector3(range * 2f, s.y, range * 2f);
rangeVisual.SetActive(false);
}
}
protected virtual void Update()
{
_attackTimer += Time.deltaTime;
if (_attackTimer >= attackInterval)
{
_attackTimer = 0f;
Attack();
}
}
protected abstract void Attack();
protected virtual MonsterAI FindTarget()
{
MonsterAI[] monsters = FindObjectsOfType<MonsterAI>();
MonsterAI closest = null;
float closestDist = Mathf.Infinity;
Vector3 myPos = transform.position;
foreach (var m in monsters)
{
if (!m.isActiveAndEnabled) continue;
float dist = Vector3.Distance(myPos, m.transform.position);
if (dist <= range && dist < closestDist)
{
closest = m;
closestDist = dist;
}
}
return closest;
}
public void SetSelected(bool selected)
{
if (rangeVisual != null)
rangeVisual.SetActive(selected);
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, range);
}
}
|
cs |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
using UnityEngine;
public class BasicTower : TowerBase
{
// 기본 타워는 단일 타겟에게 데미지를 주는 형태
protected override void Attack()
{
MonsterAI target = FindTarget();
if (target != null)
{
target.TakeDamage(damage);
}
}
}
|
cs |
- 타워 건설 시 Gold 사용
- 이동 경로에 건설 불가
- 타워 공격 범위 시각화
- 타워 공격
- 몬스터 제거 시 Gold 증가
를 구현하였습니다.
반응형
'게임 개발 > 랜덤 타워 디펜스' 카테고리의 다른 글
| [랜덤 타워 디펜스] Day 6 타워 랜덤 생성 시스템 구현 (0) | 2025.12.27 |
|---|---|
| [랜덤 타워 디펜스] Day 5 타워 데이터 셋팅 및 합성 시스템 (0) | 2025.12.21 |
| [랜덤 타워 디펜스] Day 3 Waypoint 생성 및 몬스터 이동 구현 (0) | 2025.12.11 |
| [랜덤 타워 디펜스] Day 2 마우스 호버 기능 및 Orbit Camera 기본 구현 (0) | 2025.12.09 |
| [랜덤 타워 디펜스] Day 1 그리드 매니저 기본 설계 (0) | 2025.12.08 |