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 함수를 통해 맵을 생성할 수 있다. 위 사진에선 인스펙터에 버튼을 만들어 연결해 사용했다.
결과
'Unity > 정보' 카테고리의 다른 글
[Unity] 에셋 번들(AssetBundles) 개념 맛 보기 (0) | 2024.04.05 |
---|---|
[Unity] Unity Muse 살펴보기 (0) | 2024.03.17 |
[Unity] New Input System (1) | 2024.01.13 |
[Unity] 오브젝트 풀링 (Object pooling) (0) | 2024.01.06 |
[Unity] 모바일 게임 성능 최적화 (0) | 2024.01.02 |