A Star尋路簡(jiǎn)易原理

首先說下簡(jiǎn)單的原理


image.png

如圖所示簡(jiǎn)易地圖, 其中綠色方塊的是起點(diǎn) (用 A 表示), 中間藍(lán)色的是障礙物, 紅色的方塊 (用 B 表示) 是目的地. 為了可以用一個(gè)二維數(shù)組來表示地圖, 我們將地圖劃分成一個(gè)個(gè)的小方塊.
二維數(shù)組在游戲中的應(yīng)用是很多的, 比如貪吃蛇和俄羅斯方塊基本原理就是移動(dòng)方塊而已. 而大型 游戲的地圖, 則是將各種"地貌"鋪在這樣的小方塊上.

尋路步驟
1. 從起點(diǎn) A 開始, 把它作為待處理的方格存入一個(gè)"開啟列表", 開啟列表就是一個(gè)等待檢查方格 的列表.   
2. 尋找起點(diǎn) A 周圍可以到達(dá)的方格, 將它們放入"開啟列表", 并設(shè)置它們的"父方格"為 A.      
3. 從"開啟列表"中刪除起點(diǎn) A, 并將起點(diǎn) A 加入"關(guān)閉列表", "關(guān)閉列表"中存放的都是不需要再 次檢查的方格
image.png

圖中淺綠色描邊的方塊表示已經(jīng)加入 "開啟列表" 等待檢查. 淡藍(lán)色描邊的起點(diǎn) A 表示已經(jīng)放入 "關(guān)閉列表" , 它不需要再執(zhí)行檢查.
從 "開啟列表" 中找出相對(duì)最靠譜的方塊, 什么是最靠譜? 它們通過公式 F=G+H 來計(jì)算.

F = G + H             
G 表示從起點(diǎn) A 移動(dòng)到網(wǎng)格上指定方格的移動(dòng)耗費(fèi) (可沿斜方向移動(dòng)).          
H 表示從指定的方格移動(dòng)到終點(diǎn) B 的預(yù)計(jì)耗費(fèi) (H 有很多計(jì)算方法, 這里我們?cè)O(shè)定只可以 上下左右移動(dòng)). 
image.png

我們假設(shè)橫向移動(dòng)一個(gè)格子的耗費(fèi)為 10, 為了便于計(jì)算, 沿斜方向移動(dòng)一個(gè)格子耗費(fèi)是 14. 為了 更直觀的展示如何運(yùn)算 FGH, 圖中方塊的左上角數(shù)字表示 F, 左下角表示 G, 右下角表示 H. 看看是否 跟你心里想的結(jié)果一樣?
從 "開啟列表" 中選擇 F 值最低的方格 C (綠色起始方塊 A 右邊的方塊), 然后對(duì)它進(jìn)行如下處 理:

4. 把它從 "開啟列表" 中刪除, 并放到 "關(guān)閉列表" 中. 
5. 檢查它所有相鄰并且可以到達(dá) (障礙物和 "關(guān)閉列表" 的方格都不考慮) 的方格. 如果這
些方格 還不在 "開啟列表" 里的話, 將它們加入 "開啟列表", 計(jì)算這些方格的 G, H 和 F 值
各是多少, 并設(shè)置 它們的 "父方格" 為 C.      
6. 如果某個(gè)相鄰方格 D 已經(jīng)在 "開啟列表" 里了, 檢查如果用新的路徑 (就是經(jīng)過 C 的路
徑) 到 達(dá)它的話, G 值是否會(huì)更低一些, 如果新的 G 值更低, 那就把它的 "父方格" 改為目前
選中的方格 C, 然 后重新計(jì)算它的 F 值和 G 值 (H 值不需要重新計(jì)算, 因?yàn)閷?duì)于每個(gè)方塊, 
H 值是不變的). 如果新的 G 值比較高, 就說明經(jīng)過 C 再到達(dá) D 不是一個(gè)明智的選擇, 因?yàn)?它需要更遠(yuǎn)的路, 這時(shí)我們什么也不做. 
image.png

