Unity는 A* 경로 찾기 알고리즘을 구현합니다.

머리말

A* 길찾기 알고리즘이란 무엇입니까?

게임 개발에서는 플레이어가 조종하는 캐릭터가 자동으로 목표 위치로 가는 길을 찾거나, AI 캐릭터가 목표 위치로 이동하도록 하는 등의 요구가 종종 있는데, 실제 상황은 매우 복잡할 수 있는데, 지도에 통과할 수 없는 장애물이 있거나 비용(시간 또는 기타 자원)을 지불해야 하는 경우와 같이 캐릭터가 최소 비용으로 대상에 도달하는 경로를 찾도록 하려면 강, 늪 등만 통과할 수 있습니다. , 몇 가지 특별한 알고리즘을 사용해야 하며 A 경로 찾기 알고리즘은 현재 가장 널리 사용되는 경로 찾기 알고리즘 중 하나인 유니티 에셋 스토어에서 호평을 받은 A* Pathfinding 프로젝트 플러그인도 A 기반으로 구현됩니다. 경로 찾기 알고리즘 간단히 말해서 A* 알고리즘은 최단 경로를 찾고 장애물을 피하기 위한 알고리즘입니다 .


A* 알고리즘의 기본 개념

A*가 길 찾기에 가장 인기 있는 선택인 이유는 매우 유연하여 다양한 환경에서 사용할 수 있기 때문입니다.

Dijkstra의 알고리즘과 마찬가지로 최단 경로를 찾는 데 사용할 수 있습니다.

탐욕스러운 알고리즘과 마찬가지로 휴리스틱을 사용하여 스스로를 안내할 수 있습니다. 간단한 경우에는 탐욕 알고리즘만큼 빠릅니다.

오목한 장애물이 있는 예에서 A*는 Dijkstra의 알고리즘으로 찾은 경로만큼 좋은 경로를 찾을 수 있습니다.

성공의 비결은 Dijkstra 알고리즘에서 사용하는 정보(시작점에 가까운 정점 지원)와 greedy 알고리즘에서 사용하는 정보(목표에 가까운 지원 정점)를 결합한다는 것입니다.

A*에 대해 말할 때 사용되는 표준 용어 g(n)로, 원점에서 정점까지의 경로 의  정확한 비용 n 을 나타내며 , 정점에서 목표까지의 발견적  추정 비용 h(n) 을 나타냅니다 . n

위 이미지에서 노란색 점 ( h) 은 대상에서 멀리 떨어진 정점을 나타내고, 청록색 점 ( g) 은 시작점에서 멀리 떨어진 정점을 나타냅니다.

A*는 출발점에서 목표점으로 이동할 때 이 둘의 균형을 맞춥니다. 메인 루프를 통과할 때마다 n 이 가장 낮은 정점을 가지고 있는지 확인합니다 . f(n) = g(n) + h(n)


A* 알고리즘의 구현 원리

A *  알고리즘 을 구현하려면 먼저 복잡한 게임 맵을 경로 찾기 그리드로 추상화해야 합니다(예: 위 그림). 가장 쉬운 방법은 게임 맵을 여러 개의 정사각형 단위 또는 일반 다각형 단위로 나누는 것입니다. 불균일한 볼록 다각형으로 나눌 수 있으며, 이러한 격자는 "길 찾기 포인트"로 간주될 수 있습니다. 격자가 미세할수록 경로 찾기 효과는 더 좋지만 계산량이 많아 실제 게임에서 환경, 균형이 잘 맞아야 합니다. 성능과 효과를 확인하십시오.


A 알고리즘의 기본 아이디어는 이러한 그리드를 사용하여 경로 찾기를 달성하고, 시작점에서 주변 지점을 횡단하고, 최단 경로에 있을 가능성이 가장 높은 지점을 찾고, 이 지점을 기준점으로 계속 횡단하는 것입니다. 끝점에 도달할 때까지 벤치마킹하고 경로도 찾습니다.

이 아이디어에서 A 알고리즘은 대략적인 최적의 솔루션만 얻을 수 있음을 알 수 있습니다. 사실, 경로 찾기 문제의 경우 종종 하나 이상의 최적의 솔루션이 있습니다. 모든 솔루션을 찾아야 하는 경우에만 가능한 경로를 하나씩 비교하지만 이것은 너무 비효율적이므로 A 알고리즘은 전체 맵을 탐색하지 않고 최단 경로의 점과 그 주변 점만 탐색하므로 다음을 얻습니다. 대략적인 최적의 솔루션입니다.


그렇다면 주변 지점을 횡단할 때 가장 짧은 경로에 있을 가능성이 가장 높은 지점을 어떻게 결정할 수 있습니까? 이것이 A 알고리즘의 핵심입니다. F=G+H

