게임 개발

길찾기 알고리즘 A* Algorithm 에이스타 알고리즘

오리의테크 2022. 1. 21. 15:10
728x90
반응형

길찾기에서 사용되는 알고리즘 중 가장 흔하게 사용하는 것이 에이스타 알고리즘입니다.

 

용어 설명

- openList = 갈 수 있는 길
- closeList = 이미 지나간 길
- current = 현재 위치
- NeighborNode = 탐색한 길
- 이동비용 = 도착지점까지의 거리
- 가중치 = 일반적으로 직선 : 10, 대각선 : 14

1. CreateNode : 맵을 만든다.

2. SetTargetLocation : 시작지점과 도착지점을 구한다.

3. PathFinding : 길 찾기 알고리즘을 한다.

- 시작지점을 openList에 담는다.

- openList 중에 이동 비용이 낮은 길을 찾아 current에 담는다.

- current를 openList에서 지우고 closeList에 담는다.

- current를 기준으로 4개 방향에 대해 탐색한다.

  (일반적으로는 8개 방향인데 현재 코드는 사선으로 움직이지 못하는 코드라서 4개의 방향만 탐색한다.)

  (4개의 방향 중 막다른길, openList, closeList에 포함하는 경우 탐색하지 않는다.)

- 이동 비용을 계산한다.

  (이동 비용 : current의 G + 가중치)

- current의 이동비용이 NeighborNode의 이동 비용보다 작으면 NeighborNode의 이전 길은 current로 설정해준다.

- NeighborNode는 openList에 담는다.

4. 위와 같은 방법을 반복하여 목표지점을 찾는다.

 

코드 예시

public class AStarNode
{ public bool isWall; public AStarNode ParentNode;

// G : 시작노드부터 이동하려는 위치 거리
// H : 시작노드부터 목표지점까지 거리(타일 수)
// F : G + H
public int x, y, G, H;

public int F
{
get
{
return G + H;
}
}

public AStarNode(bool isWall, int x, int y)
{
this.isWall = isWall;
this.x = x; this.y = y;
}
}

using System.Collections.Generic;
using UnityEngine;
public class AStarLogic : MonoBehaviour
{ private AStarNode[,] nodes;
private AStarNode startNode;
private AStarNode endNode;
private AStarNode currentNode;
private List
<AStarNode> openList = new List<AStarNode>();
private List
<AStarNode> closeList = new List<AStarNode>();
public Vector2Int TestStartLocation;
// 시작 위치
public Vector2Int TestEndLocation;
// 도착 위치
public int TestXSize;
// 맵 X크기
public int TestYSize;
// 맵 Y크기
[ContextMenu("테스트")]
public void Test()
{ CreateNode(TestXSize, TestYSize);
SetTargetLocation(TestStartLocation, TestEndLocation);
PathFinding(); }
/// 
<summary> 
/// x,y 크기에 맞는 노드 배열을 만든다.
/// 
</summary> 
public void CreateNode(int xSize, int ySize)
{ nodes = new AStarNode[xSize, ySize];
for (int x = 0; x 
< xSize; x++)
{ for (int y = 0; y < ySize; y++)
{ bool isWall = false; 
// 벽 여부 (막힌 길) 
nodes[x, y] = new AStarNode(isWall, x, y);
}
}
}
/// <summary>
 
/// 시작 지점과 목표 지점
/// 
</summary> 
public void SetTargetLocation(Vector2Int startLocation, Vector2Int endLocation)
{ startNode = nodes[startLocation.x, startLocation.y];
endNode = nodes[endLocation.x, endLocation.y];
}
/// 
<summary> 
/// 길 찾기 알고리즘
/// 
</summary> 
public void PathFinding()
{ openList.Clear();
// 갈 수 있는 길 목록
closeList.Clear();
// 한번 들른 곳 목록
// 맨 처음에는 시작 지점부터
openList.Add(startNode);
while (openList.Count > 0)
{ // 갈 수 있는 길 중에 가중치가 낮은 길을 찾는다.
currentNode = openList[0];
for (int i = 1; i 
< openList.Count; ++i) { if (openList[i].F <= currentNode.F && openList[i].H < currentNode.H)
{ currentNode = openList[i]; }
}
// 찾은 길은 갈 수 있는 곳 목록에서 지운다. 
// 한번 들른 곳 목록에 넣어준다. 
// 이로써 한번 갔던 길을 중복으로 검색하는 일은 없어진다. openList.Remove(currentNode); 
closeList.Add(currentNode);
// 목적지에 도착했는지 체크한다. 
if (currentNode == endNode) 
{
//for (int i = 0; i < closeList.Count; i++)
// Debug.Log(i + "번째는 " + closeList[i].x + ", " + closeList[i].y); 
return; }
// 갈 수 있는 길을 찾아서 목록에 넣어준다. 
// 순서는 일반적으로 위 ->
 오른쪽 -> 아래 -> 왼쪽 순이다.
OpenListAdd(currentNode.x, currentNode.y + 1);
OpenListAdd(currentNode.x + 1, currentNode.y);
OpenListAdd(currentNode.x, currentNode.y - 1);
OpenListAdd(currentNode.x - 1, currentNode.y); } }
void OpenListAdd(int checkX, int checkY)
{
// 상하좌우 범위를 벗어났다면 빠져나온다.
if (checkX 
< 0 || checkX >= nodes.GetLength(0) || checkY < 0 || checkY >= nodes.GetLength(1))
return;
// 한번 들른 곳이면 빠져나온다.
if (closeList.Contains(nodes[checkX, checkY]))
return;
// 벽인 경우 빠져나온다.
if (nodes[checkX, checkY].isWall)
return;
//
이동 비용을 구한다.
// 직선 : 현재 위치의 G + 10 (현재 위치 - 확인할 위치를 계산하여 x,y중 하나라도 0인 경우)
// 대각선 : 현재 위치의 G + 14 (현재 위치 - 확인할 위치를 계산하여 x,y 둘 다 0이 아닌 경우)
// 직선의 비용은 10
// 대각선의 비용은 14
AStarNode NeighborNode = nodes[checkX, checkY];
int MoveCost = currentNode.G + (currentNode.x - checkX == 0 || currentNode.y - checkY == 0 ? 10 : 14);
// 이동 비용이 확인할 위치의 G보다 작거나
// 갈 수 있는 길 목록에서 현재 확인하려는 위치가 없다면
if (MoveCost 
< NeighborNode.G || !openList.Contains(NeighborNode))
{
// 이동비용, 거리, 현재 위치를 담는다. 
NeighborNode.G = MoveCost; NeighborNode.H = (Mathf.Abs(NeighborNode.x - endNode.x) + Mathf.Abs(NeighborNode.y - endNode.y)) * 10; 
NeighborNode.ParentNode = currentNode; 
// 위와 같은 방법을 통해 parentNode가 시작점과 이어지게 된다. 
// 예)
// 시작점 : x:0,y:0 
// 도착점 : x:2,y:0 
// 도착점(2,0) ->
 이전(1,0) -> 시작점(0,0)으로 연결 되어 있다.
openList.Add(NeighborNode);
}
}
}

728x90
반응형