[유니티]탑다운 슈팅 따라하기 #10 맵 연결하기
- 게임 개발 - Unity3d
- 2020. 4. 4. 22:15
안녕하세요 유랑입니다.
실력향상을 위해서 오늘은 유튜브를 따라하면서 공부하겠습니다.
궁금하신점 있으시다면 댓글로 남겨주세요^^
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 맵 연결하기