2D獵寶行動(類掃雷小遊戲)DAY 7
阿新 • • 發佈:2018-11-06
1.建立AStar尋路演算法所需要的資料結構類
新建AstarStructure類用來寫AStar演算法的結構體
public class Point { public int x, y; public Point(int x,int y) { this.x = x; this.y = y; } public new bool Equals(object obj) { return (obj != null) && (obj is Point) && (((Point)obj).x == x && ((Point)obj).y == y); } } public class PointData { public Point point; public double g, h; public PointData parent; public PointData(Point point,double g,double h,PointData parent) { this.point = point; this.g = g; this.h = h; this.parent = parent; } public double F() { return g + h; } }
2.設計尋路所需的地圖的形式及挑選H函式
1、首先將起始點新增進“開啟列表”。
2、重複如下步驟:
a) 尋找開啟列表中F值最低的節點。我們稱其為“當前節點”。
b) 把它從“開啟列表”中移除,並新增進“關閉列表”。
c) 檢查“當前節點”是否是“目標節點”
* 如果是,停止搜尋,跳到第 3 步;
* 如果不是,繼續下面步驟;
d) 尋找“當前節點”鄰近的節點
* 如果它不可通過或者已經在關閉列表中,略過它。反之如下。
* 如果它不在開啟列表中,把它新增進開啟列表。把當前節點作為這一節點的父節點。記錄這一格的F,G和H值。
* 如果它已經在開啟列表中,檢查新路徑對它G值的產生的影響
a. 如果它的G值因為新路徑變大,那麼保持原來的狀態,不作任何改變;
b. 如果G值變小,說明新路徑更好,將其父結點改為“當前結點”,更新G和F值。
e) 檢查列表是否為空,如果為空,說明路徑未找到,直接返回,不繼續任何步驟。
3、儲存路徑。從目標節點開始,沿著每一節點的父節點移動直到回到起始節點。這就是我們要找的路徑。
根據上述,來寫AStar的尋路演算法。
using System;
public class AStarPathfinding
{
// 1、首先將起始點新增進“開啟列表”。
// 2、重複如下步驟:
// a) 尋找開啟列表中F值最低的節點。我們稱其為“當前節點”。
// b) 把它從“開啟列表”中移除,並新增進“關閉列表”。
// c) 檢查“當前節點”是否是“目標節點”
// * 如果是,停止搜尋,跳到第 3 步;
// * 如果不是,繼續下面步驟;
// d) 尋找“當前節點”鄰近的節點
// * 如果它不可通過或者已經在關閉列表中,略過它。反之如下。
// * 如果它不在開啟列表中,把它新增進開啟列表。把當前節點作為這一節點的父節點。記錄這一格的F,G和H值。
// * 如果它已經在開啟列表中,檢查新路徑對它G值的產生的影響
// a.如果它的G值因為新路徑變大,那麼保持原來的狀態,不作任何改變;
// b.如果G值變小,說明新路徑更好,將其父結點改為“當前結點”,更新G和F值。
// e) 檢查列表是否為空,如果為空,說明路徑未找到,直接返回,不繼續任何步驟。
// 3、儲存路徑。從目標節點開始,沿著每一節點的父節點移動直到回到起始節點。這就是我們要找的路徑。
//地圖元素
private const char START = 'S'; //起點
private const char END = 'E'; //終點
private const char SPACE = '.'; //空地
private const char WALL = 'W'; //牆
private const char VISTIED = '-'; //被訪問過
private const char ON_PATH = '@'; //在結果路徑上
//地圖字串
public static char[,] MAP = null;
//地圖最大尺寸
public static Point MAX_PNT = null;
//起點
public static Point START_PNT = null;
//終點
public static Point END_PNT = null;
//TODO 翻譯地圖的方法
/// <summary>
/// 曼哈頓距離,小於等於實際值
/// </summary>
/// <param name="pnt">當前評估點</param>
/// <returns>按照曼哈頓距離所評估的H值</returns>
private static double HManhattanDistance(Point pnt)
{
return Math.Abs(pnt.x - END_PNT.x) + Math.Abs(pnt.y - END_PNT.y);
}
/// <summary>
/// 歐式距離距離,小於等於實際值
/// </summary>
/// <param name="pnt">當前評估點</param>
/// <returns>按照歐式距離所評估的H值</returns>
private static double HEuclidianDistance(Point pnt)
{
return Math.Sqrt(Math.Pow(pnt.x - END_PNT.x, 2) + Math.Pow(pnt.y - END_PNT.y, 2));
}
/// <summary>
/// 歐式距離平方,大於等於實際值
/// </summary>
/// <param name="pnt">當前評估點</param>
/// <returns>按照歐式距離平方所評估的H值</returns>
private static double HPowEuclidianDistance(Point pnt)
{
return Math.Pow(pnt.x - END_PNT.x, 2) + Math.Pow(pnt.y - END_PNT.y, 2);
}
/// <summary>
/// H函式
/// </summary>
/// <param name="pnt">當前評估點</param>
/// <returns>按照指定方法評估的H值</returns>
private static double HFun(Point pnt)
{
return HManhattanDistance(pnt);
}
/// <summary>
/// 尋路函式
/// </summary>
private void Search()
{
}
}
3.編寫AStar尋路演算法(上)
/// <summary>
/// 尋路函式
/// </summary>
private void Search()
{
//用List集合做“開啟列表”來記錄擴充套件的點
List<PointData> openList = new List<PointData>();
//八個擴充套件方向
int[,] directs = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, { 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 } };
//把起始點放入堆
openList.Add(new PointData(START_PNT, 0, 0, null));
//找到最後一個點的資料,用來反推路徑
PointData endPoint = null;
//找到終點或“開啟列表”為空時退出迴圈
for(bool finish = false; !finish && openList.Count > 0;)
{
//取出f值最小的點
openList.Sort((x, y) => { return x.F().CompareTo(y.F()); });
PointData data = openList[0];
openList.RemoveAt(0);
Point point = data.point;
//將取出的點標記為已訪問點
if(MAP[point.x,point.y] == SPACE)
{
MAP[point.x, point.y] = VISTIED;
}
//遍歷八個方向的點,Rank返回維數
for (int i = 0; i < directs.Rank; i++)
{
Point newPoint = new Point(point.x + directs[i, 0], point.y + directs[i, 1]);
if(newPoint.x >= 0 && newPoint.x <= MAX_PNT.x && newPoint.y >= 0 && newPoint.y < MAX_PNT.y)
{
//如果是終點,則跳出迴圈,不用再找
char e = MAP[newPoint.x, newPoint.y];
if(e == END)
{
endPoint = data;
finish = true;
break;
}
//如果不是空地,就不需要擴充套件
if (e != SPACE)
{
continue;
}
//如果在“開啟列表”裡,則更新g值
PointData tempData = openList.Find((x) => { return x.point.Equals(newPoint); });
if(tempData != null)
{
//更新G值
}
}
}
}
}
4.編寫AStar尋路演算法(下)
/// <summary>
/// 尋路函式
/// </summary>
private void Search()
{
//用List集合做“開啟列表”來記錄擴充套件的點
List<PointData> openList = new List<PointData>();
//八個擴充套件方向
int[,] directs = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, { 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 } };
//把起始點放入堆
openList.Add(new PointData(START_PNT, 0, 0, null));
//找到最後一個點的資料,用來反推路徑
PointData endData = null;
//找到終點或“開啟列表”為空時退出迴圈
for(bool finish = false; !finish && openList.Count > 0;)
{
//取出f值最小的點
openList.Sort((x, y) => { return x.F().CompareTo(y.F()); });
PointData data = openList[0];
openList.RemoveAt(0);
Point point = data.point;
//將取出的點標記為已訪問點
if(MAP[point.x,point.y] == SPACE)
{
MAP[point.x, point.y] = VISTIED;
}
//遍歷八個方向的點,Rank返回維數
for (int i = 0; i < directs.Rank; i++)
{
Point newPoint = new Point(point.x + directs[i, 0], point.y + directs[i, 1]);
if(newPoint.x >= 0 && newPoint.x <= MAX_PNT.x && newPoint.y >= 0 && newPoint.y < MAX_PNT.y)
{
//如果是終點,則跳出迴圈,不用再找
char e = MAP[newPoint.x, newPoint.y];
if(e == END)
{
endData = data;
finish = true;
break;
}
//如果不是空地,就不需要擴充套件
if (e != SPACE)
{
continue;
}
//如果在“開啟列表”裡,則更新g值
PointData tempData = openList.Find((x) => { return x.point.Equals(newPoint); });
if(tempData != null)
{
//更新G值
float goffset;
if (Math.Abs(directs[i, 0]) + Math.Abs(directs[i, 1]) > 1)
{
goffset = 1.4f;
}
else
{
goffset = 1.0f;
}
if(tempData.g > data.g + goffset)
{
tempData.g = data.g + goffset;
tempData.parent = data;
}
}
//如果不在“開啟列表”裡,則放入“開啟列表”中,並計算g,h值
else
{
float goffset;
if (Math.Abs(directs[i, 0]) + Math.Abs(directs[i, 1]) > 1)
{
goffset = 1.4f;
}
else
{
goffset = 1.0f;
}
double h = HFun(newPoint);
PointData newData = new PointData(newPoint, data.g + goffset, h, data);
openList.Add(newData);
}
}
}
}
//反向找出路徑
for(PointData pathData = endData;pathData != null;)
{
Point point = pathData.point;
if (MAP[point.x, point.y] == VISTIED)
{
MAP[point.x, point.y] = ON_PATH;
}
pathData = pathData.parent;
}
}
5.將遊戲地圖翻譯為尋路地圖
//翻譯地圖的方法
private static bool GenerateMap(Point s,Point e)
{
if (MAP[END_PNT.x, END_PNT.y] == WALL)
{
return false;
}
if (s.Equals(e))
{
return false;
}
MAX_PNT = new Point(GameManager._instance.w, GameManager._instance.h);
START_PNT = s;
END_PNT = e;
MAP = new char[MAX_PNT.x, MAX_PNT.y];
for(int i = 0; i < MAX_PNT.x; i++)
{
for(int j = 0; j < MAX_PNT.y; j++)
{
if(GameManager._instance.mapArray[i,j].elementContent == ElementContent.Door||
GameManager._instance.mapArray[i, j].elementContent == ElementContent.Enemy ||
GameManager._instance.mapArray[i, j].elementContent == ElementContent.BigWall ||
GameManager._instance.mapArray[i, j].elementContent == ElementContent.SmallWall ||
GameManager._instance.mapArray[i, j].elementState == ElementState.Marked ||
(GameManager._instance.mapArray[i, j].elementContent == ElementContent.Trap && GameManager._instance.mapArray[i,j].elementState == ElementState.Uncovered))
{
MAP[i, j] = WALL;
}
else if(GameManager._instance.mapArray[i,j].elementState == ElementState.Uncovered ||
(GameManager._instance.mapArray[i,j].elementContent == ElementContent.Tool && ((ToolElement)GameManager._instance.mapArray[i,j]).isHide == false) ||
(GameManager._instance.mapArray[i, j].elementContent == ElementContent.Gold && ((GoldElement)GameManager._instance.mapArray[i, j]).isHide == false))
{
MAP[i, j] = SPACE0;
}
else
{
MAP[i, j] = SPACE1;
}
}
}
MAP[START_PNT.x, START_PNT.y] = START;
MAP[END_PNT.x, END_PNT.y] = END;
return true;
}