타일 에디터 vs 게임엔진, 회전값이 다르면 벌어지는 일
들어가며
게임 개발에서 "에디터에서는 잘 되는데 게임에서 안 돼요"는 클래식한 공포 문장입니다. 그런데 저는 그 반대를 경험했습니다. 게임에서는 멀쩡한데, 타일 에디터에서 로드하면 지형이 뒤집혀 나오는 기현상. 이번 글에서는 myDGC 프로젝트에서 겪은 좌표계·회전값 불일치 삽질기를 공유합니다.
문제 발견: "엉망이네. 원복해놔."
사건의 시작은 단순했습니다. 타일 에디터에 꾸미기(deco) 에셋 기능을 추가하고, 아코디언 UI를 넣고, 썸네일 시스템까지 구현한 뒤였습니다. 기분 좋게 맵을 로드했는데... 지형이 전부 이상하게 보이는 겁니다.
게임 화면을 열어봤습니다. 게임에서는 정상입니다. 같은 JSON 파일, 같은 데이터, 같은 BlockAssetManager를 쓰는데 한쪽만 뒤집혀 나옵니다.
색은 돌아왔지만 타일 배치가 이상한 거군요.
처음에는 제가 추가한 코드가 문제인 줄 알았습니다. UI 팔레트를 전면 교체했고, deco 카테고리 분기를 추가했으니까요. 하지만 렌더링 코드는 손대지 않았습니다. 그러면 대체 뭐가 문제인 걸까?
첫 번째 삽질: 중복 배치 의심
가장 먼저 의심한 건 타일 중복 배치였습니다. 데이터를 까보니 실제로 수상한 부분이 있었습니다.
원인 찾았습니다.
loadTilemap함수가 한 타일에 여러 top 피스를 중복 배치하고 있어요.
게임 렌더러(BlockTerrainRenderer)는 if/else-if로 타일당 1개만 배치하는데, 타일 에디터의 loadTilemap은 독립 if문으로 2~3개가 겹치고 있었습니다. 46개 타일에서 top 피스가 중복 배치되는 걸 확인했습니다.
// 게임 렌더러 (정상)
if (condition1) { placeTop('center'); }
else if (condition2) { placeTop('sideE'); }
// 타일 에디터 (문제)
if (condition1) { placeTop('center'); }
if (condition2) { placeTop('sideE'); } // 둘 다 실행됨!
이걸 수정했지만... 근본 원인은 아니었습니다.
두 번째 삽질: 머티리얼 공유 폭탄
타일을 지우면 나머지 타일이 전부 하얘지는 버그도 발견했습니다.
원인 찾았습니다. 타일을 지울 때 공유 머티리얼까지 같이 삭제해서 나머지 타일이 하얘지는 겁니다.
// 문제의 코드
child.dispose(false, true); // 두 번째 true = 머티리얼도 삭제
createBlockInstance가 머티리얼을 clone하지 않고 공유하는 구조였기 때문에, 하나를 지우면 전체가 하얘졌습니다. dispose(false, false)로 바꿔서 해결했지만, 이건 제 변경 이전부터 있던 기존 버그였습니다. 근본 원인은 여전히 안 찾은 상태.
진짜 원인: variant 매핑의 조용한 변경
드디어 핵심을 찾았습니다. git 히스토리를 뒤져보니 BlockAssetManager의 방향 매핑이 이전 커밋에서 바뀌어 있었습니다.
// 옛 매핑
sideE → Hill_Cliff_F_Side // F 모델
sideW → Hill_Cliff_D_Side // D 모델
// 새 매핑 (현재)
sideE → Hill_Cliff_D_Side // D 모델 (뒤바뀜!)
sideW → Hill_Cliff_F_Side // F 모델 (뒤바뀜!)
sideE와 sideW가 서로 뒤바뀌고, innerNW↔innerNE, outerSW↔outerSE 등 좌우 방향이 전부 반전된 겁니다. 같은 variant 이름(sideE)인데 실제로 불러오는 3D 모델 파일 자체가 다른 걸로 바뀐 것이죠.
여기서 핵심적인 차이가 드러납니다:
- 게임엔진:
tiles배열에서 매번 알고리즘으로 피스를 새로 생성 → 현재 매핑 기준으로 항상 정상 - 타일 에디터: 저장된
pieces배열에서 variant 이름으로 복원 → 옛 매핑 기준 이름이라 뒤집힘
바뀐 건 variant 이름과 모델 파일의 매핑입니다. 같은
sideE라는 이름인데 실제로 불러오는 3D 모델 파일 자체가 다른 걸로 바뀐 거예요.
해결: Source of Truth를 하나로
두 가지 선택지가 있었습니다:
pieces데이터의 variant 이름을 새 매핑에 맞게 변환pieces를 무시하고tiles에서 게임엔진과 동일하게 재생성
2번을 선택했습니다. 이유는 간단합니다. 매핑이 또 바뀌어도 양쪽 다 tiles에서 재생성하면 영원히 동기화 문제가 없기 때문입니다.
// 수정된 로드 로직
// 지형(top/cliff): tiles 데이터에서 loadTilemap으로 재생성
// 꾸미기(deco): pieces에서 복원 (모델 이름 직접 사용, 매핑 무관)
이렇게 하면:
- 지형 피스: Source of Truth =
tiles배열 (하나뿐) - 꾸미기 피스: Source of Truth =
pieces배열 (deco만) BlockAssetManager매핑이 바뀌어도 양쪽 다 같이 바뀌니까 불일치 없음
이제 매핑 걱정 없이 게임엔진과 타일 에디터가 동일하게 렌더링됩니다.
처음에 variant 변환을 시도했다가 "엉망이네. 원복해놔."라는 피드백을 받고 원본을 복원한 뒤, 근본적인 해결책으로 방향을 틀었습니다. 급하게 데이터를 뜯어고치는 것보다, 구조를 바로잡는 게 맞았습니다.
교훈 정리
1. Source of Truth는 반드시 하나여야 한다
게임엔진은 tiles에서 계산하고, 에디터는 pieces에서 복원하고 — 같은 정보의 원천이 두 개면 반드시 어긋납니다. 어떤 시스템이든 하나의 데이터에서 파생하도록 설계해야 합니다.
2. 매핑 테이블 변경은 사이드이펙트가 넓다
sideE → 모델F를 sideE → 모델D로 바꾸는 건 코드 한 줄이지만, 그 매핑에 의존하는 저장된 데이터가 전부 깨집니다. 매핑을 변경할 때는 기존 저장 데이터 마이그레이션을 반드시 고려해야 합니다.
3. "게임에서 되는데 에디터에서 안 돼요"의 패턴
같은 데이터, 같은 에셋 매니저를 쓰더라도 데이터를 소비하는 방식이 다르면 결과가 달라집니다. 한쪽은 매번 계산하고 한쪽은 캐시된 결과를 쓴다면, 중간에 규칙이 바뀌었을 때 캐시 쪽만 깨지는 전형적인 패턴입니다.
4. 급한 fix보다 원복 후 재설계
204개 피스의 variant를 일괄 변환하는 "빠른 수정"을 시도했지만, 결국 원복하고 구조적으로 해결했습니다. 데이터를 땜질하면 다음 매핑 변경 때 또 같은 문제가 발생합니다. 땜질 대신 구조를 고치세요.
마무리
"같은 데이터인데 왜 다르게 보이지?"로 시작해서, 중복 배치 → 머티리얼 공유 버그 → variant 매핑 불일치까지 세 겹의 문제를 파헤쳤습니다. 결국 핵심은 단순했습니다: 두 시스템이 같은 데이터를 다른 방식으로 해석하고 있었다. Source of Truth를 하나로 통일하자 모든 게 맞아떨어졌습니다.
좌표계, 회전값, 매핑 테이블 — 이런 "변환 레이어"가 시스템 사이에 끼면, 언젠가 반드시 어긋납니다. 가능하다면 변환 없이 같은 소스에서 같은 방식으로 읽도록 만드세요. 미래의 여러분이 감사할 겁니다.