2026.03.25프로젝트 회고
BabylonJS게임디자인컨셉전환세계관ViteA*알고리즘인디게임개발

게임 컨셉 전환기 - Bad North를 만들려다 원 RPG가 탄생한 이야기

시작은 Bad North였습니다

myDGC 프로젝트는 처음에 명확한 레퍼런스가 있었습니다. Bad North — 미니멀한 아이소메트릭 뷰에서 섬을 방어하는 그 게임. 타일 기반 맵 위에서 유닛을 배치하고, 적의 웨이브를 막아내는 전략 게임을 3D로 구현하겠다는 목표였습니다.

그런데 구현을 하다 보면 원래 의도와 다른 방향으로 흘러가는 순간이 옵니다. 계단 오르내리기 하나 만드는 데 A* 경로탐색을 세 번이나 뜯어고치고, 캐릭터 애니메이션 속도 하나 맞추는 데 반나절을 쓰다 보면 — 어느 순간 "이거 Bad North가 아닌데?"라는 생각이 들기 시작합니다.

게임 개발 중 방향 전환의 순간

기술적 삽질의 연속

계단, 그 끝나지 않는 싸움

3D 타일맵에서 높이 차이가 있는 타일 간 이동은 생각보다 훨씬 까다로웠습니다. 처음에는 3-phase 경로 로직이라는 복잡한 시스템을 만들었는데, 결국 돌아돌아 단순한 A* 하나로 통일하게 됩니다.

buildRoutedPath — 복잡한 3-phase 경로 로직 제거, 단순 A* 하나로 통일 (getNeighbors()가 stairLink 처리)

경로는 해결했는데, 캐릭터가 계단을 오를 때 Y축(높이) 보간이 영 어색했습니다. 타일 중앙에서 중앙으로 이동하는 로직이라 계단 모델의 실제 경사와 맞지 않았던 거죠.

계단 모델이 applyDungeonModelTransform으로 중심축 보정되어 타일 중앙에 맞춰지는데, 캐릭터 이동은 타일 중앙→중앙으로 움직이니까 계단의 실제 경사와 안 맞습니다.

해결책은 타일 쌍 기반 판정 대신, 계단의 실제 월드 좌표 바운딩박스로 progress를 계산하는 것이었습니다. getStairProgress(x, z) 함수가 계단 모델의 기하학적 위치를 기반으로 진행도를 반환하게 만들어서, 캐릭터가 계단에 발을 딛는 순간부터 자연스럽게 올라가도록 수정했습니다.

걷지 않는 기사, 싸우지 않는 메이지

경로 문제를 겨우 해결하니 이번엔 애니메이션이 말을 안 듣습니다. Knight 모델의 walk 애니메이션이 너무 느려서 캐릭터가 미끄러지듯 이동하는 문제가 있었습니다.

typescript
// stateMap에서 walking_a -> walk으로 매핑
// speedRatio = 1.8이 적용되어야 하는데...
// 같은 상태면 무시하는 로직 때문에 speed 변경이 안 될 수 있음
if (currentState === 'walk') return; // line 186

혹시 실제로 속도가 바뀌었는데 체감이 안 되는 건 아닌지... 1.8이면 기존 대비 80% 빠른 것인데, 기존 walk 애니메이션 자체가 느릴 수 있습니다. 2.5로 더 올려볼까요?

결국 1.8에서 2.5로 올려서 해결. 때로는 "코드가 맞는데 왜 안 되지?"의 답이 "값이 충분히 크지 않아서"인 경우도 있습니다.

그리고 메이지의 자동공격 버그. 이건 진짜 찾기 어려웠습니다.

typescript
// completeMovement에서 unit.stopMoving()을 호출하지 않음
// → 유닛이 MOVING 상태에 영원히 머무름
// → CombatSystem이 MOVING 상태 유닛은 공격 대상으로 안 잡음

이전 MovementSystem에서는 completeMovement에서 unit.stopMoving()을 호출했었습니다. UnifiedMovementSystem으로 통합하면서 Movable 인터페이스 기반으로 바꿨는데, Movable에는 stopMoving()이 없어서 누락된 겁니다.

