1.引言
①:本文不提供跳点寻路算法基本概念,因为别的博主有很多写的非常好,看本文之前最好先去了解一下跳点寻路算法的基本概念与思路。
②:代码写的是很基础的语法,适合初学者,不包含高级写法,因为跟我一样的初学者看那些高级语法代码,简直头疼,云里雾里,所以写完整的C#代码只是为初学者有个基本的代码过程概念,想深入研究还得靠其他博主的文章。
2.代码
2.1 寻路类
//寻路类
class JpsSearch
{
//模拟地图 初定地图大小 10*20
//1:起点 9:终点 8:障碍 7:寻路的路经
byte[,] map = new byte[,]
{
{0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0 },
{0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0 },
{0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0 },
{0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0 },
{0,0,0,0,1,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0 },
{0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,8,0,0 },
{0,0,0,0,0,0,0,0,0,0,0,8,0,8,0,0,8,0,0,0 },
{0,0,0,0,0,0,0,0,0,0,0,8,0,8,0,0,8,9,8,0 },
{0,0,0,0,0,0,0,0,0,0,0,8,0,8,8,0,0,8,0,0 },
{0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0 },
};
//地图大小
int mapX = 10;
int mapY = 20;
//起点和终点
Point startPoint;
Point endPoint;
//开启表
List<Point> openList;
//封闭表
List<Point> closeList;
public void FindMain()
{
//定义起点
startPoint = new Point(4, 4);
startPoint.G = 0;
startPoint.H = 0;
//定义终点
endPoint = new Point(7, 17);
endPoint.G = 0;
endPoint.H = 0;
//开启表
openList = new List<Point>();
//封闭表
closeList = new List<Point>();
//openList 链表中第一个
Point firstPoint;
//跳点
Point jumpPoint;
//邻居节点
Point neighbourPoint;
//当前点的邻居点
List<Point> neighbourList = new List<Point>();
//方向
int xDIr, yDir;
//将起点加入开启表中
if(startPoint.x == endPoint.x && startPoint.y == endPoint.y )
{
startPoint = endPoint;
}
else
{
openList.Add(startPoint);
}
while(openList.Count >0)
{
//获取第总估计路程最少F的节点,默认以及从小到大排序
firstPoint = openList.First();
openList.RemoveAt(0);
//放入封闭表中
closeList.Add(firstPoint);
//获取当前节点的邻居节点
neighbourList = NeighboursList(firstPoint);
//遍历所有邻居节点
for (int i = 0; i < neighbourList.Count; i++)
{
//记录一个邻居节点
neighbourPoint = neighbourList[i];
//先检测当前邻居节点是否是终点
if(neighbourPoint.x == endPoint.x && neighbourPoint.y == endPoint.y)
{
endPoint.parentPoint = firstPoint;
openList.Clear();
break;
}
//方向
xDIr = Math_Dir(neighbourPoint.x - firstPoint.x);
yDir = Math_Dir(neighbourPoint.y - firstPoint.y);
//找到跳跃点
jumpPoint = SearchPoint(neighbourPoint.x,neighbourPoint.y,xDIr,yDir);
if(jumpPoint != null)
{
//检测当前条约点是否是终点
if (jumpPoint.x == endPoint.x && jumpPoint.y == endPoint.y)
{
//设置父节点
jumpPoint.parentPoint = firstPoint;
endPoint.parentPoint = jumpPoint;
//清楚链表是为了跳出while循环
openList.Clear();
break;
}
//再次检测是否找到终点
if(openList.Count > 0)
{
if(openList[0].x == endPoint.x && openList[0].y == endPoint.y)
{
jumpPoint.parentPoint = firstPoint;
endPoint.parentPoint = jumpPoint;
openList.Clear();
break;
}
}
if(!CheckInCloseList(jumpPoint))
{
//获取jumpPoint 的G H
float jumpG = Get_G(jumpPoint, firstPoint);
float jumpH = Get_H(jumpPoint);
jumpPoint.parentPoint = firstPoint;
jumpPoint.G = jumpG;
jumpPoint.H = jumpH;
if (CheckInOpneList(jumpPoint) && (jumpG + jumpH) < jumpPoint.Get_F())
{
// jumpPoint.parentPoint = firstPoint;
jumpPoint.G = jumpG;
jumpPoint.H = jumpH;
}
else
{
AddInOpenList(jumpPoint);
}
}
}
}
}
//地图绘制
DrawMap();
}
//获取传入节点的邻居节点,起点就是邻居八个节点,非起点就方向上的五个邻居节点
List<Point> NeighboursList(Point point)
{
//存储当前节点邻居节点 point 的邻居
List<Point> neighboursList = new List<Point>();
//当前传入节点 point 的父节点
Point parentPoint = point.parentPoint;
//如果父节点是空的,说明是起点
if(parentPoint == null)
{
//获取起点周围八个节点
for(int x = point.x - 1;x <= point.x + 1;x++)
{
for(int y = point.y-1;y<= point.y+1;y++)
{
//排除自身点
if(x== point.x && y== point.y)
{
continue;
}
//如果是可走节点,就存入邻居节点表中
if(IsCanWalk(x,y))
{
neighboursList.Add(new Point(x,y));
}
}
}
return neighboursList;
}
//非起点获取方向上可行走的邻居
//获取当前节点的前进方向 通过当前节点 - 父节点 计算
int xDir = Math_Dir(point.x - parentPoint.x);
int yDir = Math_Dir(point.y - parentPoint.y);
//斜方向行走
if (xDir != 0 && yDir != 0)
{
// point 点的四个方向是否可以行走 上下左右
bool upWalk = IsCanWalk(point.x, point.y + yDir);
bool downWalk = IsCanWalk(point.x, point.y - yDir);
bool leftWalk = IsCanWalk(point.x - xDir, point.y);
bool rightWalk = IsCanWalk(point.x + xDir, point.y);
//如果行驶方向的上方可以行走
if (upWalk)
{
//记录邻居点
neighboursList.Add(new Point(point.x, point.y + yDir));
//也记录强迫邻居点
if (!leftWalk && IsCanWalk(point.x - xDir, point.y + yDir))
{
neighboursList.Add(new Point(point.x - xDir, point.y + yDir));
}
}
//如果行驶方向的右方方可以行走
if (rightWalk)
{
//记录邻居点
neighboursList.Add(new Point(point.x + xDir, point.y));
//也记录强迫邻居点
if (!downWalk && IsCanWalk(point.x + xDir, point.y - yDir))
{
neighboursList.Add(new Point(point.x + xDir, point.y - yDir));
}
}
//检测正前方行驶方向
if (upWalk || rightWalk)
{
//如果正行驶方向可以行驶就记录节点
if (IsCanWalk(point.x + xDir, point.y + yDir))
{
neighboursList.Add(new Point(point.x + xDir, point.y + yDir));
}
}
}
//横向或纵向行驶
else
{
//横向
if (yDir == 0)
{
//如果正前方可以行驶
if (IsCanWalk(point.x + xDir, point.y))
{
neighboursList.Add(new Point(point.x + xDir, point.y));
//上下强迫邻居点
if (!IsCanWalk(point.x, point.y + 1) && IsCanWalk(point.x + xDir, point.y + 1))
{
neighboursList.Add(new Point(point.x + xDir, point.y + 1));
}
if (!IsCanWalk(point.x, point.y - 1) && IsCanWalk(point.x + xDir, point.y - 1))
{
neighboursList.Add(new Point(point.x + xDir, point.y - 1));
}
}
}
//纵向
else
{
//如果正上方可以行驶
if (IsCanWalk(point.x,point.y + yDir))
{
neighboursList.Add(new Point(point.x,point.y + yDir));
//左右强迫邻居点
if(!IsCanWalk(point.x + 1,point.y) && IsCanWalk(point.x + 1,point.y + yDir))
{
neighboursList.Add(new Point(point.x+1,point.y + yDir));
}
if(!IsCanWalk(point.x - 1,point.y) && IsCanWalk(point.x - 1,point.y + yDir))
{
neighboursList.Add(new Point(point.x - 1,point.y + yDir));
}
}
}
}
return neighboursList;
}
//找出的跳点
Point SearchPoint(int curX,int curY,int xDir,int yDir)
{
//检测当前节点可否行走
if (!IsCanWalk(curX, curY))
return null;
//检测是否达到终点
if(curX == endPoint.x && curY == endPoint.y)
{
//在最前面插入终点,方便结束循环、递归
openList.Insert(0,endPoint);
return new Point(curX,curY);
}
//对角方向递归检测
if(xDir !=0 && yDir !=0)
{
//检查当前节点是否有强迫邻居
if((IsCanWalk(curX+xDir,curY-yDir) && !IsCanWalk(curX,curY-yDir))
||(IsCanWalk(curX-xDir,curY+yDir) && !IsCanWalk(curX-xDir,curY)))
{
return new Point(curX,curY);
}
//继续横向递归查找强迫邻居
if(SearchPoint(curX+xDir,curY,xDir,0) != null)
{
return new Point(curX, curY);
}
//继续纵向递归查找强迫邻居
if (SearchPoint(curX,curY+yDir,0,yDir) != null)
{
return new Point(curX,curY);
}
}
//横向深度递归
else if (yDir != 0)
{
//深度递归查找当前方向是否有强迫邻居
if((IsCanWalk(curX +1,curY+yDir) && !IsCanWalk(curX+1,curY))
|| (IsCanWalk(curX-1,curY+yDir) && !IsCanWalk(curX-1,curY)))
{
return new Point(curX,curY);
}
}
//纵向向深度递归
else if (xDir != 0)
{
//深度递归查找当前方向是否有强迫邻居
if((IsCanWalk(curX+xDir,curY+1) && !IsCanWalk(curX,curY+1))
|| (IsCanWalk(curX+xDir,curY-1) && !IsCanWalk(curX,curY-1)))
{
return new Point(curX,curY);
}
}
//继续斜走递归
return SearchPoint(curX+xDir,curY+yDir,xDir,yDir);
}
//检测当前位置是否可走
bool IsCanWalk(int x,int y)
{
//检测坐标是否超过地图界限
if (x < 0 || x > mapX-1 || y < 0 || y > mapY-1)
return false;
return map[x, y] != 8;
}
//获取单轴行驶的方向
//clampNum 当前节点与父节点的一个轴差值
int Math_Dir(int clampNum)
{
//差值限制计算
if (clampNum <= -1)
return -1;
else if (clampNum >= 1)
return 1;
else
return 0;
}
//检测当前节点是否在闭合表中
bool CheckInCloseList(Point point)
{
Point tempPoint;
for(int i = 0;i<closeList.Count;i++)
{
tempPoint = closeList[i];
//是否在封闭表中
if (tempPoint.x == point.x && tempPoint.y == point.y)
return true;
}
return false;
}
//检测当前节点是否在开启表中
bool CheckInOpneList(Point point)
{
Point tempPoin;
for (int i = 0; i < openList.Count; i++)
{
tempPoin = closeList[i];
if (point.x == tempPoin.x && point.y == tempPoin.y)
return true;
}
return false;
}
//插入一个节点在开启表中,判断大小
void AddInOpenList(Point point)
{
Point tempPoint;
//检测原本是否包含相同位置的节点,有就去除
for(int i = 0;i<openList.Count;i++)
{
tempPoint = openList[i];
if (point.x == tempPoint.x && point.y == tempPoint.y)
{
openList.RemoveAt(i);
break;
}
}
//重新插入
for(int i = 0;i<openList.Count;i++)
{
tempPoint = openList[i];
if(tempPoint.Get_F() < tempPoint.Get_F())
{
openList.Insert(i,tempPoint);
return;
}
}
openList.Add(point);
}
//获取G
float Get_G(Point point,Point parentPoint)
{
if (parentPoint == null)
{
return 0;
}
//如果是平行关系
else if (point.x == parentPoint.x || point.y == parentPoint.y)
{
return parentPoint.G + (float)Math.Sqrt(Math.Pow(point.x - parentPoint.x, 2) + Math.Pow(point.y - parentPoint.y, 2));
}
//如果是对角关系
else
{
return parentPoint.G + (float)Math.Sqrt(Math.Pow(point.x - parentPoint.x, 2) + Math.Pow(point.y - parentPoint.y, 2));
}
}
//获取H
float Get_H(Point point)
{
return (float)Math.Sqrt(Math.Pow(point.x - endPoint.x, 2) + Math.Pow(point.y - endPoint.y, 2));
}
//地图绘制
void DrawMap()
{
Point tempPoint = endPoint.parentPoint;
Point parentPoint;
int xDir;
int yDir;
int maxNum;
while (tempPoint != null)
{
parentPoint = tempPoint.parentPoint;
if (parentPoint == null)
{
break;
}
xDir = Math_Dir(tempPoint.x - parentPoint.x);
yDir = Math_Dir(tempPoint.y - parentPoint.y);
DrawRow(tempPoint.x,tempPoint.y,parentPoint,xDir,yDir);
map[tempPoint.x, tempPoint.y] = 7;
tempPoint = tempPoint.parentPoint;
}
Console.WriteLine("地图绘制");
for (int i = 9; i >= 0; i--)
{
for (int j = 0; j <= 19; j++)
{
Console.Write(map[i, j] + " ");
}
Console.WriteLine();
}
}
//路线绘制
void DrawRow(int x,int y,Point parentPoint,int xDir,int yDir)
{
if(parentPoint == null)
{
return;
}
int curX = x - xDir;
int curY = y - yDir;
//如果绘制到达目的地
if(curX == parentPoint.x && curY == parentPoint.y)
{
return;
}
map[curX, curY] = 7;
DrawRow(curX, curY, parentPoint, xDir, yDir);
}
}
2.2节点类
//节点类
class Point
{
//坐标位置
public int x;
public int y;
//父节点
public Point parentPoint;
//寻路预算
public float G; //以及行驶的预算
public float H; //当前格子移动到目标点的预算
//构造函数 初始化位置
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
//获取估计总路程(G+H)
public float Get_F()
{
return G + H;
}
}
2.3 Main类
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
JpsSearch jpsSearch = new JpsSearch();
jpsSearch.FindMain();
Console.Read();
}
}
3运行结果
0:空地 1: 起点 7:路线 8:障碍物
4.结语
代码可能有点多和啰嗦,但是初学者跟着敲一遍可能会有很深刻的理解,不想敲的可以直接复制粘贴到编辑器上运行看看。
代码有很多不好的地方请谅解,代码如有错误,提出我纠正,大家的共同学习。