1. 程式人生 > >關於A*尋路在Unity3D裡面的簡單應用

關於A*尋路在Unity3D裡面的簡單應用

這是之前做過的一個關於A*尋路演算法的小Demo ,現在分享出來 大家一起學習!

注意:在執行測試的時候 請將Cube的Scale設定成(0.9,0.9,0.9)

   在執行的時候 行列儘量設定的小點 不要超過35  (我設定40*40(1600個盒子)已經報出記憶體快取不夠的錯誤)

這是一種代價值的計算方式,也就是計算所需點的消耗值F 然後取出代價最低的點依次進行計算。


實現步驟:

G值和H值的計算方式

F=G+H
G=父格子的G+(當前位置在父格子的正【前後左右】+10否則+14)
H=從起點到重點的水平距離加上豎直距離*10

【-1】先把起點加入開啟列表
【0】然後計算周圍的非障礙非關閉格子的F值  計算過程=【1】【2】【3】
【1】計算G值 判斷是否更新G值 (新G值<原G值||原G值==0)更新
【2】計算H F值 並更新
【3】判斷如過當前計算的格子不在開啟列表中 加入開啟列表
【4】將當前父格子(就是中心點的盒子 由中心開啟周圍的盒子的那個盒子)移除開啟列表 加入關閉列表
【5】按照F值從小到大的順序進行排序
【6】判斷當前最小的F值是不是終點 不是的話 回到【0】步,是的話 尋路成功

【7】取出路線:從重點死迴圈取出父格子,判斷是起點跳出迴圈

下面是具體程式碼 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public enum CubeType
{
    /// <summary>
    /// 正常
    /// </summary>
    Normal,
    /// <summary>
    /// 障礙
    /// </summary>
    Barrier,
    /// <summary>
    /// 開始
    /// </summary>
    Start,
    /// <summary>
    /// 結束
    /// </summary>
    End
}
public class AStar : MonoBehaviour {

