본문 바로가기
TIL

바이옴에 따른 자원 군집 형성

by vvin39 2025. 7. 11.

내일배움캠프 66일차 TIL

오늘은 Unity에서 우리 게임 맵의 각 바이옴(지역)에 맞춰 자원(Resource Prefab)들이 마치 현실처럼 자연스럽게 군집(Cluster)을 이루며 배치되도록 기능을 구현했다. 그냥 막 뿌리는 게 아니라, 타일이 없는 곳이나 장애물에 겹치지 않도록 배치 시스템을 완성했다!

 

이 작업을 왜 했냐면? (배경)

우리 게임 맵은 '봄', '여름', '가을', '겨울', '광산'처럼 여러 바이옴(Biome)으로 나뉘어 있다. 각 바이옴은 Tilemap을 사용해서 구역을 정의해두었다.

바이옴이란?

월드맵에서 특정 지역을 구분하는 기준이다. 예를 들어, 숲 바이옴에는 나무가 많고, 광산 바이옴에는 광석이 많은 것처럼.

 

자원 군집(Cluster)이란?

자원이 한 지점에 무리지어 자연스럽게 배치되는 형태를 말한다. 현실의 나무 숲이나 광산 덩어리처럼 보이게 하고 싶었다. 그냥 랜덤으로 한두 개씩 흩뿌리는 것보다 훨씬 자연스럽고 탐험하는 재미가 있을 거라고 생각했다.

 

구현을 위한 설계

자연스러운 자원 배치를 위해 다음과 같은 요소들을 설계하고 구현했다.

  • Tilemap : 각 바이옴(봄, 여름, 가을 등)의 구역을 정의하는 데 사용했다.
  • ResourceGroup : 각 바이옴에서 나올 수 있는 자원 프리팹들을 묶어두는 그룹이다. 여기에 자원의 희귀도(weight)를 설정할 수 있게 해서, 어떤 자원이 더 자주 나올지 조절할 수 있게 했다.
  • clustersPerBiome : 각 바이옴마다 얼마나 많은 자원 군집을 만들지 결정하는 수치다.
  • resourcesPerCluster : 군집 하나당 몇 개의 자원 프리팹을 포함할지 정하는 수치다.
  • clusterRadius : 군집이 퍼질 수 있는 최대 반경이다. 이 반경 내에서 자원들이 랜덤으로 흩뿌려지게 해서 뭉쳐있는 듯한 느낌을 냈다.
  • clusterSpawnProbability : 특정 셀이 자원 군집의 중심이 될 확률이다. 이 확률을 조절해서 전체 맵의 자원 밀도를 조절할 수 있게 했다.
  • colliderTilemap : 물, 절벽 등 자원이 배치되면 안 되는 타일 기반의 장애물 정보를 담는 Tilemap이다. 시각적으로는 타일이 있지만, 자원은 놓을 수 없는 곳을 구분하기 위해 만들었다.
  • obstacleLayer : 프리팹(건물, 다른 자원 등)의 Collider를 검사할 때 사용할 레이어다. 자원끼리 겹치거나 건물 위에 자원이 생기는 것을 막기 위해 필요했다.
  • usedPositions : 자원이 이미 배치된 셀 좌표들을 저장하는 HashSet이다. 같은 위치에 자원이 여러 개 겹쳐서 생기는 것을 막기 위해 사용했다.

 

자원 배치는 상황별로 이렇게 처리

자원이 자연스럽게 배치되려면 여러 가지 조건을 확인해야 했다.

 

타일이 없는 곳에는 생성 ❌:

  • if (!tilemap.HasTile(spawnPos)) continue;
  • 기본적으로 타일이 없는 곳(예: 맵 경계 밖)에는 자원을 만들지 않았다.

콜라이더 타일(예: 벽, 물 위)에는 생성 ❌:

  • if (colliderTilemap != null && colliderTilemap.HasTile(spawnPos)) continue;
  • colliderTilemap에 타일이 있는 곳(예: 물 위, 절벽 위)에는 자원을 배치하지 않았다. 시각적으로는 물 타일이 있어도, 자원 자체는 물 속에 생기지 않게 하는 것이다.

프리팹 등 오브젝트와 겹치는 곳에는 생성 ❌:

  • if (Physics2D.OverlapCircle(worldPos, colliderCheckRadius, obstacleLayer)) continue;
  • 이미 다른 프리팹(건물, 다른 자원 등)이 있는 곳에는 자원이 겹쳐서 생기지 않도록 Physics2D.OverlapCircle을 사용해서 충돌을 검사했다.

다른 자원과 겹치는 위치에는 생성 ❌:

  • if (usedPositions.Contains(spawnPos)) continue;
  • usedPositions.Add(spawnPos);
  • 군집 내에서 자원을 배치할 때, 이미 해당 위치에 자원이 생성되었다면 다시 만들지 않도록 HashSet을 사용해서 중복을 방지했다.

 

구현 흐름 

전체적인 자원 생성 흐름은 다음과 같다.

