[타워 레벨/업그레이드 시스템]
● 디펜스 게임에서는 타워의 레벨 업그레이드와 속성 변화가 핵심 콘텐츠 중 하나이다.
● ScriptableObject를 이용하여 유지보수성이 높고 확장하기 쉬운 타워 업그레이드 시스템을 구현했다.
■ UpgradeCost: 업그레이드 비용 정보
● 레벨마다 필요한 자원의 종류와 수량을 정의
[System.Serializable]
public class UpgradeCost
{
public ItemType itemType; // 필요한 자원 종류
public int amount; // 필요한 자원 수량
}
■ TowerLevelData: 타워의 레벨별 세부 데이터
● 공격력, 사거리, 공격 속도, 발사체 수 등의 정보를 담음
● upgradeCosts를 통해 해당 레벨에서 필요한 업그레이드 비용도 관리
● 설치된 오브젝트의 외형(Prefab)과 발사체도 레벨에 따라 변경 가능
[CreateAssetMenu(menuName = "Tower/LevelData")]
public class TowerLevelData : ScriptableObject
{
public GameObject towerPrefab;
public GameObject projectilePrefab;
public int atk;
public float attackRange;
public float attackSpeed;
public int projectilesNumber;
public float projectilesAngle;
public List<UpgradeCost> upgradeCosts;
}
■ TowerData: 하나의 타워가 가진 모든 레벨의 데이터
● 한 종류의 타워가 가질 수 있는 모든 레벨 데이터 리스트
● GetLevelData() 함수로 특정 레벨의 세부 데이터를 쉽게 참조 가능
● 예시: 유도 미사일 타워는 레벨 0~4까지 각각 다른 속성과 외형을 가질 수 있음
[CreateAssetMenu(menuName = "Tower/TowerData")]
public class TowerData : ScriptableObject
{
public List<TowerLevelData> towerLevelDatas;
public TowerLevelData GetLevelData(int level)
{
return (level >= 0 && level < towerLevelDatas.Count) ? towerLevelDatas[level] : null;
}
}
■ ScriptableObject를 사용한 이유
● 데이터 중심 설계: 게임 밸런싱과 레벨 디자인을 코드 수정 없이 조정 가능
● 에디터 친화적: 인스펙터에서 바로 값 수정, 프리팹/발사체 교체 가능
● 유지보수 용이: 레벨 추가/수정 시 코드 변경 없이 SO 데이터만 갱신
● 메모리 효율: 다수의 타워 인스턴스가 같은 SO를 참조해 데이터 중복 없음
■ 적용 예시
● 게임 중에는 해당 데이터를 참조하여 UI에 표시하거나 실제 타워 기능에 반영
TowerData towerData = ... // 예: GuidedTowerData
TowerLevelData currentLevelData = towerData.GetLevelData(currentLevel);
float atkSpeed = currentLevelData.attackSpeed;
List<UpgradeCost> costs = currentLevelData.upgradeCosts;
[타워]
● 타워들은 다양한 공격 방식과 업그레이드 시스템을 공통적으로 갖추고 있기 때문에 모든 타워의 부모 클래스인 Tower를 설계
■ 설계 목적
● 타워마다 공격 방식이나 특수 기능은 다르지만 공통 동작이 존재
○ 일정 주기로 공격
○ 레벨에 따라 스탯 변화
○ 업그레이드 자원 체크
○ 프로젝타일 발사 등
● 이를 모두 묶어 상속 기반의 구조로 구현
■ Tower
● 모든 타워의 기반 클래스
● 내부에서 공격 주기 관리, 스탯 적용, 레벨업 처리 등을 담당
● 자식 클래스는 Attack()만 오버라이드하면 자신만의 공격 방식을 정의할 수 있음
■ 타워의 전투 주기
● Update()를 통해 일정 시간마다 공격 실행
● 실제 공격 로직은 Attack() 메서드에 정의되어 있으며 자식 클래스가 오버라이드할 수 있도록 virtaul로 선언
private void Update()
{
attackTimer += Time.deltaTime;
if (attackTimer >= attackSpeed)
{
Attack();
attackTimer = 0f;
}
}
■ virtaul Attack(): 공격 로직의 분리
● 부모 클래스 Tower는 타이머 기반 공격 흐름만 관리
● 실제 공격 방식은 각 자식 클래스에서 오버라이드한 Attack()에서 정의
public class PulseTower : Tower
{
protected override void Attack()
{
// 범위 내 적 탐지 후 데미지 부여
}
}
■ 스탯 적용 및 레벨업
● ScriptableObject로 구성된 TowerData 및 TowerLevelData를 기반으로 현재 레벨에 해당하는 타워의 공격력, 사거리, 속도, 발사체 수 등을 반영
● 외형도 towerPrefab에 따라 교체 가능
● 레벨업 절차 요약
○ CheckLevelUp(): 업그레이드 가능한지 확인
○ SpendResource(): 자원 소모
○ LevelUp(): 레벨 증가 후 스탯 적용
■ Projectile 생성
● Object Pool을 통해 재사용하며 퍼포먼스를 고려
protected void GenerateProjectile(GameObject bulletPrefab, Vector3 startPos, Vector3 dir, int damage)
{
//Object Pool에서 꺼내고 정보 설정
GameObject bullet = TowerPoolManager.Instance.Pop(bulletPrefab);
bullet.GetComponent<TowerBullet>().SetInfo(startPos, dir, damage);
}
■ 설계 장점
● 코드 재사용: 공통 로직은 부모에 두고, 자식은 핵심만 구현
● 확장 용이: 새로운 타워 추가 시 Attack()만 구현하면 됨
● 유지보수 효율: 레벨업, 스탯 관리 등의 공통 기능은 일괄 수정 가능
● 성능 최적화: Object Pool과 데이터 중심 설계(SO) 병행
[비유도 타워]
● 비유도 사격 방식을 가지고 있으며 여러 개의 발사체를 전방으로 퍼지게 발사
■ 기본 구조
● Tower의 자식 클래스로 Attack()을 오버라이드하여 비유도 사격 방식을 구현함
● 하나의 축에서 퍼지는 공격 패턴을 가지며 앞 방향(firePivot.forward) 기준으로 발사됨
■ 발사체 퍼짐 로직
● 발사체의 개수와 각도에 따라 좌우로 퍼지는 중앙 기준 분산 각도를 계산
● 예시: 발사체가 3개이고 간격이 10도일 경우 → -10°, 0°, 10°로 분산
float projectileAngleSpace = projectilesAngle;
int projectileNumber = projectilesNumber;
float minAngle = -(projectileNumber - 1) / 2f * projectileAngleSpace;
■ 살짝 아래로 기울어진 방향
● 수직 방향으로 살짝 아래를 향하도록 조정하여 발사체가 완전히 수평이 아니라 대각선 아래로 날아가게 함
● inclination 값으로 기울기 강도 조절 가능
dir = (dir + Vector3.down * inclination).normalized;
■ 최종 공격 처리
● Tower 부모 클래스에 정의된 GenerateProjectile() 메서드를 통해 실제 발사체 생성
● Object Pool을 사용해 성능 최적화
GenerateProjectile(projectilePrefab, startPos, dir.normalized, atk);
■ 설계 포인트
● 유도 없이 직진하는 투사체를 다루는 단순한 로직이지만 퍼짐 각도와 기울기를 조절함으로써 다양한 시각적 연출과 전략적 배치 가능
● 데이터 기반으로 조절 가능한 값들(projectileAngle, inclination, projectileNumber)은 게임 밸런싱에 매우 유용
[유도 타워]
● 정확성과 자동 타겟팅 기능으로 전략적인 의미를 더함
■ 기본 구조
● Tower의 자식 클래스이며 Attack()을 오버랄이드하여 범위 내에서 가장 가까운 적을 추적해서 발사함
● 사거리를 기준으로 타겟을 선정하고 유도형 발사체를 생성
■ 타겟 선정 로직
● EnemyManager에서 현재 위치(transform)를 기준으로 가장 가까운 적을 가져옴
● 가장 가까운 순으로 최대 projectileNumber 개수까지 타겟팅
● 탐색 범위는 attackRange로 제한
target = EnemyManager.Instance.GetNearestMonsters(transform, projectilesNumber, attackRange);
● GerNearestMonsters()
○ 정렬: 거리 기준으로 가까운 순으로 정렬(sqrMagnitude 사용으로 최적화)
○ 필터링: distanceThreshold 이내의 적만 필터링
○ 보정: 목표 수(count)보다 적은 경우 마지막 적을 반복하여 리스트 채움 (무기 수량과 일치 유지)
public List<EnemyController> GetNearestMonsters(Transform tower, int count, float distanceThreshold)
■ 유도형 공격 로직
● firePivot 기준으로 각 적을 향해 방향 벡터 계산
● 해당 방향으로 GenerateProjectile()을 호출하여 발사체 생성
● 유도 로직은 간단하지만 실제 타겟팅된 적을 향한 정확한 방향 조준이 핵심
for (int i = 0; i < target.Count; i++)
{
Vector3 dir = (target[i].transform.position - firePivot.position).normalized;
Vector3 startPos = firePivot.position;
GenerateProjectile(projectilePrefab, startPos, dir, atk);
}
■ 설계 장점
● 범위 기반 탐색 + 자동 타겟팅으로 방치형/자동 전투에 적합
● GetNearestMonsters()는 범용적으로 재활용 가능
● 발사체 개수가 유동적일 경우에도 로직 안정성 보장
[범위 타워]
● 한 번에 여러 적을 처리할 수 있는 핵심 유닛
● 광범위 데미지 처리와 시각적 퍼짐 이펙트를 구현
■ PulseTower
● 기본 Tower 클래스를 상속받아 Attack() 로직을 오버라이드
■ 공격 범위 탐지
● attackRange 반경 내의 적들을 LayerMask를 기준으로 탐색
● EnemyController 컴포넌트를 가진 대상에게만 데미지 적용
Collider[] overlapped = Physics.OverlapSphere(transform.position, attackRange, targetLayer);
● 한 번의 공격으로 여러 적에게 동시에 피해를 입힐 수 있음
if (enemy != null)
{
enemy.OnDamaged(atk);
}
■ 퍼지는 이펙트: ExpandEffect
● 공격 시 pulseEffectPrefab 이펙트를 Object Pool에서 꺼내 위치 지정
● ExpandEffect 스크립트를 통해 시간에 따라 원형으로 커지는 이펙트 연출
GameObject effect = TowerPoolManager.Instance.Pop(pulseEffectPrefab);
effect.transform.position = transform.position;
● duration 동안 점점 커지는 방식으로 광범위한 임팩트 표현
● 끝나면 Push()로 다시 풀에 반환해 퍼포먼스 최적화
transform.localScale = Vector3.Lerp(Vector3.zero, max, t);
■ 설계 장점
● OverlapSphere로 간단하게 범위 내 적 모두 타격 (AOE 공격)
● Object Pooling을 사용하여 이펙트를 매번 생성하지 않고 재활용
● 커지는 이팩트로 공격 타이밍과 범위를 직관적으로 전달해 타격감을 시각화함
'내일 배움 캠프 > 따봉Ib' 카테고리의 다른 글
| [따봉Ib 👍] ASTROWARD (4) 최종 (0) | 2025.06.20 |
|---|---|
| [따봉Ib 👍] ASTROWARD (3) 트러블 슈팅 (0) | 2025.06.17 |
| [따봉Ib 👍] ASTROWARD (2) (0) | 2025.06.17 |