Unity は A* 経路探索アルゴリズムを実装しています

序文

A* 経路探索アルゴリズムとは何ですか?

ゲーム開発では、プレイヤーが操作するキャラクターが目的地までの道のりを自動で見つけられるようにしたり、AIキャラクターを目的地まで移動させたりすることがよくあります.実際の状況は非常に複雑かもしれませんが、マップ上に通行できない障害物がある、または代償 (時間またはその他のリソース) を支払う必要があるなど キャラクターが最小のコストでターゲットに到達するためのパスを見つけたい場合は、川や沼地などを通過することしかできません、あなたはいくつかの特別なアルゴリズムを使用する必要があり、A * パスファインディング アルゴリズムは現在最も広く使用されているパスファインディングです. アルゴリズムの 1 つである、Unity アセット ストアで好評を博している A* パスファインディング プロジェクト プラグインも、A に基づいて実装されています。経路探索アルゴリズム. 簡単に言えば、A* アルゴリズムは、最短経路を見つけて障害物を回避するためのアルゴリズムです


A* アルゴリズムの基本概念

A* が道案内に最も人気のある選択肢である理由は、さまざまな環境で使用できるほど柔軟だからです。

ダイクストラのアルゴリズムと同様に、最短経路を見つけるために使用できます。

貪欲なアルゴリズムのように、ヒューリスティックを使用して自分自身を導くことができます。単純なケースでは、貪欲なアルゴリズムと同じくらい高速です。

凹状の障害物がある例では、A* はダイクストラのアルゴリズムで見つかったものと同じくらい良いパスを見つけることができます。

その成功の秘訣は、ダイクストラのアルゴリズム (開始点に近い頂点をサポートする) で使用される情報と貪欲なアルゴリズム (ゴールに近い頂点をサポートする) で使用される情報を組み合わせることです。

A* について話すときに使用される標準的な用語g(n)は、原点から任意の頂点までのパスの 正確なコスト nh(n)示し、頂点からゴールまでのヒューリスティックに 推定されたコスト を示します。 n

上の画像で、黄色の点( h)はターゲットから遠く離れた頂点を表し、シアンの点( g)は開始点から遠く離れた頂点を表します。

A* は、スタート地点からゴールまで移動する際に、この 2 つのバランスをとります。メイン ループを通過するたびに、nの頂点が最も低いことを確認します f(n) = g(n) + h(n)


A*アルゴリズムの実現原理

A * アルゴリズムを実装するには、まず、複雑なゲーム マップを経路探索グリッド (上図など) に抽象化する必要があります. 最も簡単な方法は、ゲーム マップを複数の正方形ユニットまたは正多角形ユニットに分割することです。等間隔でない凸多角形に分割でき、この格子を「経路探索点」とみなすことができ、格子が細かいほど経路探索効果は高くなりますが、計算量が多くなるため、実際のゲームでは環境、それはバランスが取れている必要がありますパフォーマンスと効果をチェックしてください。


A アルゴリズムの基本的な考え方は、これらのグリッドを使用して経路探索を行い、開始点から周囲の点をたどり、最短経路上にある可能性が最も高い点を見つけ、この点を基準として巡回し続けることです。終点に到達するまでベンチマークを行い、パスも検出されます。

この考えからもわかるように, A アルゴリズムはおおよその最適解しか得られません. 実際, 経路探索問題では, 多くの場合, 複数の最適解が存在します. すべての解を見つけなければならない場合は,可能なパスを 1 つずつ比較しますが、これでは非効率すぎるため、A アルゴリズムはマップ全体をトラバースせず、最短パス上のポイントとその周辺のポイントのみをトラバースするため、得られるものは近似最適解です。


では、周囲のポイントをトラバースするときに、どのポイントが最短経路上にある可能性が最も高いかをどのように判断するのでしょうか? これが A * アルゴリズムの核心です: F=G+H

各経路探索ポイントには、F、G、H の 3 つの属性があります.F は、このポイントを通過するための総コストとして理解できます.コストが低いほど、このポイントが最短経路上にある可能性が高くなります. G は始点からこの点までのコスト、H はこの点から終点までのコストです. これら 2 つのコストの合計が、この点の総コストです. 計算方法の例を次に示します.


オープン セットとクローズ セットの 2 つのセットも必要です. オープン セットにはまだコストが計算されていないポイントが格納され、クローズ セットには計算済みのポイントが含まれます. 最初は開集合に始点のみがあり、閉集合には要素がなく、反復ごとに開集合の最小 F を持つ点が基点として使用され、隣接点は
(1) この点が障害物なら直接無視する
(2) ポイントがオープン テーブルとクローズ テーブルにない場合、オープン テーブルを結合します
(3) ポイントが既にオープン テーブルにあり、現在のベース ポイントのパス コストが低い場合、その G 値を更新し、 parent
(4) この点が近いテーブルにある場合は、無視します。
処理後、近いコレクションに基点を追加します。
開いているリストに終点が表示されると、反復が終了します。
開いているテーブルが最後に到達する前に空の場合、最後に到達するパスはありません。


文章

* アルゴリズム実装

最も単純な 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 は上記の 3 つの属性、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