// 맵의 각 바이옴 Tilemap을 순회한다.
foreach (바이옴 tilemap) {
    // 해당 바이옴 내의 모든 타일 위치를 미리 수집해둔다.

    // 각 바이옴마다 정해진 군집 수만큼 반복한다.
    for (군집 수만큼 반복) {
        // 수집된 타일 위치 중 하나를 무작위로 선택해서 군집의 중심 후보로 삼는다.
        // 그리고 정해진 확률(clusterSpawnProbability)에 따라 이 중심에 군집을 생성할지 결정한다.
        if (Random.value < clusterSpawnProbability) {
            PlaceCluster(center); // 군집 생성 함수 호출
        }
    }
}

// 하나의 자원 군집을 배치하는 함수
void PlaceCluster(Vector3Int center) {
    // 군집 하나당 정해진 자원 개수만큼 반복한다.
    for (i = 0; i < resourcesPerCluster; i++) {
        // 군집 중심(center)을 기준으로 clusterRadius 범위 내에서 랜덤 오프셋을 적용하여 자원 생성 위치를 결정한다.
        Vector3Int spawnPos = center + 랜덤 오프셋;

        // 위에서 정의한 4가지 조건(타일 유무, 콜라이더 타일, 오브젝트 겹침, 중복 위치)을 모두 만족하는지 확인한다.
        if (위 조건 만족 시) {
            Instantiate(ResourcePrefab, worldPos); // 조건을 만족하면 자원 프리팹을 생성한다.
        }
    }
}

 

실제 사용 예시

내가 설계한 시스템은 이렇게 활용할 수 있다.

바이옴  가능한 자원 (ResourceGroup)  희귀도 설정 (Weight)
  springTrees 1.0 (자주 나옴)
여름   summerTrees 1.0
광산  stone(1.0), coal(0.6), iron(0.3), gold(0.1) gold는 weight가 0.1이므로 매우 희귀하게 등장한다.
  • resourcesPerCluster = 5, clusterRadius = 3으로 설정하니, 자원들이 5개씩 뭉쳐서 최대 반경 3칸 내에 자연스럽게 흩어지는 모습을 보여줬다.

 

🔍 느낀 점

  • 처음에는 tilemap.HasTile() 조건만 넣고 자원을 배치했지만, 실제 게임에서 쓰려면 현실적인 배치(장애물 회피, 자원끼리 중복 없음)를 위해 충돌 검사(Physics2D.OverlapCircle)와 좌표 추적(usedPositions)이 필수라는 것을 깨달았다. 이 부분에서 시행착오가 좀 있었다.
  • ResourceGroup에 weight를 넣어 자원의 희귀도를 조절하는 구조는 정말 유용했다. 디자이너의 디자인 유연성을 높여주고, 밸런싱 편의성까지 모두 만족시키는 좋은 방법이었다.
  • Tilemap을 렌더링용과 충돌용(colliderTilemap)으로 분리해서 사용한 덕분에, 시각적으로는 타일이 허용되지만 실제 설치는 제한되는 구조를 깔끔하게 만들 수 있었다.
  • 이 구조는 나중에 "월드 저장/불러오기", "자원 맵 UI 표시", "자원 리젠" 등 다양한 시스템과 연동하기에도 아주 유리한 기반이 된다는 것을 느꼈다. 확장성이 좋아서 뿌듯하다!

 

🧠 내가 꼭 기억할 포인트들

  • tilemap.HasTile(pos): 타일 유무를 확인하는 가장 기본적인 필터링 방법이다.
  • colliderTilemap.HasTile(pos): 충돌용 타일맵에 타일이 있으면 자원 설치를 금지하는 데 사용한다.
  • Physics2D.OverlapCircle(): 다른 프리팹(건물, 자원 등)과의 물리적 충돌을 방지하는 데 사용한다.
  • usedPositions: 같은 위치에 여러 자원이 겹쳐서 생기는 문제를 방지하는 데 사용한다.
  • weight: 자원 군집 내에서 특정 프리팹이 등장할 확률, 즉 희귀도를 조절하는 데 사용한다.
  • clusterRadius: 자원 군집이 얼마나 넓게 퍼질지 조절한다.
  • clusterSpawnProbability: 전체 맵에 자원이 얼마나 밀도 높게 분포할지 조절하는 데 큰 영향을 미친다.

 

🔧 앞으로 확장할 아이디어

  • ScriptableObject로 자원 설정을 외부화해서 디자이너도 더 편하게 조절할 수 있도록 만들고 싶다.
  • Minimap이나 Editor Gizmo를 활용해서 자원 군집이 어떻게 배치되는지 시각적으로 바로 확인할 수 있게 하면 좋을 것 같다.
  • 자원 리젠 주기나 시간 경과에 따른 희귀도 변화 같은 동적인 시스템과도 연동해보고 싶다.
  • 자원 정보 저장/복원 기능을 만들어서 세이브 시스템과도 연동할 계획이다.

 

 

 

 

최근댓글

최근글

skin by © 2024 ttuttak