主頁 >  其他 > 基于Unity的A星尋路演算法(絕對簡單完整版本)

基于Unity的A星尋路演算法(絕對簡單完整版本)

2021-08-15 09:48:50 其他

前言

在上一篇文章,介紹了網格地圖的實作方式,基于該文章,我們來實作一個A星尋路的演算法,最終實作的效果為:
請添加圖片描述

專案原始碼已上傳Github:AStarNavigate

在閱讀本篇文章,如果你對于里面提到的一些關于網格地圖的創建方式的一些地圖不了解的話,可以先閱讀了解一下下面的這篇文章:

文章鏈接:

  • Unity 制作一個網格地圖生成組件

1、簡單做一些背景介紹

在介紹A星尋路演算法前,先介紹另外一種演算法:Dijkstra尋路演算法,簡單的來說是一種A星尋路的基礎版,Dijkstra作為一種無啟發的尋路演算法,通過圍繞起始點向四周擴展遍歷,一直到找到目標點結束,簡單來說就是暴力破解,由近到遠遍歷所有可能,從而找到目標點

很明顯,這種尋路方式是很的消耗性能的,非常的不高效,有沒有更好的解決方式呢

從實際生活中出發,如果你要到達某地,卻不知道具體的路該怎么辦呢,是不是先大概確定方向,邊靠近目標點邊問路呢

A星尋路演算法也是基于這樣的思路,通過一定的邏輯找到可以靠近物體的方向,然后一步步的走進目標點,直到到達目的地,

二、A星尋路演算法的基本原理

整個理解程序是一個線性結構,只需要一步步完整的走下去,基本就可以對于A星有一個大概的了解,

確定直角斜角權重:

本質上來講,A星尋路是基于一種網格的地圖來實作的尋路的方式,在網格中,一個點可以到達的位置為周圍的八個方向,而由于水平與垂直和傾斜的方向距離不一樣,所以我們在尋路時需要設定不同的長度:

在這里插入圖片描述
通過圖片可以看出,直線距離與斜線距離是分別等腰直角三角形直角邊與斜邊,根據勾股定理我們可以得知兩者的比例關系約為1.41:1,為了方便計算,我們就將斜邊權重為14,而直角邊權重為10,這樣的話,要得到最短的路徑,可以按照下面的思路去考慮:

遍歷移動格子可能性:

接下來需要考慮第二個問題,在對起始點周圍的可移動格子遍歷完成后,如何找到最短路徑上的那個格子呢,即下一步該走哪一個格子,這里就是整個A星尋路演算法的核心:
在這里插入圖片描述

如圖,當我們第一步對起始點A周圍所有的格子遍歷后,從A出發有八個可以移動的方向可以到達下一個格子,如果你作為一個人類,當然一眼可以看出下一步向綠色箭頭方向移動產生的路徑是最短的,

我們人類可以根據經驗很快的判斷出方向,但是機器不能,計算機需要嚴謹的程式邏輯來實作這樣的效果,需要我們賦予他基本的執行程式,通過重復的執行這樣的邏輯,得到最終的效果,因此,接下來,需要思考如何讓計算機在一系列點位中找到方向最正確的那個點位

計算某一格子期望長度:

到目前,我們的目的就是使計算機可以找到找到所有可以走的格子中產生路徑最短的格子,接下來以你的經驗來思考,比較長短往往是依據什么,嘿嘿,別想歪,確實是數字的大小,所以我們需要給每一個格子一個數值來作為路徑通過該格子的代價,

當程式進行到現在,要解決的問題是如何求得一個數字來代表該格子,實作方式是通過計算一個通過格子路徑長度的對比來找到最短的路徑,而任一格子記錄路徑長度標記為All,并可以將其分為兩部分:已走路徑與預估路徑(不理解沒關系,接著往下看):
在這里插入圖片描述

如圖(靈魂畫手,順便加個防偽標志嘿嘿)求從A到B點的路徑,當前已經尋路到C點,如何求得經過該點的一個期望路徑的長度呢:

  • 到達該格子已經走過的路徑長度GG值的計算是基于遞推的思想,根據上一個格子的G再加上上一個格子到這個格子的距離即可
  • 當前格子到達終點預估路徑長度H:該距離是一個估計的距離,至于如何估計的,接下來會進行介紹

然后就可以求出該點的整個期望路徑長度All,對G和H進行一個簡單的加法:
在這里插入圖片描述
這樣我們就可以通過下一步所有可能的移動的格子中找到最短的格子

