A*寻路算法的C#实现

详细讲述了A*寻路算法,有下列地方值得学习
1. 不要用AStarPathNode来构造PathFinder的Matrix,Matrix仅仅是byte[,],需要对Matrix中某个元素进行处理时再构造相应的AStarPathNode
2. 对每个Matrix中的元素取值不仅仅0和1,而是代表其权重,0表示障碍物,1表示平地,n表示高山,沼泽难以行走的地方,Weight越大越难以通过。节点G值会根据Weight进行计算。
     newG = parentNode.G + mGrid[newNode.X, newNode.Y];
3. 使用Closed List,用于判断是否达到寻路次数的上限。如果Closed List中的节点数达到某个数值(Search Limit),就停止寻路。
4. 设置A* 算法的参数,
    Search Limit :如果Closed List中的节点数达到某个数值(Search Limit),就停止寻路。
    Heuristic Formula:使用不同的算法来计算节点的H值,确省为Manhattan算法。
    Diagonals:是否处理对角线上的相邻节点
       如果Diagonals = false,就意味着找到的路径都是直线
     

     

     HeavyDiagonals:是否认为走对角线比走直线更远,表现在算法上就是:
       if(HeavyDiagonals)
           newNodeGValue = parentNode.G + Matrix[newNode.X, newNodeY]*2.41; //???为什么不是根号2~~1.41
       else
           newNodeGValue = parentNode.G + Matrix[newNode.X, newNodeY]
     
   
    Punish Change Direction:是否希望找到的路径看起来比较平滑,如果新节点和其父节点的走向与
父节点和父节点的父节点之间的走向不符,则增加新节点的G值。这里要注意newG += 20。//为什么是20???
    算法为:
     if (PunishChangeDirection)
           mHoriz = (parentNode.X - parentNode.PX);   //???似乎还应该加上mVert = (parentNode.Y - parentNode.PY)
     newNodeGValue = //计算GValue
      if (PunishChangeDirection)
      {
           if ((newNode.X - parentNode.X) != 0)
           {
               if (mHoriz == 0)
                    newG += 20;
           }
           if ((newNode.Y - parentNode.Y) != 0)
           {
                  if (mHoriz != 0)                                //???在这里使用mVert
                      newG += 20;
           }
       }
     

    Tie Breaker: 避免出现殊途同归,轻微的改变H值
   if (mTieBreaker)
{
      int dx1 = parentNode.X - end.X;
      int dy1 = parentNode.Y - end.Y;
      int dx2 = start.X - end.X;
      int dy2 = start.Y - end.Y;
      int cross = Math.Abs(dx1 * dy2 - dx2 * dy1);
      newNode.H = (int) (newNode.H + cross * 0.001);
   }
不同的组合在不同的环境下具有最好的性能
5. 对相邻节点的处理很巧妙
    用 direction = new sbyte[8,2]{ {0,-1} , {1,0}, {0,1}, {-1,0}, {1,-1}, {1,1}, {-1,1}, {-1,-1}}; 分别表示上,右,下,左,右上,右下,左下,左上这八个相邻Node的坐标x,y值的偏移。
    比如:current node坐标为x,y,这八个节点的坐标可以这样获得
    for(int i=0; i< 8 ; i ++)
    {
         newNodeX = x + direction[i,0];
         newNodeX = x + direction[i,1];
    }

6. 不使用普通的array list保存open node,否则,找出F值最小的open node会花去大量的时间,而是引入了Priority Queue作为open node的容器。把排序的复杂度分解在Push和Pop中,保证每次Pop出的都是最小的元素

7. 与Path Find无关。工作线程与UI的交互
public delegate void PathFinderDebugHandler(int fromX, int fromY, int x, int y, PathFinderNodeType type, int totalCost, int cost);

public class PathFinder : IPathFinder
{
    public event PathFinderDebugHandler PathFinderDebug;

}

//-------- Form
mPathFinder.PathFinderDebug += new PathFinderDebugHandler(PathFinderDebug);   //handle Event

//用delegate来进行跨线程调用
private delegate void PathFinderDebugDelegate(int parentX, int parentY, int x, int y, PathFinderNodeType type, int totalCost, int cost);

private void PathFinderDebug(int parentX, int parentY, int x, int y, PathFinderNodeType type, int totalCost, int cost)
{
    if (InvokeRequired)
    {
        Invoke(new PathFinderDebugDelegate(PathFinderDebug), new object[]{parentX, parentY, x, y, type, totalCost, cost});
        return;
    }

    PnlGUI.DrawDebug(parentX, parentY, x, y, type, totalCost, cost);
}

