모름

진행 내용

 

 

에피소드 1회에서는 Cellular Automata 개념을 이용하여 타일맵을 랜덤으로 생성하는 맵 제너레이터를 구현합니다.

 

 

에피소드1 의 제목은 Cellular Automata 입니다. 이는 생소한 용어이니 간단하게 개념을 잡고 가겠습니다.

 

출처 https://ooz.co.kr/283 에 따르면 Cellular Automata 의 특징을 아래와 같이 정리합니다. 

 

1. 유한개의 상태를 갖는 셀이 1차원, 2차원 등 공간에 존재합니다.

2. 예를 들어 On/Off, True/False, black/white, 1/0/-1 등으로 각 Cell은 상태를 가집니다.

3. Cell 들은 시간에 따라 현재 자신의 상태와 주변 셀들의 상태에 따라, 근거한 규칙에 의해 변화합니다.

4. 인공지능의 한 분야인 Artificial Life 에 사용된다고합니다.

5. 규칙은 셀 간의 관계를 표현함으로서 만들어지게됩니다.

 

출처 : https://ooz.co.kr/283

 

위 예를 보시면 어떤 원리로 타일맵이 만들어지는지 이해할수있습니다.

 

 

그렇다면?

 

이번 에피소드에서 Cellular Automata 는 어떻게 쓰였을까요.

 

State는 0과 1이며 0은 하얀색타일, 1은 검은타일로 표현됩니다. 즉, 여기서 Cell은 타일 한개로 이해할수있습니다. 

 

규칙은 자신을 제외한 이웃한 타일 8개에서 인접한 타일 중 벽(1)이 4개 초과면 중심타일은 검정타일(1)이 되고, 인접한 벽이 4개 미만이면 중심타일은 하얀색타일(0)이 됩니다. 이 규칙은 SmoothMap() 함수를 살펴보시면 됩니다.

 

마지막으로 오늘 작성한 코드를 아래에 설명합니다.

 

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

public class MapGenerator : MonoBehaviour
{
    public int width;
    public int height;

    public string seed;
    public bool useRandomSeed;

    [Range(0, 100)]
    public int randomFillPercent;

    int[,] map;

    private void Start() {
        GenerateMap();
    }

    private void Update() {
        if (Input.GetMouseButtonDown(0)) {
            GenerateMap();
        }
    }

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

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

    void RandomFillMap() {
        if (useRandomSeed) {
            seed = Time.time.ToString();
        }

        //GetHashCode()를 하면 string 타입의 seed 에서 숫자를 뽑아내는데 렌덤한 숫자라 prng 로서 가치가 있는듯, 자세하게는 더 알아봐야할듯.
        //prng는 pseudoRandomNumberGenerator 의 약자인데, 컴퓨터는 난수를 생성하지 못해서 일반적으로 난수를 위해서 seed가 필요하다.
        //이때 이 난수를 위한 seed를 랜덤하게 생성해주는게 필요한데, 일반적으로 prng 라고 부르는듯하다.
        System.Random prng = new System.Random(seed.GetHashCode());

        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                //아래 조건문은 그림을 그려보던가 해서 이해를 해야겠다... 아마도 정가운데 3*3공간에 1값을 넣어주는 듯 하다.
                if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
                    map[x, y] = 1;
                }
                else {
                    //랜덤퍼센트보다 낮으면 1을 반환하는데, 이는 1벽을 의미하고 0은 빈 공간을 의미한다.
                    map[x, y] = (prng.Next(0, 100) < randomFillPercent) ? 1 : 0;
                }
            }
        }
    }

    void SmoothMap() {
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int neighbourWallTiels = GetSurroundingWallCount(x, y);
                //이웃한 타일이 4개 초과면 자신도 벽이된다..
                if (neighbourWallTiels > 4) {
                    map[x, y] = 1;
                }
                //이웃한 타일이 4개 미만이면 뚫린 공간이된다..
                //즉 이웃한 타일이 많은쪽은 점점 많은쪽으로 벽이되고, 없는쪽은 점점 없는쪽으로 빈공간이 되는것.
                else if(neighbourWallTiels < 4) {
                    map[x, y] = 0;
                }
            }
        }
    }

    int GetSurroundingWallCount(int gridX, int gridY) {
        int wallCount = 0;
        //아래 2중 for문은 gridX의 주면 3*3에 있는 이웃의 값을 가져온다.
        for (int neighbourX = gridX - 1; neighbourX <= gridX + 1; neighbourX++) {
            for (int neighbourY = gridY - 1; neighbourY <= gridY + 1; neighbourY++) {
                //아래 이프문은 예방문인듯. 가로세로값 이내에서 이웃값을 계산하기위한 조건문
                if (neighbourX >= 0 && neighbourX < width && neighbourY >= 0 && neighbourY < height) {
                    //이웃의 값만 가져오기 위해 자기자신의 값을 제외한다
                    //자기 자신을 빼고 이웃한 8개의 타일에서 벽의 개수를 카운트합니다
                    if (neighbourX != gridX || neighbourY != gridY) {
                        wallCount += map[neighbourX, neighbourY];
                    }
                }
                else {
                    //이웃값을 계산할때 width, height값을 포함하지 않았으므로 테두리는 모두 벽으로 인정되며 카운트된다;
                    wallCount++;
                }
            }
        }
        return wallCount;
    }

    private void OnDrawGizmos() {
        if (map != null) {
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    Gizmos.color = (map[x, y] == 1) ? Color.black : Color.white;
                    Vector3 pos = new Vector3(-width / 2 + x + 0.5f, 0,-height / 2 + y + 0.5f);
                    Gizmos.DrawCube(pos, Vector3.one);
                }
            }
        }
    }
}

 

코드를 읽어보면 타일맵이 생성되는 과정을 이해할 수 있습니다. 이상입니다.