300x250

Player.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class Player : MonoBehaviour
{
    public float moveSpeed;
    private Coroutine moveRoutine;

    public UnityAction<Vector2Int, Vector2Int> onDecideTargetTile;


    private void Update()
    {
        // 마우스 왼쪽버튼 클릭시
        if (Input.GetMouseButtonDown(0))
        {
            // 마우스 클릭시 좌표를 인게임 좌표로 변환하여 mousePos 변수에 저장
            Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            // z값은 사용을 안하므로 x, y 값만 저장후 Move
            int targetPosX = (int)Math.Truncate(mousePos.x);
            int targetPosY = (int)Math.Truncate(mousePos.y);

            int currentPosX = (int)Math.Round(this.transform.position.x);
            int currentPosY = (int)Math.Round(this.transform.position.y);
            Vector2Int curPos = new Vector2Int(currentPosX, currentPosY);
            Vector2Int targetPos = new Vector2Int(targetPosX, targetPosY);
            Debug.LogFormat("curPos : {0}, targetPos : {1}", curPos, targetPos);
            this.onDecideTargetTile(curPos, targetPos);
        }
    }

    // 플레이어 이동 스크립트
    // 매개변수 pathList를 입력받아 경로 단위로 움직입니다.
    public void Move(List<Vector3> pathList)
    {
        if (this.moveRoutine != null)
            this.StopCoroutine(moveRoutine);
        moveRoutine = this.StartCoroutine(this.MoveRoutine(pathList));
    }

    private IEnumerator MoveRoutine(List<Vector3> pathList)
    {
        int pathCount = pathList.Count;

        for (int index = 1; index < pathList.Count; index++)
        {
            if (pathCount < 1)
                break;

            while (true)
            {
                // dir(방향) = 타겟방향 - 플레이어 현재 위치
                var dir = pathList[index] - this.transform.position;
                this.transform.Translate(dir.normalized * this.moveSpeed * Time.deltaTime);

                // 타겟위치와 현재위치의 거리차이가 0.1이하가 될시 while문 빠져나옵니다
                var distance = dir.magnitude;
                if (distance <= 0.1f)
                    break;
                yield return null;
            }
        }
        // move루틴 끝났으므로 null로 초기화
        this.moveRoutine = null;
    }
}

Main.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;   
public class Main : MonoBehaviour
{
    private MapManager mapManager;
    private Player player;

    void Start()
    {
        this.player = GameObject.FindObjectOfType<Player>();
        this.mapManager = GameObject.FindObjectOfType<MapManager>();
        this.player.onDecideTargetTile = (startPos, targetPos) =>
        {
            this.mapManager.PathFinding(startPos, targetPos);
            this.player.Move(this.mapManager.PathList);
        };

    }
}

 

MapManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MapManager : MonoBehaviour
{
    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; } }
    }

    private GridLayout gridMap;


    int sizeX, sizeY;
    Node[,] NodeArray;
    Node StartNode, TargetNode, CurNode;
    List<Node> OpenList, ClosedList;
    public Vector2Int bottomLeft, topRight;
    private List<Node> FinalNodeList;

    public bool allowDiagonal, dontCrossCorner;
    public List<Vector3> PathList
    {
        get; private set;
    }

    private void Start()
    {
        this.gridMap = GameObject.Find("GridMap").GetComponent<GridLayout>();
        this.PathList = new List<Vector3>();
    }


    public void PathFinding(Vector2Int startPos, Vector2Int targetPos)
    {
        this.PathList.Clear();
        // 캡크기 설정
        // NodeArray의 크기 정해주고, isWall, x, y 대입
        sizeX = topRight.x - bottomLeft.x + 1;
        sizeY = topRight.y - bottomLeft.y + 1;
        NodeArray = new Node[sizeX, sizeY];


        // 장애물 감지
        // 맵에 Layer가 Wall인 태그를 가진 오브젝트가 있는지 검사하고 있을시 지나갈수없는 통로로 설정한다.
        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 + 0.5f, j + bottomLeft.y + 0.5f), 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);
                    Vector3 path = new Vector3(FinalNodeList[i].x, FinalNodeList[i].y, 0);
                    this.PathList.Add(path);
                }
                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 == null)
            return;
        if (FinalNodeList.Count != 0)
        {
            for (int i = 0; i < FinalNodeList.Count - 1; i++)
            {
                Gizmos.DrawLine(new Vector2(FinalNodeList[i].x + 0.5f, FinalNodeList[i].y + 0.5f), new Vector2(FinalNodeList[i + 1].x + 0.5f, FinalNodeList[i + 1].y + 0.5f));
            }
        }
    }
}

 

 

 

 

출처 

고라니 유니티2D: 2D 타일맵을 위한 A* 길찾기 알고리즘 (goraniunity2d.blogspot.com)

300x250