게임 개발
A* 알고리즘(에이스타 알고리즘)의 실제 예시
오리의테크
2022. 1. 21. 16:51
728x90
반응형
저번 글에는 A* 알고리즘의 용어 설명과 코드 예시를 보여 드렸는데요
이번 글에서는 실제 프로젝트에 사용한 예시를 보여드리겠습니다.
프로젝트 코드 예시
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Node
{
public Node(bool _isWall, int _x, int _y) { isWall = _isWall; x = _x; y = _y; }
public bool isWall;
public Node ParentNode;
// G : 시작으로부터 이동했던 거리, H : |가로|+|세로| 장애물 무시하여 목표까지의 거리, F : G + H
public int x, y, G, H;
public int F { get { return G + H; } }
}
public class GameManager : MonoBehaviour
{
public Vector2Int bottomLeft, topRight, startPos, targetPos;
public List<Node> FinalNodeList;
public bool allowDiagonal, dontCrossCorner;
int sizeX, sizeY;
Node[,] NodeArray;
Node StartNode, TargetNode, CurNode;
List<Node> OpenList, ClosedList;
public void PathFinding()
{
// NodeArray의 크기 정해주고, isWall, x, y 대입
sizeX = topRight.x - bottomLeft.x + 1;
sizeY = topRight.y - bottomLeft.y + 1;
NodeArray = new Node[sizeX, sizeY];
for (int i = 0; i < sizeX; i++)
{
for (int j = 0; j < sizeY; j++)
{
bool isWall = false;
foreach (Collider2D col in Physics2D.OverlapCircleAll(new Vector2(i + bottomLeft.x, j + bottomLeft.y), 0.4f))
if (col.gameObject.layer == LayerMask.NameToLayer("Wall")) isWall = true;
NodeArray[i, j] = new Node(isWall, i + bottomLeft.x, j + bottomLeft.y);
}
}
// 시작과 끝 노드, 열린리스트와 닫힌리스트, 마지막리스트 초기화
StartNode = NodeArray[startPos.x - bottomLeft.x, startPos.y - bottomLeft.y];
TargetNode = NodeArray[targetPos.x - bottomLeft.x, targetPos.y - bottomLeft.y];
OpenList = new List<Node>() { StartNode };
ClosedList = new List<Node>();
FinalNodeList = new List<Node>();
while (OpenList.Count > 0)
{
// 열린리스트 중 가장 F가 작고 F가 같다면 H가 작은 걸 현재노드로 하고 열린리스트에서 닫힌리스트로 옮기기
CurNode = OpenList[0];
for (int i = 1; i < OpenList.Count; i++)
if (OpenList[i].F <= CurNode.F && OpenList[i].H < CurNode.H) CurNode = OpenList[i];
OpenList.Remove(CurNode);
ClosedList.Add(CurNode);
// 마지막
if (CurNode == TargetNode)
{
Node TargetCurNode = TargetNode;
while (TargetCurNode != StartNode)
{
FinalNodeList.Add(TargetCurNode);
TargetCurNode = TargetCurNode.ParentNode;
}
FinalNodeList.Add(StartNode);
FinalNodeList.Reverse();
for (int i = 0; i < FinalNodeList.Count; i++) print(i + "번째는 " + FinalNodeList[i].x + ", " + FinalNodeList[i].y);
return;
}
// ↗↖↙↘
if (allowDiagonal)
{
OpenListAdd(CurNode.x + 1, CurNode.y + 1);
OpenListAdd(CurNode.x - 1, CurNode.y + 1);
OpenListAdd(CurNode.x - 1, CurNode.y - 1);
OpenListAdd(CurNode.x + 1, CurNode.y - 1);
}
// ↑ → ↓ ←
OpenListAdd(CurNode.x, CurNode.y + 1);
OpenListAdd(CurNode.x + 1, CurNode.y);
OpenListAdd(CurNode.x, CurNode.y - 1);
OpenListAdd(CurNode.x - 1, CurNode.y);
}
}
void OpenListAdd(int checkX, int checkY)
{
// 상하좌우 범위를 벗어나지 않고, 벽이 아니면서, 닫힌리스트에 없다면
if (checkX >= bottomLeft.x && checkX < topRight.x + 1 && checkY >= bottomLeft.y && checkY < topRight.y + 1 && !NodeArray[checkX - bottomLeft.x, checkY - bottomLeft.y].isWall && !ClosedList.Contains(NodeArray[checkX - bottomLeft.x, checkY - bottomLeft.y]))
{
// 대각선 허용시, 벽 사이로 통과 안됨
if (allowDiagonal) if (NodeArray[CurNode.x - bottomLeft.x, checkY - bottomLeft.y].isWall && NodeArray[checkX - bottomLeft.x, CurNode.y - bottomLeft.y].isWall) return;
// 코너를 가로질러 가지 않을시, 이동 중에 수직수평 장애물이 있으면 안됨
if (dontCrossCorner) if (NodeArray[CurNode.x - bottomLeft.x, checkY - bottomLeft.y].isWall || NodeArray[checkX - bottomLeft.x, CurNode.y - bottomLeft.y].isWall) return;
// 이웃노드에 넣고, 직선은 10, 대각선은 14비용
Node NeighborNode = NodeArray[checkX - bottomLeft.x, checkY - bottomLeft.y];
int MoveCost = CurNode.G + (CurNode.x - checkX == 0 || CurNode.y - checkY == 0 ? 10 : 14);
// 이동비용이 이웃노드G보다 작거나 또는 열린리스트에 이웃노드가 없다면 G, H, ParentNode를 설정 후 열린리스트에 추가
if (MoveCost < NeighborNode.G || !OpenList.Contains(NeighborNode))
{
NeighborNode.G = MoveCost;
NeighborNode.H = (Mathf.Abs(NeighborNode.x - TargetNode.x) + Mathf.Abs(NeighborNode.y - TargetNode.y)) * 10;
NeighborNode.ParentNode = CurNode;
OpenList.Add(NeighborNode);
}
}
}
void OnDrawGizmos()
{
if(FinalNodeList.Count != 0) for (int i = 0; i < FinalNodeList.Count - 1; i++)
Gizmos.DrawLine(new Vector2(FinalNodeList[i].x, FinalNodeList[i].y), new Vector2(FinalNodeList[i + 1].x, FinalNodeList[i + 1].y));
}
}
기본 화면 예시입니다
길 찾기 버튼을 누르게 되면 StartPos 지점과 TargetPos 지검 간에 최단거리를 찾아주게 되는 겁니다.
GameManager 오브젝트에 GameManager 스크립트를 컴포넌트를 한 이미지입니다
Bottom, Top 은 타일의 수를 나타내주는 변수이고,
StartPos는 시작 지점, TargetPos는 도착지점을 나타내는 변수입니다.
그리고 AllowDiagonal과 DontCrossCorner bool 값은 대각선을 허용 할 것 인지, 가로질러 가는 것을 허용할 것 인지를 설정해 주는 값입니다.
PathFindingBtn은 길찾기 버튼입니다. OnClick()함수에 GameManager스크립트를 삽입해 버튼 클릭시 PathFindingBtn함수가 실행하도록 설정을 해줍니다.
첫 번째로 실행을 한 것은 대각선과 가로지르는 것을 false로 설정을 했을때 콘솔창 화면입니다.
실행화면을 보면 StartPos지점에서 TargetPos지점까지에 최단 거리가 나오게 됩니다.
두 번째로 실행을 한 것은 대각선과 가로지르는 것을 true로 설정을 했을때 콘솔창 화면입니다.
실행화면을 보면 StartPos지점에서 TargetPos지점까지에 최단 거리가 나오게 됩니다.
728x90
반응형