-
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); } }