맵 자동 생성(6)
이전까지 방을 생성해서 다 펼쳐놓고 그 mst를 이용해 방 들을 연결하였다.그리고 그 간선들을 이용해 문과 벽을 생성하고 통로까지 만들었다. https://tpree.tistory.com/235 맵 자동 생성(5)이번 글에서
tpree.tistory.com
여기까지만 봐도 맵 자동생성은 완성된다.
좀 더 게임답게 만들기 위한 과정이다. 부수적인 기능들을 붙인다는 것이다.
지난번까지 랜덤으로 몬스터 스폰 및 장애물 설치를 진행했다.
이번 글에서는 각 생성물들의 상호작용을 다뤄본다.
1. 몬스터 추적기능 2.룸 배틀 시퀀스 적용
이 두가지를 구현하겠다.
먼저 몬스터 추적기능은 바로 navMesh를 떠올렸다. 그런데 3D에서는 지원하지만 2D는 지원하지 않았다. mesh renderer 컴포넌트가 있어야 bake 대상으로 선택이 되었다.
그래서 찾다보니 에셋스토어에 2D 길찾기를 제공하는 에셋을 발견했다. A*로 만들었고 api를 사용만 하면 되었다.
// Test_Monster.cs
// ...
public void StartBattle(bool[,] m)
{
map = m;
StartCoroutine(FindPathRoutine());
}
async void pathFinding()
{
Vector3 pos = GameManager.Instance.pc.gameObject.transform.localPosition;
path = await AStarPathfinding.GeneratePath(
Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.y), Mathf.RoundToInt(transform.localPosition.x), Mathf.RoundToInt(transform.localPosition.y), map);
if (path == null || path.Length == 0)
{
Debug.LogWarning("Path is empty or null!");
pathFinding();
return;
}
co = StartCoroutine(FollowPath());
}
IEnumerator FindPathRoutine()
{
while(true)
{
pathFinding();
yield return new WaitForSeconds(0.3f);
if(co!=null)
StopCoroutine(co);
}
}
IEnumerator FollowPath()
{
foreach (var point in path)
{
// 경로의 (x, y)를 월드 좌표로 변환
Vector2 targetPosition = new Vector2(point.Item1, point.Item2);
targetPosition += new Vector2(roomController.transform.position.x, roomController.transform.position.y);
// 현재 위치에서 목표 위치까지 이동
while (Vector2.Distance(transform.position, targetPosition) > 0.01f)
{
transform.position = Vector2.MoveTowards(
transform.position,
targetPosition,
moveSpeed * Time.deltaTime * 0.4f
);
yield return null; // 다음 프레임까지 대기
}
// 목표 위치에 정확히 도달하도록 설정
transform.position = targetPosition;
transform.localPosition = targetPosition - new Vector2(roomController.transform.position.x, roomController.transform.position.y);
yield return null;
}
}
몬스터 추적기능에 관한 모든 코드이다.
이 중 A* api를 사용한 코드부터 살펴보자. 가장 핵심이다.
bool[,] map;
(int, int)[] path;
path = await AStarPathfinding.GeneratePath(
Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.y),
Mathf.RoundToInt(transform.localPosition.x), Mathf.RoundToInt(transform.localPosition.y),
map);
GeneratePath함수를 실행하면 pair형태 배열을 리턴해준다. 경로를 뜻하는데 (x,y) 이다.
매개변수로 (startX,startY, destX,destY, bool[,] map) 을 받는다.
현재 몬스터 위치에서 플레이어 위치까지 경로를 구하면 된다. 그리고 맵데이터는 이미 TileType 2차원배열로 관리하고 있었기 때문에 bool로 바꿔서 넘겨주면 된다.
이제 하나씩 살펴보자.
우선 간단하게 정리하면 외부에서 StartBattle함수를 호출한다. 그러면 몬스터가 추적하기 시작한다.
이 때, Update 문에서 경로를 구하면 필요이상으로 A*알고리즘이 호출되기 때문에 0.3초에 한번씩 호출해서 경로를 재설정하는 것으로 바꾸었다.
이를 구현하기 위해서 코루틴 함수 2개를 사용하였는데, FindPath는 0.3초에 한번씩 경로를 다시 계산하여 경로를 따라가는 코루틴 함수를 실행해준다.
FindPath 코루틴함수가 추적을 위한 코루틴 함수이다.
async void pathFinding()
{
Vector3 pos = GameManager.Instance.pc.gameObject.transform.localPosition;
path = await AStarPathfinding.GeneratePath(
Mathf.RoundToInt(pos.x), Mathf.RoundToInt(pos.y), Mathf.RoundToInt(transform.localPosition.x), Mathf.RoundToInt(transform.localPosition.y), map);
if (path == null || path.Length == 0)
{
Debug.LogWarning("Path is empty or null!");
pathFinding();
return;
}
co = StartCoroutine(FollowPath());
}
먼저 pathFinding 함수를 살펴보자.
api를 이용해 경로를 구한다. 그리고 경로를 따라가는 코루틴 함수인 FollowPath를 실행하고 따로 저장해둔다.
이는 0.3초에 한번씩 경로를 세팅해야하기 때문에 기존 경로를 따라가는 코루틴을 멈추고 다시 시작해야하기 때문이다.
IEnumerator FindPathRoutine()
{
while(true)
{
pathFinding();
yield return new WaitForSeconds(0.3f);
if(co!=null)
StopCoroutine(co);
}
}
이 코루틴 함수가 추적알고리즘 전체를 관리해주는 함수이다.
경로를 찾고 경로를 따라가는 코루틴을 시작한 후, 0.3초를 기다리고 기존 코루틴을 멈춘다.
이를 반복한다.
마지막으로 경로를 어떻게 따라가는지 FollowPath 코루틴함수를 살펴보겠다.
IEnumerator FollowPath()
{
foreach (var point in path)
{
// 경로의 (x, y)를 월드 좌표로 변환
Vector2 targetPosition = new Vector2(point.Item1, point.Item2);
targetPosition +=
new Vector2(roomController.transform.position.x, roomController.transform.position.y);
// 현재 위치에서 목표 위치까지 이동
while (Vector2.Distance(transform.position, targetPosition) > 0.01f)
{
transform.position = Vector2.MoveTowards(
transform.position,
targetPosition,
moveSpeed * Time.deltaTime * 0.4f
);
yield return null; // 다음 프레임까지 대기
}
// 목표 위치에 정확히 도달하도록 설정
transform.position = targetPosition;
transform.localPosition = targetPosition -
new Vector2(roomController.transform.position.x, roomController.transform.position.y);
yield return null;
}
}
경로를 따라가는 함수이다. for문으로 각 경로 즉, 이동할 바로 다음칸에 대해서 MoveTowards 함수로 이동시킨다.
여기까지하면 아마 실행이 안된다. 왜냐하면 A*가 제공하는 경로좌표들은 Room을 기준으로 한 상대좌표들이기 때문이다.
플레이어를 해당 룸에 자식으로 할당한다. 그리고 상대 좌표를 계산하여 움직이게 하였다.
생각보다 글이 길어져 룸 배틀 시퀀스는 다음 글에서 다루겠다.
'2d 자동 맵생성' 카테고리의 다른 글
| 맵 자동 생성(9) (0) | 2025.05.13 |
|---|---|
| 맵 자동 생성(8) (0) | 2025.05.13 |
| 맵 자동 생성(6) (0) | 2025.05.12 |
| 맵 자동 생성(5) (2) | 2025.05.11 |
| 맵 자동 생성(4) (0) | 2025.05.11 |