개요
로그라이트는 매번 다른 맵이 나와야 다시 할 맛이 난다. 그래서 맵을 손으로 그리지 않고 규칙으로 만들어내는 작업을 진행했다. 이번 편은 그 규칙 이야기다. (전부 Unity MCP 바이브 코딩으로 붙였다)
1단계 — BSP로 땅 나누기
BSP는 Binary Space Partitioning, 우리말로 “이진 공간 분할”이다. 이름은 어렵지만 하는 일은 단순하다.
쉽게 말하면
큰 종이 한 장을 반으로 자른다. 그 조각을 또 반으로 자른다. 그 조각을 또 반으로… 이걸 몇 번 반복하면 크기가 제각각인 칸들이 생긴다. 그 칸마다 방을 하나씩 그려 넣는 것이다.
정리하면 이런 순서다.
- 맵 전체를 하나의 큰 사각형으로 시작
- 세로 또는 가로로 잘라서 두 조각으로 나눔 (자르는 위치는 랜덤, 단 너무 얇은 조각이 안 나오게 최소 크기 제한)
- 정해둔 깊이만큼 2번을 반복
- 더 이상 안 자르는 마지막 조각(= 잎 노드) 안에 방을 하나 배치
- 형제 조각끼리 방 중심을 이어 복도를 뚫음
4번에서 방을 조각에 꽉 채우지 않고 여백을 두는 게 포인트다. 꽉 채우면 방과 방이 붙어버려서 복도가 의미가 없어진다.
왜 굳이 BSP인가
방을 그냥 랜덤 좌표에 뿌리면 겹치거나, 서로 너무 멀거나, 갈 수 없는 방이 생긴다. BSP는 자를 때부터 영역이 겹치지 않으니 방도 안 겹치고, 형제끼리 이어 올라오면 모든 방이 연결되는 게 보장된다. 연결 검사를 따로 안 돌려도 된다는 게 제일 큰 이득이었다.
2단계 — 구역(zone)과 패턴
방이 다 똑같이 생기면 심심하다. 그래서 방마다 역할을 붙였다.
- 시작 구역 — 플레이어가 내려오는 곳, 적 없음
- 일반 구역 — 야생 슬라임이 나오는 곳
- 탈출 구역 — 살아서 돌아가려면 여기까지 와야 함
- 특수 구역 — 휴식소 등
배치 규칙은 단순하게 잡았다. 시작 구역과 탈출 구역은 BSP 트리에서 가장 멀리 떨어진 두 방에 넣는다. 그래야 “들어가서 살아 돌아오는” 루프가 짧아지지 않는다.
여기에 방 안쪽을 채우는 패턴을 얹었다. 기둥이 규칙적으로 선 방, 가운데가 뻥 뚫린 방, 벽을 따라 장애물이 늘어선 방 같은 것들이다. 같은 크기의 방이라도 패턴이 다르면 플레이 느낌이 달라진다.
3단계 — 랜덤 슬라임 스포너
적 배치도 손으로 찍지 않고 규칙으로 돌린다.
- 시작 구역과 탈출 구역은 스폰 제외
- 방 넓이에 비례해 최대 마리 수를 정함 (좁은 방에 10마리 몰아넣으면 그냥 사고다)
- 벽에 딱 붙은 칸, 복도 입구 칸은 제외 (문 열자마자 맞는 걸 막기 위해)
- 지역별 낙인 단계가 오르면 티어가 같이 오름 (
spec-004, 3스택마다 티어 상승, 티어당 +15%)
쉽게 말하면
술래잡기를 하는데 문 바로 뒤에 술래가 서 있으면 게임이 아니라 그냥 습격이다. 그래서 “문 앞 한 칸은 비워둘 것” 같은 규칙을 넣어준 것이다.
4단계 — 방이 너무 네모나서
여기까지 돌려보니 문제가 하나 보였다. 너무 네모다.
BSP는 사각형을 자르는 방식이니 결과물도 반듯한 사각형 방과 직각으로 꺾이는 복도뿐이다. 동굴이라기보다 사무실 같았다.
노이즈로 모서리 갉아먹기
그래서 방 가장자리를 침식(erode) 시켰다. 노이즈를 써서 각 칸마다 0~1 사이 값을 뽑고, 값이 기준보다 낮으면 그 칸을 벽으로 되돌린다. 대신 방 가장자리 쪽일수록 지워질 확률이 높게 가중치를 줬다. 안쪽까지 갉아먹으면 방에 구멍이 뚫려버린다.
노이즈가 뭔가요
그냥 랜덤 숫자와는 조금 다르다. 완전 랜덤은 옆칸끼리 값이 아무 상관이 없어서 지지직거리는 모래알처럼 나온다. 노이즈는 옆칸끼리 값이 비슷하게 이어져서 구름이나 산맥 같은 부드러운 얼룩이 나온다. 자연스러운 지형이 필요하면 이쪽을 쓴다.
셀룰러 오토마타로 다듬기
침식만 하면 벽에 점이 지저분하게 튀고, 혼자 떨어진 벽 조각이 생긴다. 이걸 셀룰러 오토마타(cellular automata) 로 정리했다.
규칙은 딱 한 줄이다.
각 칸을 보고, 주변 8칸 중 벽이 5개 이상이면 그 칸도 벽이 된다. 아니면 바닥이 된다.
이걸 맵 전체에 3~4번 반복해서 돌린다. 그러면 튀어나온 점들은 주변에 밀려 사라지고, 울퉁불퉁하던 경계선이 매끈한 곡선처럼 정리된다.
쉽게 말하면
반에서 다수결을 여러 번 하는 것과 비슷하다. 혼자 다른 의견을 낸 칸은 주변에 밀려서 결국 주변을 따라간다. 몇 번 반복하면 비슷한 애들끼리 덩어리로 뭉친다.
반드시 지켜야 했던 것
이렇게 깎다 보면 길이 끊어질 수 있다. 그래서 침식·평활화가 끝난 뒤 시작 구역에서 flood fill(물 붓기)로 퍼뜨려서 탈출 구역까지 닿는지 검사하고, 안 닿으면 그 맵은 버리고 다시 생성하게 했다.
flood fill이 뭔가요
시작 칸에 물을 붓는다고 상상하면 된다. 물은 벽이 아닌 칸으로만 계속 번져나간다. 다 번진 뒤에 탈출구가 젖어 있으면 길이 있는 것이고, 말라 있으면 길이 끊긴 것이다.
한줄 평
- 자연스러워 보이는 건 대부분 자연스럽게 만든 게 아니라, 규칙을 몇 겹 겹쳐서 만든 것이었다.