如圖, 我們選中了 C 因?yàn)樗?F 值最小, 我們把它從 "開啟列表" 中刪除, 并把它加入 "關(guān)閉列 表". 它右邊上下三個(gè)都是墻, 所以不考慮它們. 它左邊是起始方塊, 已經(jīng)加入到 "關(guān)閉列表" 了, 也不考 慮. 所以它周圍的候選方塊就只剩下 4 個(gè). 讓我們來看看 C 下面的那個(gè)格子, 它目前的 G 是 14, 如 果通過 C 到達(dá)它的話, G 將會(huì)是 10 + 10, 這比 14 要大, 因此我們什么也不做. 然后我們繼續(xù)從 "開啟列表" 中找出 F 值最小的, 但我們發(fā)現(xiàn) C 上面的和下面的同時(shí)為 54, 這 時(shí)怎么辦呢? 這時(shí)隨便取哪一個(gè)都行, 比如我們選擇了 C 下面的那個(gè)方塊 D.


image.png

D 右邊已經(jīng)右上方的都是墻, 所以不考慮, 但為什么右下角的沒有被加進(jìn) "開啟列表" 呢? 因?yàn)槿?果 C 下面的那塊也不可以走, 想要到達(dá) C 右下角的方塊就需要從 "方塊的角" 走了, 在程序中設(shè)置是 否允許這樣走. (圖中的示例不允許這樣走)
image.png
就這樣, 我們從 "開啟列表" 找出 F 值最小的, 將它從 "開啟列表" 中移掉, 添加到 "關(guān)閉列表". 再繼續(xù)找出它周圍可以到達(dá)的方塊, 如此循環(huán)下去...
那么什么時(shí)候停止呢? —— 當(dāng)我們發(fā)現(xiàn) "開始列表" 里出現(xiàn)了目標(biāo)終點(diǎn)方塊的時(shí)候, 說明路徑已經(jīng) 被找到.
如何找回路徑
image.png

如上圖所示, 除了起始方塊, 每一個(gè)曾經(jīng)或者現(xiàn)在還在 "開啟列表" 里的方塊, 它都有一個(gè) "父方 塊", 通過 "父方塊" 可以索引到最初的 "起始方塊", 這就是路徑.

代碼實(shí)現(xiàn)
public class Point
{
    public Point ParentPoint { get; set; }
    public float F { get; set; }
    public float G { get; set; }
    public float H { get; set; }

    public int X { get; set; }
    public int Y { get; set; }

    public bool IsWall { get; set; }
    public Point(int x, int y, Point parent = null)
    {
        this.X = x;
        this.Y = y;
        this.ParentPoint = parent;
        IsWall = false;

    }
 
    public void UpdataParent(Point parent, float g) 
    {
        this.ParentPoint = parent;
        this.G = g;
        F = G + H;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AStar : MonoBehaviour
{

   const int mapWith = 8;
   const  int mapHeight = 6;
    private Point[,] map = new Point[mapWith, mapHeight];


    // Use this for initialization
    void Start()
    {
        InitMap();
        Point start = map[3, 3];
        Point end = map[7, 3];
        FindPath(start, end);
        ShowPath(start, end);
      
    }
    void Update()
    {

    }
    void InitMap()
    {
        for (int x = 0; x < mapWith; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                map[x, y] = new Point(x, y);
            }
        }
        map[5, 2].IsWall = true;
        map[5, 3].IsWall = true;
        map[5, 4].IsWall = true;
    }
    void ShowPath(Point start,Point end)
    {
        Point temp = end;
        while (true)
        {
            Debug.Log(temp.X + "," + temp.Y);
            Color c = Color.gray;
            if (temp == start) c = Color.green;
            if (temp == end) c = Color.red;
            CreateCube(temp.X,temp.Y,c);

            if (temp.ParentPoint == null) break;
            temp = temp.ParentPoint;
        }
        for (int x = 0; x < mapWith; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                if (map[x, y].IsWall)
                {
                    CreateCube(x,y,Color.blue);
                }
            }
        }
    }