对本文我进行了下列修改:
1.在遍历当前节点的相邻节点时,需要看这个相邻节点是否已经在open list 或close list中,如果某个相邻节点已经在OpenList中,就要看如果把这个相邻节点的父节点修改为当前节点,是否会降低这个相邻节点的G值,原文中使用了for 循环来检查这个节点是否在list中
int foundInOpenIndex = -1;
for(int j=0; j<mOpen.Count; j++)
{
    if (mOpen[j].X == newNode.X && mOpen[j].Y == newNode.Y)
    {
        foundInOpenIndex = j;
        break;
    }
}
if (foundInOpenIndex != -1 && mOpen[foundInOpenIndex].G <= newG)
    continue;

int foundInCloseIndex = -1;
for(int j=0; j<mClose.Count; j++)
{
    if (mClose[j].X == newNode.X && mClose[j].Y == newNode.Y)
    {
        foundInCloseIndex = j;
        break;
    }
}
if (foundInCloseIndex != -1 && mClose[foundInCloseIndex].G <= newG)
    continue;


为了提高性能,我给保存open node的Queue中加入
private Dictionary<Point, int> pointDictionary = new Dictionary<Point, int>();
并使用Dictionary<Point, AStarNode> closedNodes 保存close node,
以便使用x,y坐标可以在open list或closed list中找到相应的Node

2.为了避免出现这种”穿墙”的情况,我加入了一个条件”CrossDiagonalBar”,


效果如下
  

还有一个问题有待解决
如果目标节点本身为障碍物,或是目标节点被障碍物完全包围,如何给出一条路径,使其尽量逼近目标节点?

 

源码下载请至置顶页~~

此为用C#写的A*算法源代码 using System; using System.Collections.Generic; using System.Text; using System.Drawing; namespace EtSoft.AStarLib { public class AStar { private StarNodeCollection openList = new StarNodeCollection(); private StarNodeCollection closeList = new StarNodeCollection(); public StarNode currentNode = null; #region 构造函数 /// <summary> /// 使用指定的地图对象、起点和终点初始化A星算法 /// </summary> /// <param name="map">地图对象</param> public AStar(Map map) { this.map = map; } /// <summary> /// /// </summary> /// <param name="map">地图对象</param> /// <param name="start">起点坐标</param> /// <param name="end">终点坐标</param> public AStar(Map map, Point start, Point end) : this(map) { this.start = new StarNode(start); this.end = new StarNode(end); openList.Add(new StarNode(start)); //AddStartNodeToOpenList(this.start); } /// <summary> /// /// </summary> /// <param name="map">地图对象</param> /// <param name="startX">起点X坐标</param> /// <param name="startY">起点Y坐标</param> /// <param name="endX">终点X坐标</param> /// <param name="endY">终点Y坐标</param> public AStar(Map map, int startX, int startY, int endX, int endY) : this(map, new Point(startX, startY), new Point(endX, endY)) { } #endregion #region 属性 protected Map map; /// <summary> /// 地图数据 /// </summary> public Map Map { get { return map; } set { map = value; } } private StarNode start = null; /// <summary> /// 起点坐标,如果没有设置起点,返回null /// </summary> public StarNode Start { get { return start; } set { start = value; openList.Clear(); openList.Add(start); //AddNodeToOpenList(start); } } private StarNode end = null; /// <summary> /// 终点坐标,如果没有设置终点,返回null /// </summary> public StarNode End { get { return end; } set { end = value; } } private StarNodeCollection path; /// <summary> /// 返回路径节点集合,没有路径则返回null /// </summary> public StarNodeCollection Path { get { return path; } } private bool allDirection = false; /// <summary> /// true,允许八方向寻路 /// </summary> public bool AllDirection { get { return allDirection; } set { allDirection = value; } } #endregion /// <summary> /// 开始寻路 /// </summary> public void StartSearchPath() { if (start == null) throw new InvalidNodeException(InvalidNodeTypes.NoStart); if (end == null) throw new InvalidNodeException(InvalidNodeTypes.NoEnd); path = null; openList.Clear(); closeList.Clear(); currentNode = start; SearchPath(currentNode); } //寻路递归,受保护的虚方法,允许在子类重写寻路算法 protected virtual void SearchPath(StarNode starNode) { //currentNode = starNode; openList.Remove(starNode); closeList.Add(starNode); AddNodeToOpenList(); if (closeList.Contains(end)) { //如果终点在关闭列表中,找到路径 StarNode node=starNode.Parent; path = new StarNodeCollection(); do { path.Add(node); node = node.Parent; } while (node != null && !node.Equals(start)); path.Reverse(); return; } else if (openList.Count == 0) { //终点不在关闭列表,开放列表已空,没有可通行的路径 return; } currentNode = GetNextNode(); //迭代过程 SearchPath(currentNode); } /// <summary> /// 获得当前节点的下一个最佳节点 /// </summary> /// <returns></returns> public StarNode GetNextNode() { openList.SortByF(); return openList[0]; } /// <summary> /// 把当前点周围可通过的点加入到开放列表中 /// </summary>
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值