모름

드디어 왔습니다. 이 튜토리얼을 하게 된 결정적인 이유가 바로 tile map 때문이었습니다. 대략 앞으로 5챕터 정도가 map generator를 만드는 과정을 담고 있습니다. 

 

이 튜토리얼을 마스터 할 수 이다면, 이렇게 맵을 생성하고, 인스펙터 창을 통해서 타일의 크기, 맵의 크기, 색상, 장애물이 나타나는 빈도 등을 컨트롤 할 수 있는 기능을 배워볼 수 있습니다. 바로 고고!

 

09. tile map

오늘 진행한 내용은 위 Gif와 같이 타일맵을 생성했습니다. 그리고 이 타일맵을 플레이 상이 아닌 리얼타임으로 인스펙터 값이 반영되도록 처리했습니다. Map Editor를 만들어 실시간으로 맵을 조정 할 수 있기때문에 레벨디자인이 훨씬 편해집니다.

 

작업한 내용은 위와 같습니다. 우선 MapGenerator를 만들고 타일을 집어넣고 맵 사이즈에 맞춰서 배열로 맵을 생성해줬습니다. 그리고 outlinePercent 값을 가지고 타일의 테두리를 조절할 수 있게 했습니다. 

 

이 과정을 Play에서말고 리얼타임으로 인스펙터에서 값을 변환하고 editor상에서 보기 위해서 MapEditor를 따로 만들어 관리해줬습니다.

 

코드 정리

코드를 정리 안하고 요약만 하고 넘어가려고 했으나, 튜토리얼이 진행될수록 난이도가 올라가는 느낌이 들어서 아무래도 코드를 기록하고 넘어가야 할 듯 싶습니다. 앞으로 쭉이요...

 

public Variables

public Transform tilePrefab;
public Vector2 mapSize;
    
[Range(0,1)]
public float outlinePercent;

변수는 위와 같습니다. 쿼드로 만든 타일을 프리펩으로 받고, 맵 사이즈를 입력받습니다. 아웃라인도 입력받을 수 있습니다. [Range(0,1)]을 지정해줬습니다.

 

GenerateMap()

public void GenerateMap()
{
    string holderName = "Generated Map";

    if (transform.FindChild(holderName))
    {
        DestroyImmediate(transform.FindChild(holderName).gameObject);
    }

    Transform mapHolder = new GameObject(holderName).transform;
    mapHolder.parent = transform;

    for (int x = 0; x < mapSize.x; x++)
    {
        for (int y = 0; y < mapSize.y; y++)
        {
            Vector3 tilePosition = new Vector3(-mapSize.x/2 + 0.5f + x, 0, -mapSize.y/2 + 0.5f + y);
            Transform newTile = Instantiate(tilePrefab, tilePosition, Quaternion.Euler(Vector3.right * 90));
            newTile.parent = mapHolder;
            newTile.localScale = Vector3.one * (1-outlinePercent);
        }
    }
}

함수명 그대로 맵을 만들어주는 책임을 가지고 있습니다. 위 코드는 Editor를 위한 코드와 섞여 있는데, 우선 타일을 생성하는 코드부터 보겠습니다.

 

GenerateMap() 中 타일맵 생성 부분

for (int x = 0; x < mapSize.x; x++)
{
    for (int y = 0; y < mapSize.y; y++)
    {
        Vector3 tilePosition = new Vector3(-mapSize.x/2 + 0.5f + x, 0, -mapSize.y/2 + 0.5f + y);
        Transform newTile = Instantiate(tilePrefab, tilePosition, Quaternion.Euler(Vector3.right * 90));
        newTile.parent = mapHolder;
        newTile.localScale = Vector3.one * (1-outlinePercent);
    }
}

2중 for문을 통해 mapSize.x와 mapSize.y크기만큼 타일을 생성합니다 이때 타일 포지션을 (-mapSize.x/2 + 0.5f + x)로 정해줍니다. [-mapSize.x/2]는 맵 사이즈가 x로 10일 경우 -5부터 5까지 타일이 생성됨을 의미합니다. 이때 0.5f를 더해줌으로서 유니티상의 사각형 틀과 딱 맞게 만들어 줍니다. 기존에는 모서리가 맞지 않거든요. 이후 [+x]를 해주면서 -5, -4, -3, ... , 4, 5까지 타일을 만들어줍니다. y축도 마찬가지입니다.

 

그리고 추가적으로 newTile의 부모를 맵홀더로 지정해줌으로써 정리해주고 있는 모습입니다. newTile의 localScale을 만지는 이유는 아웃라인을 그려주기 위함입니다. 기존 tile의 로컬스케일은 Vector3.one과 같고, 이를 1-outlinePercent값만큼 조정해줄수있습니다.

 

GenerateMap() 中 Editor를 위한 코드 및 부모를 만들어 정리

string holderName = "Generated Map";

if (transform.FindChild(holderName))
{
    DestroyImmediate(transform.FindChild(holderName).gameObject);
}

Transform mapHolder = new GameObject(holderName).transform;
mapHolder.parent = transform;

GenerateMap()을 유니티 에디터 상에서 실시간으로 보기 위해서 위와 같은 코드를 짰습니다. 만약 if문으로 holderName을 제거해주지 않는다면 계속해서 타일들이 중복해서 생겨나고 유니티 에디터는 멈춰버리고 말겁니다.

 

Destroy()를 사용하지 않고 DestroyImmediate()를 사용하는 이유는 에디터 사에서 오브젝트를 제거하기 때문입니다.

 

그리고 실제로 Transform mapHolder를 만들고 mapHolder를 transform의 자식으로 넣습니다.

 

MapEditor.cs : 에디터상에서 맵을 조절이 반영될수있도록 해줌니다.

using UnityEditor;

[CustomEditor(typeof(MapGenerator))]
public class MapEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        MapGenerator map = target as MapGenerator;

        map.GenerateMap();
    }
}

우선 UnityEditor를 가져온뒤 Editor를 상속시킵니다. 그러면 OnInspectorGUI()함수에 접근이 가능해집니다. OnInspectorGUI()는 말그대로 인스펙터 GUI를 불러옵니다. 단 누구의 GUI를 불러올것인지 명확하지 않기 때문에 [CustomEditor([CustomEditor(typeof(MapGenerator))]를 통해 MapGenerator를 커스텀에디터로 사용할거라고 명시해줍니다.

 

다시 OnInspectorGUI()설명으로 돌아와서, 여기서 base.OnInspectorGUI()를 먼저 실행해줌으로서 원래 유니티 인스펙터 에디터를 똑같이 불러옵니다. 그리고 그 위에 MapGenerator의 참조를 만들어줍니다. 여기서 target이 생뚱맞게 등장합니다. target은 [CustomEditor] 키워드로 이 에디터 스크립트가 다룰것이라 선언한 오브젝트는 target으로 접근할 수 있도록 자동으로 설정됩니다. target을 MapGenerator형식으로 가져옵니다.

 

그리고 map.GenerateMap()을 인스펙터 창에서 실행시켜줍니다.

 

여기서 인스펙터창은 라이프 사이클의 Update와 비슷하게 항시 실행됩니다. 때문에 map.GenerateMap()이 보이는 인스펙터창을 켜놓게 되면 에디터 상에서 이 함수가 자동으로 업데이트되고 있는 것입니다.

 

 

 

이상입니다. 이번 에피소드도 정말 배울게 많았네요. 소화하기가 벅차지만 보람있습니다!