https://ksw0723.tistory.com/194

 

A* 알고리즘 구현해보기 (2) - 출발 지점에서 목표 지점까지 경로 구하기

https://ksw0723.tistory.com/193 A* 알고리즘 구현해보기 (1) - 이동 불가 노드와 이동 가능 노드 구별하기 오늘은 A* 알고리즘을 구현해보려고 한다. 참고 블로그 및 사이트는 https://m.blog.naver.com/pkh879/221735

ksw0723.tistory.com

 

 

<최종 실행 이미지>

 

 

 

지난 글에 이어서 

 

이번에는 출발 지점에서 목표 지점까지 구한 경로를 통해 

 

오브젝트를 이동시켜 보려고 한다.

 

그리고 눈으로 확인 할 수 있도록

 

 

하얀색 : 이동할 수 있는 위치

빨간색 : 이동할 수 없는 위치

노란색 : 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* 알고리즘을 통해 이동하는 모습

 

+ Recent posts