關于預估路徑長度H的計算:

  • 實作對于H的計算的估計有很多,由于本來就是預估,換句話就是不是一定準確的結果,所以我們可以通過計算當前節點到目標點的直線距離或者水平加垂直距離來獲得

在本文章的后面演示案例中,是基于水平加垂直距離來計算預估路徑長度H,即在上面的圖中,從C到B的預估路徑計算方式為:

Hcb = 水平格子差 * 10 + 垂直格子差 * 10

上述步驟總結升級:

假設我們走到了C點,并且接下來只能從C點向下一步移動,可以在下面的圖中看出接下來格子的所有可能性:
在這里插入圖片描述

下面我們來手動計算一下4號5號的預估路徑長度來幫助你理解這個程序,開始前我們要知道一條斜邊長14,直邊長度為10

則AC的長度為:

Lac=4*14=56

4號:

 H = Lac + 1 * 14 = 70
 G = 2 * 10 + 2 * 10 = 40
 All = H + G = 110

5號:

H = Lac + 1 * 10 = 66
G = 2 * 10 + 3 * 10 = 50
All = H + G = 116

經過對比,5號格子的期望路徑長度長于4號,在計算機運行程式時,會對1到7號都進行這樣的計算,然后求得其中的一個最小值并作為下一步的移動目標

注意:

  • 如過有兩個或者多個相同的最小值,會根據程式的寫法選擇任意一個,這不影響整個程式的運行思路

進一步升級

我們發現,上述步驟是有一些問題,因為場景中沒有障礙物,所以物體會一直走直線,但是在實際情況中,假若尋路走進了死胡同,最后的C點周圍沒有可以移動的點位怎么辦呢,

事實上在前面為了便于理解,我們在A星尋路上將問題簡化了,一直以最終點作為下一次尋路的起始點,這種方式是沒有辦法保證最短的路徑的,而在實際的A星尋路中,在每一步中,都會記錄新的可以移動的路徑加入到串列中,我們命名這個串列為開放串列,找到最短的一個節點后,將該點移除,并加入另外一個節點,命名為關閉串列,具體的可以這么說

  • 開放串列:用來在其中選擇預估路徑長度最短的點
  • 封閉串列:用來表示已經計算過該點,以后不再進行索引請添加圖片描述

圖中資訊注解:

  • 紅色格子:障礙物
  • 白色格子:可以移動區域
  • 黃色格子:起始點與終點
  • 藍色格子:代表開放串列中的格子,用來標識下一步所有可以移動的區域
  • 綠色格子:所有走過的格子,同時代表閉合串列中的格子
  • 黑色格子:最終的路徑

通過反復的觀看這張動圖,相信你應該對于A星尋路有一個完整的理解,接下來,就需要通過編程來實作該尋路演算法

三、編程實作

1、制作格子預制體模板

如果你之前看過Unity 制作一個網格地圖生成組件這篇文章,你應該很清楚接下來要做什么,如果你不了解也沒有關系,我這里再演示一遍:

創建一個Cube,并調整其縮放,掛載一個腳本Grid,然后編輯該腳本:
由于是作為尋路的基本格子,因此需要其記錄一些資訊,我們定義一些變數:

	//格子的坐標位置
    public int posX;
    public int posY;
    //格子是否為障礙物
    public bool isHinder;
    public Action OnClick;

    //計算預估路徑長度三個值
    public int G = 0;
    public int H = 0;
    public int All = 0;

    //記錄在尋路程序中該格子的父格子
    public Grid parentGrid;

同時在本專案中格子模板需要一個可以改變其顏色的方法用來標識當前模板所處于的狀態(障礙、起始點、終點、路徑等等),以及一個注冊點擊事件的委托方法,所以最后完整的代碼為:

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

public class Grid : MonoBehaviour
{

    public int posX;
    public int posY;
    public bool isHinder;
    public Action OnClick;

    //計算預估路徑長度三個值
    public int G = 0;
    public int H = 0;
    public int All = 0;

    //記錄在尋路程序中該格子的父格子
    public Grid parentGrid;
    public void ChangeColor(Color color)
    {
        gameObject.GetComponent<MeshRenderer>().material.color = color;
    }

    //委托系結模板點擊事件
    private void OnMouseDown()
    {
        OnClick?.Invoke();
    }

}

完成代碼的撰寫后,就可以將其拖入我們的資源管理視窗Project面板做成一個預制體,或者直接隱藏也可以

注意:

  • 如果你不理解我在干什么或者不懂代碼的內容,一定要去查看這篇文章:Unity 制作一個網格地圖生成組件

