공부/인프런 - Rookiss

Part 7-2-5. Unity 2D : MapTool

셩잇님 2024. 12. 14. 17:09
반응형

 

 

🎞 Unity 2D

 

 지난 시간에는 새로운 레이어인 Tilemap_Collision을 새롭게 만들어주고, 갈 수 없는 영역인 'X'를 나타내는 의미로 바위를 사용하여 건물과 같은 장소에 이를 배치하였다. 이 후, 테스트 스크립트를 만들어주어 GetTile을 통해 타일 값을 가져오고 좌표(X/Y)에 타일이 배치되어있는지를 확인하는 스크립트까지 작성해보았다. 이번 시간에는 더 나아가 유니티 내 확장을 통해 MapTool을 만들고 이를 실행할 경우 자동으로 맵을 순차적으로 돌면서 타일을 검사 후, 이를 파일로 저장하는 로직을 작성해보도록 하자.

 


 

💥 MapTool

 

 유니티에서 툴을 확장하기 위해 기본적으로 해주어야 하는 작업이 있다. 바로 'Project' 폴더 내 Editor라는 폴더를 명시해주고 해당 폴더 내부에 작성해야 한다는 점이다. 이는 모든 UI 작업들을 'Resource'에 넣는 것과 마찬가지이다. 따라서 유니티 내부에 새롭게 'Editor' 폴더를 만들어 주도록 하자. 이 후 오늘은 맵툴을 만들것이므로 MapEditor로 스크립트를 새롭게 만들어주도록 하자.

 

새로 만든 Editor 폴더 및 스크립트

 

 또한 에디터 내부에 생성된 스크립트들은 라이브 서비스 외부에 노출되면 않되기 때문에 #if UNITY_EDITOR ~ #endif 영역 내부에 스크립트를 작성해주어야 한다. 이렇게 될 경우, 유니티 내부에서만 사용할 수 있으며 만약 빌드를 하더라도 컴파일러에게 생성을 하지 말아달라는 명령어와 같기 때문에 라이브 시 노출되지 않는다.

 

 간단하게 테스트를 해보도록 하자. 팝업을 띄우고, 팝업의 '예'를 누르면 게임 오브젝트를 생성하고, 그렇지 않을 경우 그냥 취소하는 간단한 함수이다. 

 

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

#if UNITY_EDITOR
using System.IO;
using UnityEditor;
#endif

public class MapEditor
{
#if UNITY_EDITOR
    // % : Ctrl, # : Shift, & : Alt
    [MenuItem("Tools/GenerateMap %#g")]
    private static void HelloWorld()
    {
        if (EditorUtility.DisplayDialog("Hello World", "Create?", "Create", "Cancel"))
        {
            new GameObject("Hello World");
        }
    }
   
#endif
}

 

 위와 같이 EditorUtility내 DisplayDialog 함수를 통해 타이틀, 내용, "예"를 선택할 때 나타날 문구, "아니오"를 선택할 때 나타날 문구를 설정해준다. 만약 예를 누를 경우, "Hello World"라는 이름을 가진 게임 오브젝트를 생성해준다. 하지만 여기서 끝날게 아니라, attribute인 [MenuItem] 스크립트를 통해 해당 함수가 어디에, 어떤 이름으로 배치될 지 설정해주어야 한다. 루키스님은 Tools 폴더의 GenerateMap 이라는 이름으로 만들었기 때문에 나 또한 이와 동일하게 설정해주었다.

 

 단, Tools가 아닌 기존에 존재한 유니티 메뉴인 Edit, Assets, GameObject에도 추가적으로 메뉴를 달 수 있다. 하지만 이렇게 할 경우 가시성이 떨어지므로 추천하지 않는다. 또한 단축키도 설정할 수 있다. 주석으로 설정한 %가 Ctrl, #이 Shift, &가 Alt를 의미한다. 따라서 %#g는 Ctrl-Shift-G를 의미한다. 유니티 내부에서 단축키를 입력하면 정상적으로 나타나는 것을 볼 수 있다. 

 

