[유니티]탑다운 슈팅 따라하기 #10 맵 연결하기

안녕하세요 유랑입니다.

 

 

실력향상을 위해서 오늘은 유튜브를 따라하면서 공부하겠습니다.

궁금하신점 있으시다면 댓글로 남겨주세요^^

 

 

 

1. 탑다운 슈팅 따라하기

 

 

이번 강의는 Sebastian Lague님께서 만든 예제이며,

유튜브를 보시면 자세한 내용을 배우실 수 있습니다.

 

유튜브 사이트 => 유튜브 

 

 

 

유니티 슈팅

 

 

 

1-1) 맵 연결하기

 

 

이번 시간에는 맵 연결을 해보겠습니다.

장애물을 생성할 때 필요없이 이동경로를 막으면 안되겠죠?

그러기 위해서 Flood Fill 알고리즘을 사용할텐데요.

주어진 시작점으로부터 연결된 영역들을 찾아내는 알고리즘입니다.

 

 

 

유니티 슈팅

 

 

 

1-2) Flood Fill 알고리즘

 

 

Flood Fill 알고리즘은 시작점으로부터 연결된 영역들을 찾는 알고리즘이에요.

이동 가능한 지역을 알 수 있겠죠?

그래서 이 알고리즘을 이용해 맵을 연결해 보겠습니다.

 

 

 

유니티 슈팅

 

 

 

1-3) 스크립트 작성 - MapGenerator

 

MapGenerator 스크립트에 Flood Fill 알고리즘을 이용해

장애물을 이어주겠습니다.

 

 

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

public class MapGenerator : MonoBehaviour
{
    public Transform tilePrefab; // 타일 프리팹
    public Transform obstaclePrefab; // 장애물 프리팹
    public Vector2 mapSize; // 맵 사이즈

    [Range(0, 1)]
    public float outlinePercent; // 테두리 영역
    [Range(0, 1)]
    public float obstaclePercent; // 장애물 영역

    List<Coord> allTileCoords; // 모든 좌표 리스트
    Queue<Coord> shuffledTileCoords; // 셔플된 좌표 리스트

    public int seed = 10;
    Coord mapCentre; // 맵 정중앙