2、地圖創建

為了提升代碼的通用性,在這篇文章中,對于網格地圖創建的腳本做出了一些修改,主要在于替換掉腳本中的Grid變數的定義,轉換為GameObject,由于之前對該腳本有了詳細的介紹,所以只貼出了代碼:

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



public class GridMeshCreate : MonoBehaviour 
{
    [Serializable]
    public class MeshRange
    {
        public int horizontal;
        public int vertical;
    }
    [Header("網格地圖范圍")]
    public MeshRange meshRange;
    [Header("網格地圖起始點")]
    private Vector3 startPos;
    [Header("創建地圖網格父節點")]
    public Transform parentTran;
    [Header("網格地圖模板預制體")]
    public GameObject gridPre;
    [Header("網格地圖模板大小")]
    public Vector2 scale;


    private GameObject[,] m_grids;
    public GameObject[,] grids
    {
        get
        {
            return m_grids;
        }
    }
    //注冊模板事件
    public Action<GameObject, int, int> gridEvent;
    /// <summary>
    /// 基于掛載組件的初始資料創建網格
    /// </summary>
    public void CreateMesh()
    {
        if (meshRange.horizontal == 0 || meshRange.vertical == 0)
        {
            return;
        }
        ClearMesh();
        m_grids = new GameObject[meshRange.horizontal, meshRange.vertical];
        for (int i = 0; i < meshRange.horizontal; i++)
        {
            for (int j = 0; j < meshRange.vertical; j++)
            {
                CreateGrid(i, j);

            }
        }
    }

    /// <summary>
    /// 多載,基于傳入寬高資料來創建網格
    /// </summary>
    /// <param name="height"></param>
    /// <param name="widght"></param>
    public void CreateMesh(int height, int widght)
    {
        if (widght == 0 || height == 0)
        {
            return;
        }
        ClearMesh();
        m_grids = new GameObject[widght, height];
        for (int i = 0; i < widght; i++)
        {
            for (int j = 0; j < height; j++)
            {
                CreateGrid(i, j);

            }
        }
    }

    /// <summary>
    /// 根據位置創建一個基本的Grid物體
    /// </summary>
    /// <param name="row">x軸坐標</param>
    /// <param name="column">y軸坐標</param>
    public void CreateGrid(int row, int column)
    {
        GameObject go = GameObject.Instantiate(gridPre, parentTran);
        //T grid = go.GetComponent<T>();

        float posX = startPos.x + scale.x * row;
        float posZ = startPos.z + scale.y * column;
        go.transform.position = new Vector3(posX, startPos.y, posZ);
        go.SetActive(true);
        m_grids[row, column] = go;
        gridEvent?.Invoke(go, row, column);
    }
    /// <summary>
    /// 洗掉網格地圖,并清除快取資料
    /// </summary>
    public void ClearMesh()
    {
        if (m_grids == null || m_grids.Length == 0)
        {
            return;
        }
        foreach (GameObject go in m_grids)
        {
            if (go != null)
            {
                Destroy(go);
            }
        }
        Array.Clear(m_grids, 0, m_grids.Length);
    }
}

3、實作尋路的程序:

創建一個腳本命名為AStarLookRode作為尋路的腳本

變數定義:

在正式的邏輯代碼開始前,需要定義一些基本的變數:

  • 開放串列:存盤所有下一步可移動的格子
  • 封閉串列:存盤所有移動過的格子
  • 路徑堆疊:存盤最終尋路的路徑格子
  • 起始點
  • 終點
  • 場景中的網格地圖

完成變數的定義后,需要在尋路程式開始,對一些變數進行賦值,同時初始化串列,所以我們定義一個初始化的方法:

    public GridMeshCreate meshMap;
    public Grid startGrid;
    public Grid endGrid;

    public List<Grid> openGrids;
    public List<Grid> closeGrids;
    public Stack<Grid> rodes;

    public void Init(GridMeshCreate meshMap, Grid startGrid, Grid endGrid)
    {
        this.meshMap = meshMap;
        this.startGrid = startGrid;
        this.endGrid = endGrid;
        openGrids = new List<Grid>();
        closeGrids = new List<Grid>();
        rodes = new Stack<Grid>();
    }

添加路徑點周圍格子至開放串列:

接下來進行一個功能的代碼邏輯設計,如何將一個點周圍的格子加入到開放串列,可以觀察場景中的格子,有下面的兩種情況:

  • 位于地圖中心:周圍有八個可以移動的格子:
  • 位于地圖的邊緣:左邊或者右邊,上邊或者下邊沒有格子

