Unity/정보

[Unity] 2D 랜덤 맵 생성(Celluar Automata)

달시_Dalsi 2024. 1. 20. 00:54
728x90

Celluar Automata 소개

셀룰러 오토마타를 사용한 맵 생성 과정은 다음과 같습니다.
1. 초기 상태 설정: 특정 비율(ex: 50%)로 맵을 벽으로 채웁니다.
2. 셀룰러 오토마타 규칙 적용: 각 타일을 선택하여 주변 8칸 중 벽이 4칸을 초과할 경우 해당 타일을 벽으로, 4칸 미만일 경우 길로 변환합니다.
3. 반복적인 업데이트: 2번의 규칙을 정해진 횟수(ex: 5회)만큼 반복하여 맵을 계속 업데이트합니다.


이러한 과정을 통해 초기에 무작위로 채워진 맵이 셀룰러 오토마타의 규칙에 따라 반복적으로 변화하면서 벽과 길의 패턴이 형성됩니다. 

 

주요 스크립트

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public class CellularAutomata : MonoBehaviour
{
    [SerializeField] Tilemap tilemap;
    [SerializeField] TileBase groundTile;
    [SerializeField] TileBase wallTile;
    [SerializeField] TileBase borderTile;
    [SerializeField] int width = 50;
    [SerializeField] int height = 50;
    [SerializeField] float InitFillPercent = 0.45f;
    [SerializeField] int softlyCount = 5;

    private bool[,] map;

    public void CreateMap()
    {
        tilemap.ClearAllTiles();
        GenerateMap();
        Placement_tile();
    }

    void GenerateMap()
    {
        map = new bool[width, height];
        RandomFillMap();

        for (int i = 0; i < softlyCount; i++)
        {
            SmoothMap();
        }
    }

    void RandomFillMap()
    {
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                map[x, y] = (Random.value < InitFillPercent);
            }
        }
    }

    void SmoothMap()
    {
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                int neighborWallCount = GetSurroundWallCount(x, y);

                if (neighborWallCount > 4)
                {
                    map[x, y] = true;
                }
                else if (neighborWallCount < 4)
                {
                    map[x, y] = false;
                }
            }
        }
    }

    int GetSurroundWallCount(int gridX, int gridY)
    {
        int wallCount = 0;

        for (int neighborX = gridX - 1; neighborX <= gridX + 1; neighborX++)
        {
            for (int neighborY = gridY - 1; neighborY <= gridY + 1; neighborY++)
            {
                if (neighborX >= 0 && neighborX < width && neighborY >= 0 && neighborY < height)
                {
                    if (neighborX != gridX || neighborY != gridY)
                    {
                        wallCount += (map[neighborX, neighborY]) ? 1 : 0;
                    }
                }
                else
                {
                    wallCount++;
                }
            }
        }
        return wallCount;
    }

    void Placement_tile()
    {
        for (int i = -1; i < width + 1; i++)
        {
            Vector3Int leftPos = new Vector3Int(i, -1, 0);
            tilemap.SetTile(leftPos, borderTile);
            Vector3Int rightpos = new Vector3Int(i, height, 0);
            tilemap.SetTile(rightpos, borderTile);
        }
        for (int i = -1; i < height + 1; i++)
        {
            Vector3Int leftPos = new Vector3Int(-1, i, 0);
            tilemap.SetTile(leftPos, borderTile);
            Vector3Int rightpos = new Vector3Int(width, i, 0);
            tilemap.SetTile(rightpos, borderTile);
        }

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                Vector3Int cellPosition = new Vector3Int(x, y, 0);
                tilemap.SetTile(cellPosition, map[x, y] ? wallTile : groundTile);
            }
        }
    }
}

 

코드 설명

GenerateMap() -  초기 맵을 생성하고 부드럽게 만들기위해 지정한 횟수만큼 규칙 2번을 반복한다.


RandomFillMap() - 초기 맵을 InitFillPercent 변수의 값만큼 랜덤으로 채워진다.


SmoothMap() - 셀룰러 오토마타 규칙2번을 이용하여 맵을 부드럽게 만든다. 

 

GetSurroundingWallCount() - 주어진 셀의 주변에 있는 벽의 개수를 반환합니다.

 

VisualizeMap() - 생성된 맵을 Tilemap을 사용하여 2D로 표현한다. 우선 맵의 크기+1만큼 테두리를 생성하여 빈틈이 없게 만든 뒤 생성된 맵에 맞게 벽 타일, 땅 타일을 배치한다.

 

사용 방법

 

유니티 Tilemap을 만들고, Tile Palette를 생성하여 필요한 tile을 추가한뒤 인스펙터를 통해 변수에 할당한다.

Width, Height를 통해 만들고자하는 맵의 크기를 설정하며 

 

코드의 CreateMap 함수를 통해 맵을 생성할 수 있다. 위 사진에선 인스펙터에 버튼을 만들어 연결해 사용했다.

 

결과

728x90