AI/Unity

타워디펜스 만들기 - Stage 운영 구조를 갈아엎고, 월드맵 UI와 선택 체감을 다듬기(Codex)

blacknabis 2026. 3. 8. 01:19
반응형

들어가며

godot엔진을 공부하기 위해 (cluade code, antigravity를 사용하였기 때문에 codex로만 작업을 진행.)

 

이번 작업은 기능 하나를 추가하는 식의 단발성 수정이 아니었다.
스테이지를 계속 늘려갈 수 있는 운영 구조를 만들고, 월드맵 UI를 정리하고, 실제 플레이 체감까지 손보는 쪽에 가까웠다.

정리하면 2026-03-07부터 03-08까지 한 일은 크게 네 가지다.

  • StageId 단일 int 구조를 WorldId + StageNumber + StageKey로 확장
  • Draft / Test / Playable 상태 기반 운영 구조 도입
  • Stage 2를 다시 설계하고 자동 오케스트레이션에 태움
  • 월드맵 popup과 선택 시스템을 실제 사용 기준으로 정리

겉으로 보면 서로 다른 작업처럼 보이지만, 결국 하나의 방향으로 연결돼 있다.

“새 맵을 계속 추가해도, 운영 데이터와 작업 중 데이터를 섞지 않고, UI와 테스트 동선까지 버티는 구조를 만들자.”


1. Stage 1 only 구조를 버린 이유

초기에는 사실상 Stage 1만 운영 기준이었다.
문제는 Stage 2부터는 테스트 잔재 데이터와 작업 중 자산이 섞여 있어서, 맵을 늘릴수록 기준이 흐려진다는 점이었다.

예전 방식의 문제는 명확했다.

  • StageId 단일 int에 모든 의미가 몰려 있었다
  • 운영 스테이지와 작업 중 스테이지를 코드가 구분하지 못했다
  • 월드맵, 저장, 결과 복귀, 스킬 조건, 보스 이벤트가 모두 같은 키 체계에 기대고 있었다
  • Stage 2를 테스트하려고 상태를 억지로 운영 상태처럼 다뤄야 했다

그래서 이번에는 아예 구조를 바꿨다.

public readonly struct StageKey
{
    public readonly int WorldId;
    public readonly int StageNumber;
}

여기에 StageContentStatus를 붙였다.

public enum StageContentStatus
{
    Draft,
    Test,
    Playable
}

핵심은 단순하다.

  • Draft: 제작 중
  • Test: 내부 테스트용
  • Playable: 실제 운영 가능

이렇게 나누고 나니 월드맵, 게임씬, 저장, 에디터 도구가 같은 기준으로 움직이기 시작했다.


2. Stage 2를 다시 “쓸 수 있는 초안”으로 만든 과정

Stage 2는 맵 경로와 타워 슬롯은 이미 들어가 있었지만, 웨이브 데이터는 예전 테스트 흔적이 많이 남아 있었다.

처음 확인한 문제는 이런 식이었다.

  • 현재 맵은 단일 경로인데 웨이브는 존재하지 않는 PathId를 참조
  • 테스트용 낮은 시작 자원과 임시 조합이 섞여 있음
  • 실제 스테이지 초안이라기보다 실험 데이터에 가까움

그래서 목표를 먼저 잠갔다.

  • 역할: 학습 확장형
  • 상태: Draft
  • 목표: 초심자 1회 클리어 가능
  • 구조: 7 waves, PathId 0 only
  • 규칙: Wave 1~6 최소 8마리

초기 초안은 자동 회귀 기준으로는 파손이 없었다.
문제는 실제 플레이였다.

직접 해보니 초반 아처 3개를 전방에 깔면 이후 추가 건설 없이도 클리어가 가능했다.

이건 두 가지를 의미했다.

  • 시작 골드가 많다
  • 중후반 압박이 약하다

그래서 2차 조정에서는 방향을 분명하게 잡았다.

  • InitialGold: 360 -> 280
  • Wave 4~7 물량 상향
  • 초반 Wave 1~3는 유지