這就需要我們從中找到可以取值的范圍,由于格子的位置資訊是一個二維坐標,XY,單純的從X軸來考慮,X-1會是格子左邊的格子的坐標,但是如果X-1<0則說明其左邊沒有格子,基于這樣的計算方式,來求得當前格子item周圍格子的坐標范圍,并剔除一些不需要添加的格子,具體的選擇步驟為:

  • 遍歷周圍的格子grid,如果存在于封閉串列closeGrids內,不處理
  • 如果格子在開放串列openGrids中,計算該點位到目前尋路位置點的期望路徑長度,如果長度更短的話,將當前格子item的父物體替換為該格子的grid
  • 接下來如果grid既不在開放串列openGrids,也不再閉合串列closeGrids內,若判斷不為障礙物,則將其加入開放串列openGrids,并設定其父物體為當前尋路位置item

簡單的從圖中理解:
在這里插入圖片描述
假定我們現在走到了A點(A代表當前路徑點Item),那么添加其周圍的格子(用grid代表)范圍限定在紅色框,為了便于區分不同的情況,我做了一些簡單的標識:

  • 紅色格子:障礙物,不處理
  • 綠色格子:已經走過的路徑,在閉合串列closeList內,不處理
  • 標有圓形的格子,未執行過任何操作,添加到openList里面
  • 橙色小框C:最需要理解的一個格子,首先要明白,該格子已經被其上面的綠色格子遍歷過,簡單的來說是已經在開放串列內,這個時候我們就要判斷A點如果經過C點過來,路徑會不會更短,如果會,則修改該A點的父元素為C點,否則不處理
    public void TraverseItem(int i, int j)
    {
        int xMin = Mathf.Max(i - 1, 0);
        int xMax = Mathf.Min(i + 1, meshMap.meshRange.horizontal - 1);
        int yMin = Mathf.Max(j - 1, 0);
        int yMax = Mathf.Min(j + 1, meshMap.meshRange.vertical - 1);

        Grid item = meshMap.grids[i, j].GetComponent<Grid>();
        for (int x = xMin; x <= xMax; x++)
        {
            for (int y = yMin; y <= yMax; y++)
            {
                Grid grid = meshMap.grids[x, y].GetComponent<Grid>();
                if ((y == j && i == x) || closeGrids.Contains(grid))
                {
                    continue;
                }
                if (openGrids.Contains(grid))
                {
                    if(item.All > GetLength(grid, item))
                    {
                        item.parentGrid = grid;
                        SetNoteData(item);
                    }  
                    continue;
                }                    
                if (!grid.isHinder)
                {
                    openGrids.Add(grid);
                    grid.parentGrid= item;
                }               
            }
        }
    }

求任一格子的期望路徑長度:

接下來就需要計算出一個格子的期望路徑的長度,要基于的父元素的G來計算出該格子的G,同時預估出來該格子到達目標的距離H,計算方式在原理里面已經介紹過,這里直接貼出代碼的執行方式:

    public int SetNoteData(Grid grid)
    {
        Grid itemParent = rodes.Count == 0 ? startGrid : grid.parentGrid;
        int numG = Mathf.Abs(itemParent.posX - grid.posX) + Mathf.Abs(itemParent.posY - grid.posY);
        int n = numG == 1 ? 10 : 14;
        grid.G = itemParent.G + n;

        int numH = Mathf.Abs(endGrid.posX - grid.posX) + Mathf.Abs(endGrid.posY - grid.posY);
        grid.H = numH * 10;
        grid.All = grid.H + grid.G;
        return grid.All;
    }

在前面的代碼中,有一個開放串列中已經存在,對比期望長度的更改父格子的功能功能,用到了求根據一個格子求下一個格子期望路徑長度的功能,雖然與上面的代碼功能類似,但是不能直接使用,提升通用性修改起來又麻煩,所以直接再寫一個:

    public int GetLength(Grid bejinGrid,Grid grid)
    {
        int numG = Mathf.Abs(bejinGrid.posX - grid.posX) + Mathf.Abs(bejinGrid.posY - grid.posY);
        int n = numG == 1 ? 10 : 14;
        int G = bejinGrid.G + n;
        
        int numH = Mathf.Abs(endGrid.posX - grid.posX) + Mathf.Abs(endGrid.posY - grid.posY);
        int H = numH * 10;
        int All = grid.H + grid.G;
        return All;
    }

