https://ksw0723.tistory.com/194
지난 글에 이어서
이번에는 출발 지점에서 목표 지점까지 구한 경로를 통해
오브젝트를 이동시켜 보려고 한다.
그리고 눈으로 확인 할 수 있도록
하얀색 : 이동할 수 있는 위치
빨간색 : 이동할 수 없는 위치
노란색 : Player의 위치를 나타내는 위치
검은색 : A* 알고리즘으로 계산된 경로
로 나타내볼려고 한다.
일단 지난번의 코드를 다음과 같이 수정한다.
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public float speed = 5f;
private int pathIndex = 0;
private List<Node> currentPath;
void Update()
{
if (currentPath != null && currentPath.Count > 0)
{
MoveAlongPath();
}
}
void MoveAlongPath()
{
if (pathIndex < currentPath.Count)
{
Vector2 targetPosition = currentPath[pathIndex].worldPos;
if (MoveTowardsTarget(targetPosition))
{
pathIndex++;
}
}
}
bool MoveTowardsTarget(Vector2 target)
{
transform.position = Vector2.MoveTowards(transform.position, target, speed * Time.deltaTime);
return Vector2.Distance(transform.position, target) < 0.1f;
}
public void SetPath(List<Node> path)
{
this.currentPath = path;
this.pathIndex = 0;
}
}
Player.cs
using System.Collections.Generic;
using UnityEngine;
public class Grid : MonoBehaviour
{
public LayerMask unwalkableMask;
public Vector2 gridWorldSize;
public float nodeRadius;
public List<Node> path;
Node[,] grid;
float nodeDiameter;
int gridSizeX, gridSizeY;
public GameObject player;
public Transform playerPosition; // Player의 Transform을 참조하기 위한 필드
void Start()
{
nodeDiameter = nodeRadius * 2;
gridSizeX = Mathf.RoundToInt(gridWorldSize.x / nodeDiameter);
gridSizeY = Mathf.RoundToInt(gridWorldSize.y / nodeDiameter);
CreateGrid();
}
void CreateGrid()
{
grid = new Node[gridSizeX, gridSizeY];
Vector2 worldBottomLeft = (Vector2)transform.position - Vector2.right * gridWorldSize.x / 2 - Vector2.up * gridWorldSize.y / 2;
for (int x = 0; x < gridSizeX; x++)
{
for (int y = 0; y < gridSizeY; y++)
{
Vector2 worldPoint = worldBottomLeft + Vector2.right * (x * nodeDiameter + nodeRadius) + Vector2.up * (y * nodeDiameter + nodeRadius);
bool walkable = !(Physics2D.OverlapCircle(worldPoint, nodeRadius, unwalkableMask));
grid[x, y] = new Node(walkable, worldPoint, x, y);
}
}
}
public List<Node> GetNeighbours(Node node)
{
List<Node> neighbours = new List<Node>();
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
if (x == 0 && y == 0)
continue;
int checkX = node.gridX + x;
int checkY = node.gridY + y;
if (checkX >= 0 && checkX < gridSizeX && checkY >= 0 && checkY < gridSizeY)
{
neighbours.Add(grid[checkX, checkY]);
}
}
}
return neighbours;
}
public Node GetNodeFromWorldPoint(Vector2 worldPosition)
{
float percentX = (worldPosition.x + gridWorldSize.x / 2) / gridWorldSize.x;
float percentY = (worldPosition.y + gridWorldSize.y / 2) / gridWorldSize.y;
percentX = Mathf.Clamp01(percentX);
percentY = Mathf.Clamp01(percentY);
int x = Mathf.RoundToInt((gridSizeX - 1) * percentX);
int y = Mathf.RoundToInt((gridSizeY - 1) * percentY);
return grid[x, y];
}
void OnDrawGizmos()
{
Gizmos.DrawWireCube(transform.position, new Vector3(gridWorldSize.x, gridWorldSize.y, 1));
if (grid != null)
{
Node playerNode = GetNodeFromWorldPoint(playerPosition.position);
foreach (Node n in grid)
{
Gizmos.color = (n.isWalkAble ? Color.white : Color.red);
if (path != null && path.Contains(n))
Gizmos.color = Color.black;
if (playerNode == n)
Gizmos.color = Color.yellow;
Gizmos.DrawCube(n.worldPos, Vector3.one * (nodeDiameter - .1f));
}
}
}
}
grid.cs
using UnityEngine;
using System.Collections.Generic;
public class Pathfinding : MonoBehaviour
{
Grid grid;
void Awake()
{
grid = GetComponent<Grid>();
}
void Update()
{
if (Input.GetMouseButtonDown(1)) // 마우스 우클릭 감지
{
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
FindPath(grid.playerPosition.position, mousePosition);
}
}
void FindPath(Vector2 startPos, Vector2 targetPos)
{
Node startNode = grid.GetNodeFromWorldPoint(startPos);
Node targetNode = grid.GetNodeFromWorldPoint(targetPos);
List<Node> openList = new List<Node>();
HashSet<Node> closedSet = new HashSet<Node>();
openList.Add(startNode);
while (openList.Count > 0)
{
Node currentNode = openList[0];
for (int i = 1; i < openList.Count; i++)
{
if (openList[i].fCost < currentNode.fCost || openList[i].fCost == currentNode.fCost && openList[i].hCost < currentNode.hCost)
{
currentNode = openList[i];
}
}
openList.Remove(currentNode);
closedSet.Add(currentNode);
if (currentNode == targetNode)
{
RetracePath(startNode, targetNode);
return;
}
foreach (Node neighbour in grid.GetNeighbours(currentNode))
{
if (!neighbour.isWalkAble || closedSet.Contains(neighbour))
{
continue;
}
int newCostToNeighbour = currentNode.gCost + GetDistanceCost(currentNode, neighbour);
if (newCostToNeighbour < neighbour.gCost || !openList.Contains(neighbour))
{
neighbour.gCost = newCostToNeighbour;
neighbour.hCost = GetDistanceCost(neighbour, targetNode);
neighbour.parentNode = currentNode;
if (!openList.Contains(neighbour))
openList.Add(neighbour);
}
}
}
}
void RetracePath(Node startNode, Node endNode)
{
List<Node> path = new List<Node>();
Node currentNode = endNode;
while (currentNode != startNode)
{
path.Add(currentNode);
currentNode = currentNode.parentNode;
}
path.Reverse();
grid.path = path;
grid.player.GetComponent<Player>().SetPath(path);
}
int GetDistanceCost(Node nodeA, Node nodeB)
{
int distX = Mathf.Abs(nodeA.gridX - nodeB.gridX);
int distY = Mathf.Abs(nodeA.gridY - nodeB.gridY);
if (distX > distY)
return 14 * distY + 10 * (distX - distY);
return 14 * distX + 10 * (distY - distX);
}
}
Pathfinding.cs
using UnityEngine;
public class Node
{
public bool isWalkAble;
public Vector2 worldPos; // 2D
public int gridX;
public int gridY;
public int gCost;
public int hCost;
public Node parentNode;
public Node(bool walkable, Vector2 worldPosition, int nGridX, int nGridY)
{
isWalkAble = walkable;
worldPos = worldPosition;
gridX = nGridX;
gridY = nGridY;
}
public int fCost
{
get { return gCost + hCost; }
}
}
Node.cs
+++
Player 스크립트를 움직일 오브젝트에 추가한다.
그리고 게임 씬을 시작해 확인 해보자.
마우스 클릭 위치로 A* 알고리즘을 통해 이동하는 모습
'산대특 > 게임 플랫폼 응용 프로그래밍' 카테고리의 다른 글
유니티 공식 오브젝트 풀링 API로 총알(Bullet) 프리팹 관리하기 (0) | 2024.03.04 |
---|---|
Bullet 발사 시에 오브젝트 풀링으로 Bullet 관리하기 (0) | 2024.03.04 |
A* 알고리즘 구현해보기 (2) - 출발 지점에서 목표 지점까지 경로 구하기 (0) | 2024.03.03 |
A* 알고리즘 구현해보기 (1) - 이동 불가 노드와 이동 가능 노드 구별하기 (0) | 2024.03.03 |
Larva 클릭 및 드래그로 TargetPoint 지정하기 (0) | 2024.03.01 |