1. 主要思想:
首先,尋路即使尋點,一個一個正確的點走過去,便達成尋路的要求. 這裡,每個點都有3個屬性:F,G,H.H是當前點到達終點的預計消耗,可以用距離表示,但是這裡我們的計算方法是
/**now和end都是點,一個是當前點,一個是終點*因此,這裡計算出來的距離就是兩點的x差和y差的和*/H = Mathf.Abs(now.x - end.x) + Mathf.Abs(now.y - end.y);
而G表示的是起點到達當前點的消耗,可以用起點到當前點的距離來表示.但是計算的時候是一個點一個點的加過來,所以G的值在計算過程中可能會改變,因為父節點有可能會改變,我們在演算法中是這樣計算G的值的:
//now.Parent就是擷取到當前點的父節點G=Vector2.Distance(new Vector2(now.x,now.y),new Vector2(now.Parent.x,now.Parent.y))+now.Parent.G;
最後的F=G+H;
2. 建立Point類
此類表示點,也就之最基本的點,尋路時候的一個個的點.
using System.Collections;using System.Collections.Generic;using UnityEngine;public class Point{ public Point Parent { 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; Parent = parent; isWall = false; } /// <summary> /// 更新父節點、g和f、值 /// </summary> /// <param name="parent"></param> /// <param name="g"></param> public void UpdateParent(Point parent, float g) { Parent = parent; G = g; F = G + H; }} 3. 建立Astar類實現尋路
using System.Collections;using System.Collections.Generic;using UnityEngine;public class AStar : MonoBehaviour{ // Use this for initialization void Start() { InitMap(); Point start = map[2, 3]; Point end = map[6, 3]; FindPath(start, end); ShowPath(start, end); } private const int mapWith = 15; private const int mapHeigh = 15; private Point[,] map = new Point[mapWith, mapHeigh]; /// <summary> /// 構造地圖 /// </summary> public void InitMap() { for (int x = 0; x < mapWith; x++) { for (int y = 0; y < mapHeigh; y++) { map[x, y] = new Point(x, y); } } map[4, 2].isWall = true; map[4, 3].isWall = true; map[4, 4].isWall = true; } /// <summary> /// 核心尋路 /// </summary> /// <param name="star">開始點</param> /// <param name="end">結束點</param> public void FindPath(Point star, Point end) { List<Point> openList = new List<Point>(); List<Point> closeList = new List<Point>(); openList.Add(star); while (openList.Count > 0) { Point point = FindMinPointF(openList); openList.Remove(point); closeList.Add(point); List<Point> surrounPoints = GetSurroundPoints(point); PointSFilter(surrounPoints, closeList); //遍曆擷取到的所有點,並做相關處理 foreach (Point surroundPoint in surrounPoints) { if (openList.IndexOf(surroundPoint) > -1) {//如果該點已經在開始列表上,再來看是否要更新g值 float nowG = CalcG(surroundPoint, point); if (nowG < surroundPoint.G) { //如果新的g值小於原來的g值,就更新g值 surroundPoint.UpdateParent(point, nowG); } } else {//如果該店不再開始列表上,就添加該點到開始列表上,並做相關構造處理 surroundPoint.Parent = point; CalcF(surroundPoint, end); openList.Add(surroundPoint); } } if (openList.IndexOf(end) > -1) {//如果開啟列表裡面出現了終點,就結束迴圈 break; } } } /// <summary> /// 計算F值 /// </summary> /// <param name="now">當前點</param> /// <param name="end">終點</param> private void CalcF(Point now, Point end) { now.H = Mathf.Abs(now.x - end.x) + Mathf.Abs(now.y - end.y); if (now.Parent != null) { now.G = Vector2.Distance(new Vector2(now.x, now.y), new Vector2(now.Parent.x, now.Parent.y)) + now.Parent.G; } now.F = now.H + now.G; } /// <summary> /// 尋找開啟列表裡面F值最小的點 /// </summary> /// <param name="openList"></param> /// <returns></returns> private Point FindMinPointF(List<Point> openList) { float f = float.MaxValue; Point temp = null; foreach (Point point in openList) { if (point.F < f) { f = point.F; temp = point; } } return temp; } /// <summary> /// 擷取point點周圍的點 /// </summary> /// <param name="point"></param> /// <returns></returns> private List<Point> GetSurroundPoints(Point point) { Point up = null, down = null, left = null, right = null; Point lu = null, ru = null, ld = null, rd = null; if (point.y < mapHeigh - 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 (ld != null && ld.isWall == false && left.isWall == false && down.isWall == false) { list.Add(ld); } if (ru != null && ru.isWall == false && right.isWall == false && up.isWall == false) { list.Add(ru); } if (rd != null && rd.isWall == false && right.isWall == false && down.isWall == false) { list.Add(rd); } return list; } /// <summary> /// 過濾掉關閉列表上的點 /// </summary> /// <param name="now"></param> /// <param name="close"></param> private void PointSFilter(List<Point> now, List<Point> close) { foreach (Point point in close) { if (now.IndexOf(point) > -1) { now.Remove(point); } } } /// <summary> /// 計算now點的G值並返回 /// </summary> /// <param name="now"></param> /// <param name="parent"></param> /// <returns></returns> private float CalcG(Point now, Point parent) { return Vector2.Distance(new Vector2(now.x, now.y), new Vector2(parent.x, parent.y)) + parent.G; } /// <summary> /// 顯示路徑 /// </summary> /// <param name="star"></param> /// <param name="end"></param> public void ShowPath(Point star, Point end) { Point temp = end; //畫路徑 while (true) { Color c = Color.gray; if (temp == star) { c = Color.green; } else if (temp == end) { c = Color.red; } CreateCube(temp.x, temp.y, c); if (temp.Parent == null) break; temp = temp.Parent; } //畫障礙物 for (int x = 0; x < mapWith; x++) { for (int y = 0; y < mapHeigh; y++) { if (map[x, y].isWall) { CreateCube(x, y, Color.blue); } } } } /// <summary> /// 選擇地點建立正方體 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="color">正方體顏色</param> private void CreateCube(int x, int y, Color color) { print("CreateCube"); GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);//建立幾何體 go.transform.position = new Vector3(x, y, 0); go.GetComponent<Renderer>().material.color = color; }}
這裡較為重要是核心演算法,我也就重點拿出來看一下:首先是建立了兩個列表openList和closeList,就是用來存放需要檢查的點,和不需要檢查的點,比如出發點,一開始就加入到closeList關閉列表,然後通過FindMinPointF()擷取到其周圍可以走的點,經過篩選再加入到開啟列表等待檢查.遍曆擷取到的點進行檢,如果該點已經在開始列表上,再來看是否要更新g值,如果新的g值小於原來的g值,就更新g值,如果該點不再開始列表上,就添加該點到開始列表上,並做相關構造處理.如此反覆添加新的點進入開啟列表,範圍就會慢慢擴大,並且每個點都會有它的父節點,當終點進入開啟列表時就結束迴圈,同樣的終點也有父節點,父節點也有父節點,直到父節點為空白,也就是起點,這樣就出現了一條由終點指向起點的鏈表,根據這個方法就擷取到了最短路徑.
/// <summary> /// 核心尋路 /// </summary> /// <param name="star">開始點</param> /// <param name="end">結束點</param> public void FindPath(Point star, Point end) { List<Point> openList = new List<Point>(); List<Point> closeList = new List<Point>(); openList.Add(star); while (openList.Count > 0) { Point point = FindMinPointF(openList); openList.Remove(point); closeList.Add(point); List<Point> surrounPoints = GetSurroundPoints(point); PointSFilter(surrounPoints, closeList); //遍曆擷取到的所有點,並做相關處理 foreach (Point surroundPoint in surrounPoints) { if (openList.IndexOf(surroundPoint) > -1) {//如果該點已經在開始列表上,再來看是否要更新g值 float nowG = CalcG(surroundPoint, point); if (nowG < surroundPoint.G) { //如果新的g值小於原來的g值,就更新g值 surroundPoint.UpdateParent(point, nowG); } } else {//如果該店不再開始列表上,就添加該點到開始列表上,並做相關構造處理 surroundPoint.Parent = point; CalcF(surroundPoint, end); openList.Add(surroundPoint); } } if (openList.IndexOf(end) > -1) {//如果開啟列表裡面出現了終點,就結束迴圈 break; } } }