최종 초안은 이렇게 정리했다.

InitialGold = 280
InitialLives = 75

W1: Goblin x8
W2: Goblin x8 + Scout x2
W3: Goblin x8 + Scout x2
W4: Goblin x10 + Orc x4
W5: Scout x8 + Goblin x6
W6: Goblin x10 + Orc x5
W7: Goblin x10 + Orc x4 + Shaman x2

아직 Playable은 아니다.
하지만 최소한 “작업 중 Draft 자산”이 아니라, 실제로 검증 가능한 초안 상태까지는 끌어올렸다.


3. 테스트 동선을 바꾼 이유

구조를 엄격하게 만들면 보통 테스트가 불편해진다.

실제로 Draft/Test를 운영 경로에서 막아버리면, 개발자는 새 스테이지를 확인할 때마다 상태를 바꾸거나 별도 메뉴를 타야 한다.
이건 스테이지가 하나둘일 때나 참을 수 있다.

그래서 운영 원칙과 개발자 동선을 분리했다.

  • 운영 기준: Playable만 시작 가능
  • 개발자 기준: 에디터/개발 빌드에서는 Draft/Test도 월드맵에서 직접 시작 가능

이렇게 해두니 두 마리 토끼를 같이 잡을 수 있었다.

  • 운영 구조는 안 무너진다
  • 개발자는 월드맵에서 바로 새 스테이지를 눌러 테스트할 수 있다

이 작업은 단순 편의 기능이 아니라, 콘텐츠 생산 속도를 떨어뜨리지 않기 위한 안전장치에 가까웠다.


4. 월드맵 popup은 결국 프리팹 기준으로 정리했다

이번 구간에서 의외로 시간을 많이 쓴 부분이 월드맵 popup이었다.

문제는 두 가지였다.

4.1 한글 깨짐

처음엔 폰트 문제처럼 보였는데, 실제 원인은 코드에서 런타임으로 만드는 문자열 일부가 깨져 있던 쪽이었다.
총 별, 사용 별, 남은 별, 강화, 별이 부족합니다 같은 문구를 정상 한글로 정리하면서 바로 잡았다.

4.2 유지보수 방식

더 큰 문제는 UI 레이아웃을 코드가 런타임에 자꾸 다시 만들고 밀어넣는 구조였다.

이 구조는 처음엔 빠르지만 결국 이런 문제가 생긴다.

  • 프리팹을 수정해도 코드가 덮어쓴다
  • 어떤 값이 실제 기준인지 파악하기 어렵다
  • 참조가 깨져도 fallback UI가 조용히 뜬다
  • 버그가 “즉시 드러나지 않고” 나중에 이상한 형태로 나타난다

그래서 방향을 바꿨다.

  • SkillTreeUI, HeroSelectionUI를 프리팹 기준 구조로 전환
  • 빌더 메뉴로 프리팹 재생성 가능하게 정리
  • 최종적으로 런타임 fallback 제거
  • 참조 누락 시 Debug.LogError + 중단

이제는 프리팹이 기준이고, 문제가 있으면 바로 드러난다.
유지보수 관점에서는 이쪽이 훨씬 낫다.


5. 선택 체감: “보이는 걸 눌렀는데 왜 안 눌리지?”

플레이하다 보면 가장 거슬리는 버그 중 하나가 이런 종류다.

“오브젝트 이미지를 눌렀는데 선택이 안 된다.”

원인을 보면 대개 단순하다.
보이는 스프라이트를 누르는 게 아니라, 더 작은 충돌 범위나 중심점 거리를 누르고 있기 때문이다.

이번에는 선택 판정을 SpriteRenderer.bounds 기준으로 보강했다.

즉,

  • 영웅
  • 병사
  • 타워

모두 “실제로 보이는 이미지”에 더 가깝게 선택되도록 만든 것이다.