    void Start()
    {
        GenerateMap();
    }
    // 맵 생성
    public void GenerateMap()
    {
        allTileCoords = new List<Coord>();
        for (int x = 0; x < mapSize.x; x++)
        {
            for (int y = 0; y < mapSize.y; y++)
            {
                allTileCoords.Add(new Coord(x, y)); // 좌표 리스트 추가
            }
        }
        shuffledTileCoords = new Queue<Coord>(Utility.ShuffleArray(allTileCoords.ToArray(),seed));
        mapCentre = new Coord((int)mapSize.x/2, (int)mapSize.y/2);

        string holderName = "Generated Map";
        if (transform.Find(holderName))
        {
            DestroyImmediate(transform.Find(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.localScale = Vector3.one * (1 - outlinePercent); // 테두리 영역 설정
                newTile.parent = mapHolder; // 부모 설정
            }
        }
        bool[,] obstacleMap = new bool[(int)mapSize.x, (int)mapSize.y];

        int obstacleCount = (int)(mapSize.x * mapSize.y * obstaclePercent);
        int currentObstacleCount = 0;

        for (int i = 0; i < obstacleCount; i++)
        {
            Coord randomCoord = GetRandomCoord();
            obstacleMap[randomCoord.x, randomCoord.y] = true;
            currentObstacleCount++;

            if (randomCoord != mapCentre && MapIsFullyAccessible(obstacleMap, currentObstacleCount))
            {
                Vector3 obstaclePosition = CoordToPosition(randomCoord.x, randomCoord.y);
                Transform newObstacle = Instantiate(obstaclePrefab, obstaclePosition + Vector3.up * 0.5f, Quaternion.identity);
                newObstacle.parent = mapHolder;
            }
            else
            {
                obstacleMap[randomCoord.x, randomCoord.y] = false;
                currentObstacleCount--;
            }
        }
    }
    // 맵 접근 가능 여부(Flood fill 알고리즘)
    bool MapIsFullyAccessible(bool[,] obstacleMap, int currentObstacleCount)
    {
        bool[,] mapFlags = new bool[obstacleMap.GetLength(0), obstacleMap.GetLength(1)];
        Queue<Coord> queue = new Queue<Coord>();
        queue.Enqueue(mapCentre);
        mapFlags[mapCentre.x, mapCentre.y] = true;

        int accessibleTileCount = 1; // 접근 가능 타일

        while (queue.Count > 0){
            Coord tile = queue.Dequeue();

            for (int x = -1; x <= 1; x++){
                for(int y = -1; y <= 1; y++){
                    int neighbourX = tile.x + x;
                    int neighbourY = tile.y + y;
                    if (x == 0 || y == 0){
                        if (neighbourX >= 0 && neighbourX < obstacleMap.GetLength(0) && 
                            neighbourY >= 0 && neighbourY < obstacleMap.GetLength(1)){
                            if (!mapFlags[neighbourX, neighbourY] && !obstacleMap[neighbourX, neighbourY]){
                                mapFlags[neighbourX, neighbourY] = true;
                                queue.Enqueue(new Coord(neighbourX, neighbourY));
                                accessibleTileCount++;
                            }
                        }
                    }
                }
            }
        }
        int targetAccessibleTileCount = (int)(mapSize.x * mapSize.y - currentObstacleCount);
        return targetAccessibleTileCount == accessibleTileCount;
    }
    // Coord를 Vector3로 변환
    Vector3 CoordToPosition(int x, int y)
    {
        return new Vector3(-mapSize.x / 2 + 0.5f + x, 0, -mapSize.y / 2 + 0.5f + y);
    }
    // 랜덤 좌표 반환
    public Coord GetRandomCoord()
    {
        Coord randomCoord = shuffledTileCoords.Dequeue();
        shuffledTileCoords.Enqueue(randomCoord);
        return randomCoord;
    }

    public struct Coord
    {
        public int x;
        public int y;

        public Coord(int _x, int _y)
        {
            x = _x;
            y = _y;
        }
        public static bool operator == (Coord c1, Coord c2)
        {
            return c1.x == c2.x && c1.y == c2.y;
        }
        public static bool operator !=(Coord c1, Coord c2)
        {
            return !(c1 == c2);
        }
    }
}

 

 

public을 이용해 장애물 수를 지정가능하며,

Flood Fill 알고리즘 때 사용할 맵 중앙 데이터도 필요해요.

 

 

 

유니티 슈팅

 

 

맵 생성을 할 때 맵 중앙의 정보와 맵 크기,

그리고 현재 장애물 수도 기록해 주겠습니다^^

 

 

유니티 슈팅

 

 

이제 여기서 맵 접근이 가능한지 판단한 후 장애물을 생성해 주겠습니다.

물론 생성이 불가능하다면 장애물 카운터도 다시 감소해주어야겠죠?!

 

 

유니티 슈팅

 

 

이건 Flood Fill 알고리즘인데요.

형태가 어려울 수 있으니 복습하면서 참고해 주세요ㅎㅎ

 

 

유니티 슈팅

 

 

좌표 비교를 위해서 Coord 구조체에다가 비교해 주는 부분도 추가해 주겠습니다!!

 

 

유니티 슈팅

 

 

에디터 상에서 Obstacle Percent를 증가시켜주면

짜잔 장애물이 생성되는걸 볼 수 있어요.

물론 막히는 부분 없이요.

 

 

유니티 슈팅

 

 

 

2. 마무리

 

 

오늘 강의는 여기까지입니다.

탑다운 슈팅을 따라하면서 장애물을 연결해 보았습니다.

감사합니다.

 

 

 

수업자료: 탑다운 슈팅 따라하기 #10 맵 연결하기

 

 

 

 

 

 

 

댓글

Designed by JB FACTORY