Tool을 통한 유니티 다이얼로그 시스템!

 

 이제 해당 창에서 Create를 누르면 Hello World의 이름을 가진 게임오브젝트가 생긴 것을 볼 수 있다.

 

 

 정말 별거 아니지만, 맨날 유니티 내부에 있던 기능만 활용하다가 나만의 커스텀 메뉴를 만들어보니 정말 신기했다. 하지만 해당 스크립트를 통해 단순히 게임 오브젝트를 생성하기 위해 진행한 것은 아니기 때문에, 이제 해당 기능을 통해 자동으로 맵을 검사하여 타일을 검출하는 작업을 진행해보도록 하자.

 

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

#if UNITY_EDITOR
using System.IO;
using UnityEditor;
#endif

public class MapEditor
{
#if UNITY_EDITOR
    
    [MenuItem("Tools/GenerateMap %#g")]
    private static void GenerateMap()
    {
        GameObject[] gameObjects = Resources.LoadAll<GameObject>("Prefabs/Map");

        foreach (GameObject go in gameObjects)
        {
            Tilemap tm = Util.FindChild<Tilemap>(go, "Tilemap_Collision", true);
        
            // 파일을 만듭니다.
            using (var writer = File.CreateText($"Assets/Resources/Map/{go.name}.txt"))
            {
                // MinX, MinY, MaxX, MaxY
                writer.WriteLine(tm.cellBounds.xMin);
                writer.WriteLine(tm.cellBounds.xMax);
                writer.WriteLine(tm.cellBounds.yMin);
                writer.WriteLine(tm.cellBounds.yMax);
            
                // 왼쪽 최상단부터 시작한다.
                for (int y = tm.cellBounds.yMax; y >= tm.cellBounds.yMin; y--)
                {
                    for (int x = tm.cellBounds.xMin; x <= tm.cellBounds.xMax; x++)
                    {
                        TileBase tile = tm.GetTile(new Vector3Int(x, y, 0));
                    
                        // 타일이 있는 경우 "1" 작성
                        if (tile != null)
                        {
                            writer.Write("1");
                        }
                        else
                        {
                            writer.Write("0");
                        }
                    }
                
                    writer.WriteLine();
                }
            }
        }
    }
    
#endif
}

 

 먼저, 맵을 와우와 같은 형태인 심리스 형태로 할지, 바람의 나라와 같은 존 형태로 할지 정해야 한다. 어떤 방식으로 할 지에 따라 개발 방향이 달라지는데 현재 프로젝트에서는 바람의 나라와 같은 존 형태로 맵을 저장하도록 한다. 따라서 현재까지 만들어둔 타일맵을 'Resources'의 Prefabs, Map 폴더 내부에 Map_001과 같은 형태로 저장하도록 하자. 따라서 맵이 늘어날 경우 Map_002..3..4와 같이 숫자로 이를 관리하도록 하자.

 

 이에 따라 Gameobject 형태의 배열 gameobjets를 만들어 주고, 해당 배열은 "Prefabs-Map"에 있는 정보들을 불러와 gameobjets에 저장하도록 한다. 이렇게 할 경우, 우리가 만들어준 여러 맵들이 gameobjects에 저장된다. 이 후 foreach문을 순회하며 FindChild<>를 통해 "Tilemap_Collision" 객체가 있는지 검사한다.

 

 이 후, Null 검사를 하고있진 않지만 문제가 될 경우 에러가 뜨게끔 설정하여 이를 표시하기 위해 Null 검사를 하지 않는다. 마지막으로 tm이 null이 아닐 경우, 파일을 만들어 준다. 이 때 파일의 이름 또한 Map_001과 동일하게 설정하기 위해 go.name을 통해 파일을 저장해주도록 한다.

 

 마지막으로 2중 포문을 통해 좌상단부터 타일을 순회하며, 장애물이 있을 경우 "1"을, 그렇지 않을 경우 "0"을 작성한다. 본인이 만든 맵에 대해서 맵툴을 이용하고 확인해보도록 하자. 

 

 

 확인 결과, 파일 이름도 정상적으로 Map_001로 설정되며, 내가 배치한 타일의 정보를 불러와 정상적으로 0과 1로 맵을 그려주고 있다. 비록 따라하며 작업한 것이지만, 유니티 확장기능을 이용해 이를 구현한 것이 정말 신기하다! 😎

 

 

 

반응형