開放串列中尋找期望路徑最短的格子:

在完成對于一個格子的期望路徑長度的計算,我們就需要從開放串列中找出期望路徑長度最短的路徑加入到路徑堆疊中

但是在這一步有這樣的一個問題,在原理介紹中也有說到,尋路程序中遇到障礙會進行回溯到之前的某一個路徑點,如果在在堆疊中執行這樣的操作呢

這里就要用到格子模板Grid中存盤的父格子的資訊,通過對比堆疊中的資料,查找到父格子的位置,清除后面的資料,并將該格子插入,具體代碼為:

     /// <summary>
    /// 在開放串列選中路徑最短的點加入的路徑堆疊,同時將路徑點加入到閉合串列中
    /// </summary>
    public void Traverse()
    {
        if (openGrids.Count == 0)
        {
            return;
        }
        Grid minLenthGrid = openGrids[0];
        int minLength = SetNoteData(minLenthGrid);
        for (int i = 0; i < openGrids.Count; i++)
        {
            if (minLength > SetNoteData(openGrids[i]))
            {
                minLenthGrid = openGrids[i];
                minLength = SetNoteData(openGrids[i]);
            }
        }
        minLenthGrid.ChangeColor(Color.green);
        Debug.Log("我在尋找人生的方向" + minLenthGrid.posX + "::::" + minLenthGrid.posY);

        closeGrids.Add(minLenthGrid);
        openGrids.Remove(minLenthGrid);               
        rodes.Push(minLenthGrid);
    }

獲取最終路徑:

在完成尋路的步驟后,需要根據路徑堆疊和格子的父物體來找到最短的路徑,這里比較功能邏輯比較清晰,直接貼代碼:

    void GetRode()
    {
        List<Grid> grids = new List<Grid>();
        rodes.Peek().ChangeColor(Color.black);
        grids.Insert(0, rodes.Pop());
        while (rodes.Count != 0)
        {
            if (grids[0].parentGrid != rodes.Peek())
            {
                rodes.Pop();

            }
            else
            {
                rodes.Peek().ChangeColor(Color.black);
                grids.Insert(0, rodes.Pop());               
            }

        }      
    }

封裝方法,對外暴露:

在解決三個關鍵功能的代碼后,就需要通過一個方法來完成整個尋路的程序,在方法的最后需要通過對終點坐標與堆疊頂物體的坐標進行對比,如果相同,則跳出回圈,執行路徑查找完成后的操作

同時為了在本案例中為了使得整個尋路程序的步驟可視化,使用一個協程來完成尋路程序的方法呼叫,這樣,在每一次完成一格的尋路后,可以通過協程來延時執行下一次回圈:

    public IEnumerator OnStart()
    {

        //Item itemRoot = Map.bolls[0].item;
        rodes.Push(startGrid);
        closeGrids.Add(startGrid);

        TraverseItem(startGrid.posX, startGrid.posY);
        yield return new WaitForSeconds(1);
        Traverse();

        //為了避免無法完成尋路而跳不出回圈的情況,使用For來規定尋路的最大步數
        for (int i = 0; i < 6000; i++)
        {
            if (rodes.Peek().posX == endGrid.posX && rodes.Peek().posY == endGrid.posY)
            {
                GetRode();
                break;
            }
            TraverseItem(rodes.Peek().posX, rodes.Peek().posY);
            yield return new WaitForSeconds(0.2f);
            Traverse();
        }
    }

四、 執行代碼

接下來需要創建一個腳本明命名為MainRun 來執行整個專案,主要部分為創建場景的網格地圖,在前面反復提到的文章里面已經有這一部分的介紹,接下來就需要對A星的呼叫:

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