    void CreateCube(int x,int y,Color color)
    {
        GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
        go.transform.position = new Vector3(x,y,0);
        go.GetComponent<MeshRenderer>().material.color = color;
    }
    void FindPath(Point start, Point end)
    {
        List<Point> openList = new List<Point>();
        List<Point> closeList = new List<Point>();
        openList.Add(start);
        while (openList.Count > 0)
        {

            Point point = FindMinFOfPoint(openList);
            openList.Remove(point);
            closeList.Add(point);
            List<Point> SurroundPoints = GetSurroundPoints(point);
            PointsFilter(SurroundPoints,closeList);
            foreach (Point SurroundPoint in SurroundPoints)
            {
                if (openList.IndexOf(SurroundPoint) > -1)
                {
                    float nowG = CalcG(SurroundPoint, point);
                    if (nowG < SurroundPoint.G)
                    {
                        SurroundPoint.UpdataParent(point, nowG);
                    }
                }
                else
                {
                    SurroundPoint.ParentPoint = point;
                    CalcF(SurroundPoint, end);
                    openList.Add(SurroundPoint);
                }

            }
            if (openList.IndexOf(end) > -1)
                break;

        }

       // start.CalcF();

    }
   void PointsFilter(List<Point> src, List<Point> closePoint)
    {
        foreach (Point p in closePoint)
        {
            if (src.IndexOf(p) > -1)
            {
                src.Remove(p);
            }
        }
    }
    List<Point> GetSurroundPoints(Point point)
    {
        Point left=null, up=null, down=null, right=null;
        Point lu = null, ru = null, ld = null, rd = null;
        if (point.Y < mapHeight - 1) up = map[point.X, point.Y + 1];
        if (point.Y > 0) down = map[point.X, point.Y - 1];
        if (point.X > 0) left = map[point.X-1, point.Y];
        if (point.X < mapWith - 1) right = map[point.X + 1, point.Y];
        if(up!=null&&left!=null)
            lu = map[point.X - 1, point.Y+1];
        if (up != null && right != null)
            ru = map[point.X + 1, point.Y + 1];
        if (down != null && left != null)
            ld = map[point.X - 1, point.Y - 1];
        if (down != null && right != null)
            rd = map[point.X + 1, point.Y - 1];
        List<Point> list = new List<Point>();
        if (down != null && down.IsWall == false)
            list.Add(down);
        if (up != null && up.IsWall == false)
            list.Add(up);
        if (left != null && left.IsWall == false)
            list.Add(left);
        if (right != null && right.IsWall == false)
            list.Add(right);
        if(lu!=null&&lu.IsWall==false&&left.IsWall==false&&up.IsWall==false)
            list.Add(lu);
        if (ru != null && ru.IsWall == false && right.IsWall == false && up.IsWall == false)
            list.Add(ru);
        if (ld != null && ld.IsWall == false && left.IsWall == false && down.IsWall == false)
            list.Add(ld);
        if (rd != null && rd.IsWall == false && right.IsWall == false && down.IsWall == false)
            list.Add(rd);
        return list;
    }
    Point FindMinFOfPoint(List<Point> openList)
    {
        float f = float.MaxValue;

        Point temp = null;
        foreach (Point p in openList)
        {
            if (p.F < f)
            {

                temp = p;
                f = p.F;
            }
        }
        return temp;
    }

    float CalcG(Point now,Point parent)
    {
      return  Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(parent.X, parent.Y)) + parent.G;
    }
    public void CalcF(Point now, Point end)
    {
        float h = Mathf.Abs(end.X - now.X) + Mathf.Abs(end.Y - now.Y);
        float g = 0;
        if (now.ParentPoint == null)
        {
            g = 0;
        }
        else
        {
            g = Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(now.ParentPoint.X, now.ParentPoint.Y)) + now.ParentPoint.G;
        }

        float f = g + h;
        now.F = f;
        now.G = g;
        now.H = h;
    }
}

運(yùn)行后,綠色代表起點(diǎn),紅色代表終點(diǎn),藍(lán)色是障礙物,灰色就是最短路徑了。


image.png

相關(guān)插件:A* Pathfinding Project

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容