나이트와 로그는 멀쩡한데 메이지만 안 되는 이유가 특히 교활했습니다. 근접 유닛(나이트/로그)은 CombatSystem이 먼저 COMBAT 상태로 전환시켜서 우연히 동작했고, 원거리 유닛인 메이지는 COMBAT 진입 타이밍이 달라서 버그가 드러난 것이었습니다. 같은 버그인데 유닛 타입에 따라 증상이 달랐던 전형적인 케이스입니다.

Vite와 URL의 함정

기능 구현도 힘든데, 아예 게임이 로드가 안 되는 상황까지 겪었습니다.

window.goTown이 등록 안 됐다면 main.ts 자체가 실행되지 않는 것입니다. 게임 초기화가 아예 안 되고 있습니다.

원인은 허무할 정도로 단순했습니다. Vite의 public/ 폴더 서빙 규칙을 몰라서 잘못된 URL로 접속하고 있었던 겁니다.

code
❌ /public/game3d.html
✅ /game3d.html

❌ /public/actioneditor.html  
✅ /actioneditor.html

index.html의 링크 자체가 /public/ 경로로 되어 있어서, 모든 링크가 404를 내고 있었습니다. Claude Code가 즉시 수정해줬지만, 이런 종류의 "설정 실수"는 코드 로직 버그보다 디버깅이 더 어렵습니다. 로직에는 문제가 없으니까요.

복잡한 코드 디버깅 과정

그래서 이 게임은 뭔가요?

이 모든 삽질을 거치며 게임의 정체성이 서서히 드러났습니다. Bad North의 "미니멀한 섬 방어"와는 완전히 다른 무언가가 되어 있었습니다.

전투 시스템도 Bad North와는 꽤 달라졌습니다. 나이트(근접 탱커), 로그(근접 딜러), 메이지(원거리) 같은 클래식한 클래스 구분에, 각 유닛의 공격 사거리(range)와 인식 거리가 전술적 깊이를 만듭니다. 플레이어 유닛은 죽어도 시체가 남고, 적만 페이드아웃하는 디테일까지.

교훈: 컨셉 전환은 실패가 아니다

1. 시스템 통합 시 인터페이스 체크리스트를 만드세요

MovementSystemUnifiedMovementSystem으로 통합하면서 stopMoving() 호출이 누락된 건, 인터페이스가 바뀔 때 기존 호출부를 전수 조사하지 않아서 생긴 문제입니다. 리팩토링할 때는 "이전 시스템이 하던 것" 목록을 먼저 만들고, 새 시스템이 그걸 다 커버하는지 확인하는 게 좋습니다.

2. "우연히 동작하는 코드"를 경계하세요

나이트와 로그가 정상 동작한 건 순전히 CombatSystem의 상태 전환 타이밍 덕분이었습니다. 메이지를 추가하기 전까지는 아무 문제 없어 보였죠. 이런 "우연한 정상 동작"은 새 기능을 추가할 때마다 시한폭탄처럼 터집니다.

3. 레퍼런스는 출발점이지 목적지가 아닙니다

Bad North를 레퍼런스로 시작했지만, 구현 과정에서 나만의 게임이 되어갔습니다. 계단 시스템, 유닛 클래스, 이중 세계관 — 이런 것들은 "Bad North를 똑같이 만들자"는 마인드셋에서는 나올 수 없는 것들입니다. 구현하면서 자연스럽게 변형되는 걸 두려워하지 마세요. 그게 오리지널리티의 시작입니다.

4. 빌드 도구의 규칙을 먼저 이해하세요

Vite의 public/ 폴더 서빙 규칙 같은 건 공식 문서 한 번이면 해결되는 문제입니다. 하지만 모르면 "코드가 분명 맞는데 왜 안 돼?"라며 몇 시간을 날릴 수 있습니다. 새 도구를 쓸 때는 코드를 작성하기 전에 해당 도구의 컨벤션부터 파악하는 습관이 중요합니다.


myDGC는 아직 갈 길이 멉니다. 현실 세계 파트는 아직 미구현이고, 전투 시스템도 계속 진화 중입니다. 하지만 "전투가 Bad North가 아니야"라는 깨달음의 순간이 오히려 이 프로젝트에 고유한 방향성을 선물해줬다고 생각합니다. 때로는 원래 계획에서 벗어나는 것이, 더 흥미로운 곳으로 데려다줍니다.