    /// <summary>
    /// 是否可以斜著走
    /// </summary>
    public bool MayInclinedToWalk;
    /// <summary>
    /// 生成路障的機率
    /// </summary>
    public int jiLv;
    /// <summary>
    /// 行
    /// </summary>
    public int Row=20;
    /// <summary>
    /// 列
    /// </summary>
    public int Line=20;
    /// <summary>
    /// 用來生成路面的Cube
    /// </summary>
    public GameObject Prefab;
    /// <summary>
    /// 儲存路面資訊
    /// </summary>
    public Grid[,] Cubes;
    /// <summary>
    /// 開始點的位置
    /// </summary>
    public Vector3 start;
    /// <summary>
    /// 結束點的位置
    /// </summary>
    public Vector3 end;
    /// <summary>
    /// 終點的格子資訊
    /// </summary>
    public Grid End;
    /// <summary>
    /// 用於尋路時對計算次數的記錄
    /// </summary>
    public int count;
     /// <summary>
     /// 
     /// </summary>
    public Stack<Grid> stack;
    /// <summary>
    /// 開放列表
    /// </summary>
    public List<Grid> Open=new List<Grid>();
    /// <summary>
    /// 閉合列表
    /// </summary>
    public List<Grid> Close=new List<Grid>();
    /// <summary>
    /// 尋路演算法
    /// </summary>
    void WayFinding()
    {
        while (true)//死迴圈遍歷 直到找到尋路結果或者尋路失敗的時候跳出
        {
            if (Open.Count == 0)//當開啟列表沒有值時 尋路失敗
            {
                Debug.Log("尋路失敗" + count);
                break;
            }
            if (Open[0].type == CubeType.End)//當到達重點的F值最小的格子是終點時 到達重點
            {
                Debug.Log("尋路成功" + count);
                if (End.parent)
                {
                    Grid grid = End;
                    while (true)
                    {
                        if (grid.parent.type == CubeType.Start)
                        {
                            stack.Push(grid);
                            break;
                        }
                        else
                        {
                            stack.Push(grid);
                            grid = grid.parent;
                        }
                    }
                }
                break;
            }
            //遍歷次數計數器
            count++;
            //遍歷當前格子周圍的8個格子
            for (int i = -1; i <= 1; i++)
            {
                for (int j = -1; j <= 1; j++)
                {
                    //如果不可以斜著走
                    if (!MayInclinedToWalk)
                    {
                        if (Mathf.Abs(i) == 1 && Mathf.Abs(j) == 1)//跳過不是上下左右的路徑
                            continue;
                    }
                    if (i == 0 && j == 0)//如果檢測的是當前格子就跳過
                        continue;
                    //計算當前檢測的格子
                    int x = (int)Open[0].transform.position.x + i;
                    int y = (int)Open[0].transform.position.z + j;
                    //計算條件(在範圍內 不是障礙 ***不在關閉列表)
                    if (x >= 0 && y >= 0 && x < Row && y < Line
                        && Cubes[x, y].type != CubeType.Barrier
                        && !Close.Contains(Cubes[x, y]))
                    {
                        //計算G
                        int g = (int)(Vector3.Distance(Cubes[x, y].gameObject.transform.position
                            , Open[0].transform.position) * 10) + Open[0].g;
                        //判斷更新G和父格子 (沒賦值過 或者新值小的時候)
                        if (Cubes[x, y].g == 0 || Cubes[x, y].g > g)
                        {
                            Cubes[x, y].parent = Open[0];//設定父格子
                            Cubes[x, y].g = g;//設定G值
                        }
                        Cubes[x, y].h = (Mathf.Abs((int)end.x - x) + Mathf.Abs((int)end.z - y)) * 10;//更新H值
                        Cubes[x, y].f = Cubes[x, y].h + Cubes[x, y].g;//更新F值
                        if (!Open.Contains(Cubes[x, y]))//不在開啟列表的時候加入開啟列表 相當於在開啟列表更新值 不在的話計算後加入
                            Open.Add(Cubes[x, y]);//加入開啟列表
                    }
                }
            }
            //遍歷結束 加入關閉列表
            Close.Add(Open[0]);
            //移除開始列表
            Open.Remove(Open[0]);
            //重新排序計算
            Open.Sort();
        }
    }
    void Start () {
        Cubes = new Grid[Row, Line];
        stack = new Stack<Grid>();
        ///按行列生成盒子
        for (int i = 0; i < Row; i++)
        {
            for (int j = 0; j < Line; j++)
            {                
                GameObject cube= GameObject.Instantiate(Prefab, new Vector3((float)i, 0, (float)j), Quaternion.identity);
                if (Random.Range(1, 101) < jiLv)
                {
                    cube.GetComponent<Grid>().type = CubeType.Barrier;
                    cube.GetComponent<MeshRenderer>().material.color = Color.blue;
                }
                if (Vector3.Distance(cube.transform.position, start) <= 0.1f)
                {
                    cube.GetComponent<Grid>().type = CubeType.Start;
                    cube.GetComponent<MeshRenderer>().material.color = Color.green;
                    Open.Add(cube.GetComponent<Grid>());
                }
                if (Vector3.Distance(cube.transform.position, end) <= 0.1f)
                {
                    cube.GetComponent<Grid>().type = CubeType.End;
                    End = cube.GetComponent<Grid>();
                    cube.GetComponent<MeshRenderer>().material.color = Color.red;
                }                
                cube.name += "_" + i + "|" + j;
                Cubes[i,j]=cube.GetComponent<Grid>();              
            }
        }
        WayFinding();
    }
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            StartCoroutine(Move());
        }
        if (Input.GetKeyDown(KeyCode.A))
        {
            SceneManager.LoadScene(0);
        }
        if (Input.GetKeyDown(KeyCode.Z))
        {
            zanTing = false;
        }
    }
    /// <summary>
    /// 用於協成裡的暫停標識位
    /// </summary>
    public bool zanTing;
    /// <summary>
    /// 走路效果實現//用盒子變黑模擬走路
    /// </summary>
    /// <returns></returns>
    IEnumerator Move()
    {
        while(stack.Count!=0)
        {
            stack.Pop().GetComponent<MeshRenderer>().material.color = Color.black;
            yield return new WaitForSeconds(0.2f);
        }      
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[System.Serializable]
public class Grid : MonoBehaviour, IComparable//用於該類(Grid)實現比較的方法:CompareTo
{
    //對當前盒子所在座標初始化 用於計算g,h,f的值
    private void Start()
    {
      x = (int)transform.position.x;
      y = (int)transform.position.z; 
    }
    /// <summary>
    /// 座標x
    /// </summary>
    public int x;
    /// <summary>
    /// 座標y
    /// </summary>
    public int y;

    /// <summary>
    /// F=G+H
    /// G=父格子的G+(當前位置在父格子的正【前後左右】+10否則+14)
    /// H=從起點到終點的水平距離加上豎直距離*10
    /// </summary>
    public int f;
    public int g;
    public int h;
    /// <summary>
    /// 盒子型別 預設路面=Normal
    /// </summary>
    public CubeType type= CubeType.Normal;
    /// <summary>

    /// 盒子的父盒子 就是從父親便利 發現的該盒子
    /// </summary>
    public Grid parent;
    /// <summary>
    /// 比較介面的實現
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public int CompareTo(object obj)
    {
        Grid grid = (Grid)obj;
        if (f < grid.f)
        {
            return -1;
        }
        else if (f == grid.f)
        {
            return 0;
        }
        else
            return 1;
    }
}

這個是可以斜著走路的版本 也就是勾選第一個選項的時候

下面 是不可以斜著走的的情況,也就是在遍歷周圍格子的時候不要吧斜著方向的盒子加入開啟列表 ;這裡路障產生機率較大 不是每次都有能到終點的路線