public class MainRun : MonoBehaviour
{
    //獲取網格創建腳本
    public GridMeshCreate gridMeshCreate;
    //控制網格元素grid是障礙的概率
    [Range(0,1)]
    public float probability;
    bool isCreateMap=false;
    int clickNum=0;
    Grid startGrid;
    Grid endGrid;
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Run();
            isCreateMap = false;
            clickNum = 0;
        }
        if (Input.GetKeyDown(KeyCode.Q))
        {
            AStarLookRode aStarLookRode = new AStarLookRode();
            aStarLookRode.Init(gridMeshCreate,startGrid,endGrid);
            StartCoroutine(aStarLookRode.OnStart());          
        }
    }
    private void Run()
    {        
        gridMeshCreate.gridEvent = GridEvent;
        gridMeshCreate.CreateMesh();
    }

    /// <summary>
    /// 創建grid時執行的方法,通過委托傳入
    /// </summary>
    /// <param name="grid"></param>
    private void GridEvent(GameObject go,int row,int column)
    {
        //概率隨機決定該元素是否為障礙
        Grid grid = go.GetComponent<Grid>();
        float f = Random.Range(0, 1.0f);
        Color color = f <= probability ? Color.red : Color.white;
        grid.ChangeColor(color);
        grid.isHinder = f <= probability;
        grid.posX = row;
        grid.posY = column;
        //模板元素點擊事件
        grid.OnClick = () => {
            if (grid.isHinder)
                return;
            clickNum++;
            switch (clickNum)
            {
                case 1:
                    startGrid = grid;
                    grid.ChangeColor(Color.yellow);
                    break;
                case 2:
                    endGrid = grid;
                    grid.ChangeColor(Color.yellow);
                    isCreateMap = true;
                    break;
                default:
                    break;
            }

        };

    }
}

在該腳本中,主要是用來執行網格地圖創建的方法的,同時寫入A星腳本的執行介面,

場景執行:

創建一個空物體,并掛載網格地圖創建腳本GridMeshCreate與運行腳本MainRun,然后對這兩個腳本進行賦值:
在這里插入圖片描述
在兩個腳本中,我們可以控制一些變數來改變網創建網格地圖大小與障礙物的占比:

  • MainRunProbability:用來控制地圖中障礙物的數量占比
  • GridMeshCreateMesh Range:用來控制網格地圖的大小范圍

在完成上面的腳本掛載與設定后,就可以運行游戲,進入游戲場景后,點擊空格即可創建地圖,

在創建地圖后,可以使用滑鼠點擊地圖中的白色格子,第一次點擊表示選擇了路徑的起始點,而第二次點擊表示選擇了目標點格子

注意:

  • 這一塊Bug挺多的,我也沒有修改,所以盡量按著提示來,不要非要點擊障礙物,或者非要在場景中點擊三次

在完成對于兩個關鍵節點的選擇后,就可以點擊Q鍵開始執行尋路程序,然后就可以直接觀察整個場景中的運行流程:
請添加圖片描述

4、關于A星尋路的能效問題

演算法復雜度問題:

第一張圖片:障礙物的比例比較低時,尋找的路徑接近于一條直線,同時沒有多余的尋路節點產生:
在這里插入圖片描述
當地圖復雜度上升后,A星尋路產生巨大的代價才能獲取最后的路徑,而這些代價產生的原因是由于為了獲取最短的路徑而進行大量的回溯,而回溯又進一步造成了遍歷串列長度的增加,進一步的消耗了計算資源,

所以當地圖復雜度到達一定閾值并再次上升后,尋路的代價會急速的上升,也可以簡單的理解為指數的形式,而當這一數值超過了0.5,地圖基本就處于不可用的狀態,會有大量的死胡同,很大概率造成無路可循,

特殊情況的尋路效果:

話不多說,先看圖:
請添加圖片描述

通過上圖可以看出,雖然場景中的網格地圖很簡單,但是當兩個尋路點之間存在比較大的橫截面時,也同樣會付出巨大的尋路代價

擴展:

  • 看到這張圖,你知道Unity官方的NavMesh是如何實作尋路的嗎?

當我們使用NavMesh來執行尋路操作時,會事先對場景進行烘培,如果你曾經觀察過這張烘培地圖,就會發現其是由一張張三角面來構成的,而當我們進入游戲,執行尋路操作時,NavMesh就會根據這些三角面的頂點來執行可移動的路徑計算,

在這里插入圖片描述

如圖,其實NavMesh的優勢在與烘培階段對于地圖障礙的處理,通過一些頂點來大大簡化了尋路時的計算,

如果你先學習NavMesh 的使用方式:

  • 可以通過該文章:unity中Navigation實作自動尋路功能

總結

總的來說,A星是目前應用最廣的尋路方式,其特點簡單明了,整個程序以最短路徑為設計準則,逐漸的接近目標點,

但是要注意,A星雖然一直以最短為驅動,但是最終得到的路徑不一定最短(至少本篇文章的案例是這樣),至于原因,你如果理解了代碼的實作程序應該也能明白,如果你不理解,知道原因也沒意義,嘿嘿!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/293830.html

標籤:其他

上一篇:實作三子棋

下一篇:OpenCV(十三)影像金字塔(高斯金字塔(向下采樣)、拉普拉斯金字塔(向上采樣))

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more