그리고 여기서 한 걸음 더 가서, 타워를 선택하면 공격 범위가 따로 보이도록 했다.

  • 선택 원과 범위 원은 분리
  • 일반 타워는 공격 범위
  • 배럭은 RallyRange
  • 선택 해제 시 즉시 숨김

처음엔 범위용 스프라이트 로드가 안 되어 Hierarchy에는 있는데 게임 화면에는 안 보이는 상태가 있었다.
원인은 새 이미지의 임포트 설정이 Multiple Sprite였던 쪽이었고, Single로 정리하면서 해결했다.

이건 작은 디테일 같지만 체감에는 꽤 중요했다.
“선택됐다”와 “어디까지 닿는지 안다”는 플레이 감각이 완전히 다르기 때문이다.


6. 오케스트레이션을 같이 굴린 이유

이번 작업에서 개인적으로 가장 중요하게 본 건, 기능을 넣는 것보다 회귀를 같이 잠그는 것이었다.

구조 변경, UI 변경, 밸런스 변경은 서로 영향을 많이 준다.
이럴 때 수동 테스트만 믿으면, 당장은 보여도 나중에 어디서 깨졌는지 찾기 어려워진다.

그래서 가능한 건 오케스트레이션에 같이 태웠다.

  • Selection UI smoke
  • Stage1 Pipeline
  • Stage2 Draft Pipeline
  • WorldMap Meta Popup Regression
  • BattleSim Economy Checklist

그리고 포커스를 잃으면 회귀가 멈추는 문제도 같이 잡았다.
프로젝트가 기본적으로 runInBackground: 0이라서, 회귀 실행 동안에는 Application.runInBackground = true를 강제로 켜고 끝나면 복원하도록 보강했다.

결국 이번 이틀 작업은 “기능 추가”라기보다, 작업 흐름 전체를 덜 깨지게 만드는 쪽에 더 가까웠다.


7. 이번 작업에서 얻은 정리

이번 구간에서 특히 확실해진 건 세 가지다.

1. 운영 구조와 개발자 편의는 분리해야 한다

운영 기준을 느슨하게 하면 결국 데이터가 섞인다.
반대로 너무 엄격하게 막으면 개발이 느려진다.
Draft/Test/Playable + 개발자 전용 월드맵 시작 게이트 조합은 이 균형을 잘 잡아줬다.

2. UI는 결국 프리팹이 기준이어야 한다

코드 fallback은 프로토타입 단계에서는 빠르지만, 운영 단계에서는 문제를 숨기는 쪽으로 작동한다.
지금처럼 popup 구조가 정리된 뒤에는 fail-fast가 맞다.

3. 자동 회귀는 기능이 아니라 작업 속도다

선택 판정, popup, 웨이브 초안, 월드맵 진입처럼 서로 얽힌 변경이 많아질수록
“한 번에 돌려보고 상태를 잠글 수 있는 흐름” 자체가 생산성이다.


8. 다음 작업

다음 우선순위는 명확하다.

  1. Stage 2 실제 플레이를 3~5회 더 돌린다
  2. 3아처 방치 클리어가 완전히 깨졌는지 확인한다
  3. 필요하면 Wave 5~7만 추가 미세 조정한다
  4. 이후 Stage 2Test로 올릴지 판단한다

그리고 선택/범위 표시 쪽은 이제 체감이 안정적이면 큰 구조 변경보다는 시각 튜닝 단계로 넘어가면 된다.


마무리

이번 작업은 눈에 확 들어오는 새 기능보다, 프로젝트가 앞으로 더 많은 스테이지와 UI 변경을 감당할 수 있게 바닥을 다시 까는 작업에 가까웠다.

특히 좋았던 건, 코드를 바꾼 뒤 끝내지 않고

  • 자산
  • 프리팹
  • 문서
  • 자동 회귀
  • 실제 플레이 피드백

을 같이 묶어서 잠그는 흐름이 조금씩 자리를 잡기 시작했다는 점이다.

 

지난 블로그의 사진과 외관상 큰차이는 없지만 타워 공격 범위가 잘보인다.

반응형