각 길찾기 포인트는 F, G, H의 3가지 속성을 가지고 있으며, F는 이 포인트를 통과하는데 드는 총 비용으로 이해할 수 있으며, 비용이 낮을수록 이 포인트가 최단 경로에 있을 가능성이 높습니다. G는 시작점에서 이 지점까지의 비용, H는 이 지점에서 종료 지점까지의 비용으로 이 두 비용의 합이 이 지점의 총 비용입니다. 계산 방법에 대한 예는 아래에 나와 있습니다.


우리는 또한 두 개의 세트가 필요합니다. 하나는 공개 세트이고 다른 하나는 닫기 세트입니다.공개 세트는 아직 비용을 계산하지 않은 포인트를 저장하고 닫기 세트는 계산된 포인트를 포함합니다. 처음에 열린 집합에는 시작점만 있고 닫은 집합에는 요소가 없습니다. 각 반복에서 열린 집합에서 가장 작은 F를 가진 점을 기준점으로 사용하고 인접한 점을 기준점 주변은 다음과 같이 처리된다.
(1) 이 점이 장애물이라면 직접 무시한다.
(2) 만약 그 점이 open table과 close table에 없다면 open table을 결합한다.
(3) point가 이미 open table에 있고 현재 기준점의 path cost가 더 낮으면 G 값을 업데이트하고 부모
(4) 이 점이 닫기 테이블에 있으면 무시합니다.
처리 후 닫기 컬렉션에 기준점을 추가합니다.
열린 목록에 끝점이 나타나면 반복이 종료됩니다.
열린 테이블이 끝에 도달하기 전에 비어 있으면 끝에 도달하는 경로가 없습니다.


텍스트

A알고리즘 구현

가장 간단한 A* 알고리즘을 구현해 보자. A* 알고리즘은 실제 개발을 위해 많은 변화가 있다. 디자인 방법은 게임의 요구와 관련이 있다. 여기에서 Unity는 기본적인 2D 정사각형 격자 경로 찾기를 구현하는 데 사용됩니다. 실제 개발 Unity의 탐색 메쉬 또는 A* Pathfinding Project 플러그인을 직접 사용할 수도 있습니다.

이 구현에서는 그리드에 통과할 수 없는 장애물이 있는 10x10 그리드를 정의합니다.

public class Point
{
    public int X;
    public int Y;
    public int F;
    public int G;
    public int H;
    public Point parent=null;

    public bool isObstacle = false;

    public Point(int x,int y)
    {
        X = x;
        Y = y;
    }

    public void SetParent(Point parent,int g)
    {
        this.parent = parent;
        G = g;
        F = G + H;
    }
}

여기에서 Point 클래스는 각 길 찾기 포인트를 나타내기 위해 정의되고, X와 Y는 좌표를 나타내고, F, G, H는 위에서 언급한 세 가지 속성, isObstacle은 포인트가 장애물(통과 불가)인지 여부를 나타내고, parent는 포인트를 나타냅니다. 아버지 노드는 최단 경로에 있을 수 있는 다음 지점으로 이동할 때마다 해당 경로의 아버지를 현재 노드로 설정하여 경로 찾기가 끝난 후 끝점에서 다음과 같이 단계적으로 시작점으로 돌아갈 수 있습니다. 아버지 노드를 방문하여 경로를 저장합니다.

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

public class AStar : MonoBehaviour
{
    public const int width = 10;
    public const int height = 10;

    public Point[,] map = new Point[height,width];
    public SpriteRenderer[,] sprites = new SpriteRenderer[height, width];//图片和结点一一对应

    public GameObject prefab;   //代表结点的图片
    public Point start;
    public Point end;

    void Start()
    {
        InitMap();
        //测试代码
        AddObstacle(2, 4);
        AddObstacle(2, 3);
        AddObstacle(2, 2);
        AddObstacle(2, 0);
        AddObstacle(6, 4);
        AddObstacle(8, 4);
        SetStartAndEnd(0, 0, 7, 7);
        FindPath();
        ShowPath();
    }

    public void InitMap()//初始化地图
    {
        for(int i=0;i<width;i++)
        {
            for (int j = 0; j < height; j++)
            {
                sprites[i, j] = Instantiate(prefab, new Vector3(i, j, 0),Quaternion.identity).GetComponent<SpriteRenderer>();
                map[i, j] = new Point(i, j);
            }
        }
    }

    public void AddObstacle(int x,int y)//添加障碍
    {
        map[x, y].isObstacle = true;
        sprites[x, y].color = Color.black;
    }

