關於A*尋路在Unity3D裡面的簡單應用
阿新 • • 發佈:2019-02-10
這是之前做過的一個關於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
【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;
}
}
這個是可以斜著走路的版本 也就是勾選第一個選項的時候
下面 是不可以斜著走的的情況,也就是在遍歷周圍格子的時候不要吧斜著方向的盒子加入開啟列表 ;這裡路障產生機率較大 不是每次都有能到終點的路線