내일배움캠프 62일차 TIL
오늘의 목표는 우리 게임 맵을 사각형 타일맵(Tilemap)으로 구성하면서도, 외형적으로는 육각형 셀 7개가 딱 붙어 있는 것처럼 보이게 하는 것이었다. 그리고 각 육각형 셀 안의 지형은 셀룰러 오토마타(Cellular Automata) 방식을 사용해서 자연스럽게 생성되도록 구현하고 싶었다.
전체 구조
나는 이런 방식으로 시스템을 만들었다.
타일맵(Tilemap): Unity의 기본 사각형 기반 Tilemap을 그대로 사용했다.
외형 구조: 중심에 육각형 셀 하나를 두고, 그 주변 6방향으로 육각형 셀을 배치해서 총 7개의 셀이 보이도록 했다.
지형 생성: 각 셀 안의 지형은 'Plains(들판)', 'Forest(숲)', 'Quarry(채석장)' 세 가지로 나눴고, 각각 랜덤 시드를 기반으로 셀룰러 오토마타를 적용해서 만들었다.
주요 구성 클래스들
이번 시스템을 만들면서 몇 가지 핵심 클래스들을 만들었다.
1. HexGridHelper
이 클래스는 육각형 맵의 좌표 계산을 도와주는 역할을 한다.
Flat-Top 육각형 구조를 기준으로 HexToWorldOffset(q, r, width, height) 함수를 만들었다. 이 함수가 육각형 좌표 (q, r)을 실제 유니티 월드 좌표로 변환해준다.
육각형의 6방향 이웃 정보를 제공하는 기능도 넣었다.
// 이 코드가 Flat-Top 육각형 배열의 위치를 정렬해주는 핵심 수식이다.
// q와 r은 육각형 좌표계의 열과 행이다.
float x = q * (maxWidth * 0.75f); // 가로 간격 조절
float y = r * height; // 세로 위치 조절
if (q > 0) y += height * 0.5f; // q가 양수면 y를 반 칸 올리고
else if (q < 0) y -= height * 0.5f; // q가 음수면 y를 반 칸 내린다.
이전 TIL에서도 이 Flat-Top 좌표 계산이 중요하다고 느꼈었는데, 이번에도 역시 핵심이었다.
2. HexMapData
이 클래스는 각 육각형 셀 내부의 지형 데이터를 관리한다.
TerrainType[,] terrainMap이라는 2차원 배열로 각 셀 안의 지형 정보를 저장했다.
GenerateCellularTerrain() 함수를 만들어서 초기 지형을 랜덤으로 채운 다음, 여러 차례 스무딩(smoothing) 작업을 거치게 했다.
스무딩할 때는 주변 이웃 셀의 Forest/Plains/Quarry 개수를 세서, 다수를 차지하는 지형으로 수렴하게 만들었다.
// 이 코드가 지역적으로 비슷한 지형이 뭉쳐 나오게 유도하는 부분이다.
// 주변 5개 이상이 Forest면 나도 Forest가 되고, Plains면 Plains가 된다.
// Quarry는 4개 이상만 되어도 Quarry가 되게 해서 좀 더 자주 보이게 했다.
if (forest >= 5) newMap[x, y] = TerrainType.Forest;
else if (plains >= 5) newMap[x, y] = TerrainType.Plains;
else if (quarry >= 4) newMap[x, y] = TerrainType.Quarry;
이렇게 하니까 지형들이 자연스럽게 뭉쳐서 마치 실제 지형처럼 보였다.
3. HexMapGenerator
이 클래스는 HexMapData에서 받은 지형 데이터를 실제 타일맵에 그리는 역할을 한다.
셀 하나의 지형 데이터를 받아서 유니티의 타일맵에 실제 타일을 배치한다.
TerrainType에 따라 적절한 TileBase를 선택해서 그렸다.
4. HexMapManager
이 클래스는 전체 시스템을 총괄하는 매니저 역할을 한다.
7개의 육각형 셀(중심 + 육방향)의 위치를 정의했다.
각 위치마다 HexMapData를 새로 생성하고, 셀룰러 오토마타를 적용해서 지형을 채우게 했다.
그 다음 HexMapGenerator를 호출해서 생성된 지형을 타일맵에 그리도록 명령했다.
결과
각 셀 내부는 사각형 타일로 구성되어 있지만, 전체적으로는 내가 의도했던 육각형처럼 보이는 외형을 가졌다.
각 셀 안에서 Forest, Plains, Quarry 지형이 적절히 섞여서 자연스러운 모습을 보여줬다.
하나의 타일맵 안에 다양한 지형이 살아 있는, 자연스러우면서도 반복적이지 않은 맵 연출이 가능해졌다.
📊 Perlin Noise와 Cellular Automata를 비교해봤다
이번에 지형을 만들면서 Perlin Noise와 Cellular Automata 두 가지 방식을 고민했다.
| 항목 | Perlin Noise | Cellular Automata |
| 원리 | 좌표 기반의 연속적인 잡음 함수 | 이웃 기반의 상태 전이 반복 |
| 패턴 | 부드럽고 연속적인 형태 | 뭉침, 불균형도 표현 가능 |
| 랜덤성 | 시드 기반으로 연속적인 값을 생성 | 초기 랜덤값 + 이웃 기반으로 진화 |
| 제어성 | Noise Scale, Threshold로 조절 | Iteration 수, 임계치로 조절 |
| 적합한 용도 | 높낮이, 지형 등급 구분 | 던전, 바이옴, 구조적 변화 표현 |
이번 구현에서는 지형 간의 뚜렷한 경계와 지역적으로 뭉치는 특성을 표현하는 것이 중요했기 때문에, 셀룰러 오토마타가 훨씬 적합하다고 판단했다. Perlin Noise는 너무 부드러워서 내가 원하는 '구역' 느낌을 내기 어려웠다.
🤔 내가 느낀 점
육각형처럼 보이게 하기 위해 Flat-Top 기준 좌표 계산이 정말 중요한 포인트였다. 이 부분을 잘 잡으니까 나머지가 술술 풀렸다.
셀룰러 오토마타의 이웃 기반 스무딩 방식이 지역적인 특징을 표현하는 데 매우 유용하다는 것을 직접 경험했다. 정말 자연스럽게 뭉치는 모습을 보면서 신기했다.
사각형 타일맵을 사용하더라도, 배치 방식과 오프셋을 잘 조절하면 육각형처럼 보이게 할 수 있다는 것을 다시 한번 확인했다. 앞으로 다른 형태의 맵을 만들 때도 이 원리를 응용할 수 있을 것 같다.
💡 내가 꼭 기억할 포인트들
Flat-Top 육각형 배치는 x * 0.75f와 y + (q > 0 ? +0.5f : -0.5f) 방식으로 y축을 보정해야 한다. 셀룰러 오토마타의 핵심은 초기 랜덤값보다 반복적인 이웃 기반 스무딩에 있다. 사각형 타일맵으로도 배치 방식과 오프셋을 잘 조절하면 육각형처럼 보이게 할 수 있다!

'TIL' 카테고리의 다른 글
| Unity에서 Find() 함수 사용을 자제해야 하는 이유 (2) | 2025.07.09 |
|---|---|
| 박싱, 언박싱 그리고 제너릭 (1) | 2025.07.08 |
| 육각형 맵 Flat-Top 전환 & 6방향 랜덤 지형 배치 (0) | 2025.07.04 |
| 아이템 레시피 데이터를 SO에 반영하고 CSV ↔ SO 동기화 기능 구현 (2) | 2025.07.03 |
| CSV 기반 아이템 SO 수정 시 CSV 자동 업데이트 기능 구현 (0) | 2025.07.02 |