ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Astar알고리즘을 이용한 몬스터 이동
    알고리즘/문제해결 2022. 9. 15. 16:46

    http://www.gisdeveloper.co.kr/?p=3897

     

    최단 경로 탐색 – A* 알고리즘 – GIS Developer

    최단 경로 탐색 알고리즘 중 A*(A Star, 에이 스타) 알고리즘에 대해 실제 예시를 통해 풀어가면서 설명하겠습니다. A* 알고리즘은 시작 노드만을 지정해 다른 모든 노드에 대한 최단 경로를 파악하

    www.gisdeveloper.co.kr

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Events;
    
    public class MapData
    {
        public Vector2Int bottomLeft, topRight;
        public int sizeX, sizeY;
        public Node[,] NodeArray;
        public Node StartNode, TargetNode;
    }
    public class MonsterMoveTest : MonoBehaviour
    {
        public Monster[] monsters;
        public GameObject hero;
        private float span;
        public MapData mapData = new MapData();
        public Vector2Int bottomLeft, topRight;
        // Start is called before the first frame update
        void Start()
        {
            this.mapData.bottomLeft = this.bottomLeft;
            this.mapData.topRight = this.topRight;
            this.mapData.sizeX = topRight.x - bottomLeft.x + 1;
            this.mapData.sizeY = topRight.y - bottomLeft.y + 1;
            this.mapData.NodeArray = new Node[this.mapData.sizeX, this.mapData.sizeY];
    
        }
    
        void Update()
        {
            span += Time.deltaTime;
            if (span > 0.5f)
            {
                StartCoroutine(MonsterMoveRoutine());
    
                span = 0;
            }
        }
        private IEnumerator MonsterMoveRoutine()
        {
            foreach (Monster monster in monsters)
            {
                if ((monster.transform.position - hero.transform.position).magnitude > 1.1f)
                {
                    for (int i = 0; i < this.mapData.sizeX; i++)
                    {
                        for (int j = 0; j < this.mapData.sizeY; j++)
                        {
                            bool isWall = false;
                            foreach (Collider2D col in Physics2D.OverlapCircleAll(new Vector2(i + bottomLeft.x, j + bottomLeft.y), 0.3f))
                                if (col.gameObject.layer == LayerMask.NameToLayer("Water")) isWall = true;
    
                            this.mapData.NodeArray[i, j] = new Node(isWall, i + bottomLeft.x, j + bottomLeft.y);
                        }
                    }
                    monster.Move(hero.transform, this.mapData);
                }
                else { monster.Attack();}
                yield return new WaitForSeconds(0.05f);
            }
        }
    }
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Events;
    
    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 Monster : MonoBehaviour
    {
        private Animator anim;
        public List<Node> FinalNodeList;
        private GameObject target;
        private MapData mapData;
        Node StartNode, TargetNode, CurNode;
        List<Node> OpenList, ClosedList;
        
        private void Start()
        {
            currentHp = maxHp;
            this.anim = this.GetComponent<Animator>();
        }
    
        public void Move(Transform hero, MapData mapData)
        {
            this.target = hero.gameObject;
            this.mapData = mapData;
            StartNode = mapData.NodeArray[Mathf.RoundToInt(this.gameObject.transform.position.x) - mapData.bottomLeft.x, Mathf.RoundToInt(this.gameObject.transform.position.y) - mapData.bottomLeft.y];
            TargetNode = mapData.NodeArray[Mathf.RoundToInt(target.transform.position.x) - mapData.bottomLeft.x, Mathf.RoundToInt(target.transform.position.y) - mapData.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.ParentNode;
                    while (TargetCurNode != StartNode)
                    {
                        FinalNodeList.Add(TargetCurNode);
                        TargetCurNode = TargetCurNode.ParentNode;
                    }
                    FinalNodeList.Add(StartNode);
                    FinalNodeList.Reverse();
    
                    //hero.Move(this.FinalNodeList);
                    this.gameObject.transform.position = new Vector2(FinalNodeList[1].x, FinalNodeList[1].y);
                }
    
                // ↑ → ↓ ←
                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 >= mapData.bottomLeft.x && checkX < mapData.topRight.x + 1 && checkY >= mapData.bottomLeft.y &&
                checkY < mapData.topRight.y + 1 && !mapData.NodeArray[checkX - mapData.bottomLeft.x, checkY - mapData.bottomLeft.y].isWall && 
                !ClosedList.Contains(mapData.NodeArray[checkX - mapData.bottomLeft.x, checkY - mapData.bottomLeft.y]))
            {
                // 이웃노드에 넣고, 직선은 10, 대각선은 14비용
                Node NeighborNode = mapData.NodeArray[checkX - mapData.bottomLeft.x, checkY - mapData.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);
                }
            }
        }
    
        public void Attack()
        {
            this.StartCoroutine(AttackRoutine());
        }
    
        private IEnumerator AttackRoutine()
        {
            this.anim.SetInteger("State", 1);
            yield return new WaitForSeconds(0.34f);
            this.anim.SetInteger("State", 0);
        }
    }
Designed by Tistory.