    public void SetStartAndEnd(int startX,int startY,int endX,int endY)//设置起点和终点
    {
        start = map[startX,startY];
        sprites[startX, startY].color = Color.green;
        end = map[endX, endY];
        sprites[endX, endY].color = Color.red;
    }

    public void ShowPath()//显示路径
    {
        Point temp = end.parent;
        while(temp!=start)
        {
            sprites[temp.X, temp.Y].color = Color.gray;
            temp = temp.parent;
        }
    }

    public void FindPath()
    {
        List<Point> openList = new List<Point>();
        List<Point> closeList = new List<Point>();
        openList.Add(start);
        while(openList.Count>0)//只要开放列表还存在元素就继续
        {
            Point point = GetMinFOfList(openList);//选出open集合中F值最小的点
            openList.Remove(point);
            closeList.Add(point);
            List<Point> SurroundPoints = GetSurroundPoint(point.X,point.Y);

            foreach(Point p in closeList)//在周围点中把已经在关闭列表的点删除
            {
                if(SurroundPoints.Contains(p))
                {
                    SurroundPoints.Remove(p);
                }
            }

            foreach (Point p in SurroundPoints)//遍历周围的点
            {
                if (openList.Contains(p))//周围点已经在开放列表中
                {
                    //重新计算G,如果比原来的G更小,就更改这个点的父亲
                    int newG = 1 + point.G;
                    if(newG<p.G)
                    {
                        p.SetParent(point, newG);
                    }
                }
                else
                {
                    //设置父亲和F并加入开放列表
                    p.parent = point;
                    GetF(p);
                    openList.Add(p);
                }
            }
            if (openList.Contains(end))//只要出现终点就结束
            {
                break;
            }
        }
    }


    public List<Point> GetSurroundPoint(int x,int y)//得到一个点周围的点
    {
        List<Point> PointList = new List<Point>();
        if(x>0&&!map[x-1,y].isObstacle)
        {
            PointList.Add(map[x - 1, y]);
        }
        if(y>0 && !map[x , y-1].isObstacle)
        {
            PointList.Add(map[x, y - 1]);
        }
        if(x<height-1 && !map[x + 1, y].isObstacle)
        {
            PointList.Add(map[x + 1, y]);
        }
        if(y<width-1 && !map[x , y+1].isObstacle)
        {
            PointList.Add(map[x, y + 1]);
        }
        return PointList;
    }


    public void GetF(Point point)//计算某个点的F值
    {
        int G = 0;
        int H = Mathf.Abs(end.X - point.X) + Mathf.Abs(end.Y - point.Y);
        if(point.parent!=null)
        {
            G = 1 + point.parent.G;
        }
        int F = H + G;
        point.H = H;
        point.G = G;
        point.F = F;
    }


    public Point GetMinFOfList(List<Point> list)//得到一个集合中F值最小的点
    {
        int min = int.MaxValue;
        Point point = null;
        foreach(Point p in list)
        {
            if(p.F<min)
            {
                min = p.F;
                point = p;
            }
        }
        return point;
    }
}

위는 A* 알고리즘의 코드로, 각 노드는 100x100 픽셀의 이미지를 사용하여 각 노드의 색상을 수정하여 시작점, 끝점, 장애물, 경로를 표현하였다. 여기서 계산하는 방법은 그리드 이동 비용이 1이므로 시작점의 G 값은 0이고 각 순회에 대해 G+1이 추가되고 H는 현재 노드와 x축과 y축의 끝점.

최종 효과

길 찾기 전에

길찾기 결과


요약하다

A* 길찾기는 확장할 곳이 꽤 많은데, 핵심만 잡고 있는 한 계속해서 주변 포인트의 비용을 계산하고 종점에 도달하기까지 최소 비용이 소요되는 경로를 찾는 것입니다 . 이 비용을 계산할 수 있습니다. 다양한 복잡한 상황에 대해 다양한 방식으로.

예를 들어 FPS 게임의 AI의 경우 게임 내 플레이어는 반드시 화력 범위 내에서 적을 공격하게 되는데 이때 최단 경로를 취하기 위해 플레이어의 총에 노출되면 플레이어의 공격 범위 내 포인트의 비용 값은 AI가 짧은 경로와 공격을 받을 위험 사이에서 절충을 할 수 있도록 하거나 현재 어딘가에 보상 아이템이 있습니다 , 보상 아이템 근처의 포인트 비용 값을 줄일 수 있으며, AI가 소품을 얻기 위해 우회하는 경향이 있습니다.

요컨대, 알고리즘 아이디어를 이해해야만 다양한 길 찾기 상황에서 유연한 적용을 위한 토대를 마련할 수 있습니다.

рекомендация

отblog.csdn.net/flyTie/article/details/127151517