공부/유니티 디펜스 게임 만들기
A* 알고리즘 with Unity 01
원클릭쓰리버그
2022. 1. 22. 18:51
728x90
using UnityEngine;
public class Node
{
public bool IsWalkable;
public Vector3 worldPosition;
public int gridX, gridY;
// 노드의 현재까지 움직인 비용.
public int gCost;
// 노드가 도착점까지 예상되는 비용.
public int hCost;
//비용의 총합
public int Fcost { get => gCost + hCost; }
public Node(bool walkable, Vector3 pos, int x, int y)
{
this.IsWalkable = walkable;
worldPosition = pos;
gridX = x;
gridY = y;
}
}
노드에 비용을 넣는다.
player가 출발선에서 소모한 비용, 앞으로 소모할 비용, 두 비용의 총합.
using System.Collections.Generic;
using UnityEngine;
public class Grid : MonoBehaviour
{
GameObject player;
public LayerMask unwalkableMask;
Vector2 gridSize;
Node[,] grid;
float nodeRadius; //노드 반지름
float nodeDiameter; //노드 지름
int gridXCount, gridYCount; //Grid X축 Node 갯수, Grid Y축 Node 갯수
private void Awake()
{
player = GameObject.Find("Player");
nodeRadius = 0.5f;
nodeDiameter = nodeRadius * 2f;
gridSize = new Vector2(30, 30);
gridXCount = Mathf.RoundToInt(gridSize.x / nodeDiameter);
gridYCount = Mathf.RoundToInt(gridSize.y / nodeDiameter);
CreateGrid();
}
public List<Node> GetNearByNode(Node currentNode)
{
List<Node> NearByNodes = new List<Node>();
// 해당되는 노드의 붙어있는 노드를 체크하여 List에 담는다.
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
// 본인 자신이면 넘어간다.
if (x == 0 && y == 0)
continue;
int check_X = currentNode.gridX + x;
int check_Y = currentNode.gridY + y;
// 범위내 주변노드가 맞다면 List에 저장한다.
if ((0 <= check_X && check_X < gridXCount) &&
(0 <= check_Y && check_Y < gridYCount))
NearByNodes.Add(grid[check_X, check_Y]);
}
}
return NearByNodes;
}
private void CreateGrid()
{
grid = new Node[gridXCount, gridYCount]; // [30,30]
Vector3 worldBottomLeft = transform.position - new Vector3((gridSize.x * 0.5f), 0, (gridSize.y * 0.5f));
for (int x = 0; x < gridXCount; x++)
{
for (int y = 0; y < gridYCount; y++)
{
Vector3 worldPosition = worldBottomLeft + new Vector3((x * nodeDiameter + nodeRadius), 0, (y * nodeDiameter + nodeRadius));
bool walkable = !(Physics.CheckSphere(worldPosition, nodeRadius, unwalkableMask));
grid[x, y] = new Node(walkable, worldPosition, x, y);
}
}
}
public Node GetNodeFromWorldPosition(Vector3 WP)
{
float percen_X = (WP.x + gridSize.x * 0.5f) / gridSize.x;
float percen_Y = (WP.z + gridSize.y * 0.5f) / gridSize.y;
percen_X = Mathf.Clamp01(percen_X);
percen_Y = Mathf.Clamp01(percen_Y);
int x = Mathf.RoundToInt((gridXCount - 1) * percen_X);
int y = Mathf.RoundToInt((gridYCount - 1) * percen_Y);
return grid[x, y];
}
private void OnDrawGizmos()
{
Gizmos.DrawWireCube(transform.position, new Vector3(gridSize.x, 1f, gridSize.y));
if (grid != null)
{
foreach (var node in grid)
{
if (node == GetNodeFromWorldPosition(player.transform.position))
Gizmos.color = Color.green;
else
Gizmos.color = node.IsWalkable ? Color.white : Color.red;
Gizmos.DrawCube(node.worldPosition, Vector3.one * nodeDiameter);
}
}
}
}
GetNearByNode() 함수를 통해 붙어 있는 노드를 묶어 반환해준다.
using System.Collections.Generic;
using UnityEngine;
public class PathManager : MonoBehaviour
{
public Transform player, target;
[SerializeField] private Grid grid;
private void Awake()
{
grid = GetComponent<Grid>();
}
private void Start()
{
FindPath(player.position, target.position);
}
private void FindPath(Vector3 seeker, Vector3 target)
{
Node startNode = grid.GetNodeFromWorldPosition(seeker); // 출발 Node[x,y]
Node targetNode = grid.GetNodeFromWorldPosition(target); // 도착 Node[x,y]
// openSet은 비용적으로 효율적인 Node를 모아 놓는 List이다.
List<Node> openSet = new List<Node>();
// closeSet은 여지껏 왔던 Node 중 왔던 길,혹은 비용적으로 비효율적인 Node를 모아 놓은 HashSet이다.
HashSet<Node> closeSet = new HashSet<Node>();
openSet.Add(startNode); // 효율적인 Node면 저장해 놓는다.
while (openSet.Count > 0)
{
//openSet List에서 가장 적은 비용의 Node를 찾는다.
Node currentNode = openSet[0];
for (int i = 0; i < openSet.Count; i++)
{
// 임시로 담겨있는 Node currentNode에서 비용이 작은 Node를 뽑아 그것으로 바꿔준다.
// 만약 Fcost 비용이 같은 경우 앞으로 갈 비용 중 가장 적은 Node를 우선순위로 둔다.
if (openSet[i].Fcost < currentNode.Fcost ||
(openSet[i].Fcost == currentNode.Fcost && openSet[i].hCost < currentNode.hCost))
currentNode = openSet[i];
}
// 뽑은 Node는 openSet에 없애주고, CloseSet에 담겨준다.
openSet.Remove(currentNode);
closeSet.Add(currentNode);
//뽑은 노드와 타켓 노드가 같으면 도착했으므로 탐색을 그만둔다.
if (currentNode == targetNode)
break;
//뽑은 노드의 주변 노드를 List로 받아 확인한다..
foreach(Node nearByNode in grid.GetNearByNode(currentNode))
{
if (closeSet.Contains(nearByNode) || !nearByNode.IsWalkable)
continue;
}
}
}
}