寻路算法 之 A*寻路

A*寻路是游戏设计使用比较频繁的寻路算法,主要原理是将平面划分为多个网格,来进行目标查找。


图例说明:

目标:

                                                            

                                           


开始搜索:


启发式搜索:启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无谓的搜索路径,提到了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。
估价函数:从当前节点移动到目标节点的预估费用;这个估计就是启发式的。在寻路问题和迷宫问题中,我们通常用曼哈顿(manhattan)估价函数预估费用。
A*算法的特点:A*算法在理论上是时间最优的,但是也有缺点:它的空间增长是指数级别的。(优化:二叉堆)

在A*寻路算法中,我们通过从点A开始,检查相邻方格的方式,向外扩展直到找到目标。


开启列表:待检查方格的集合列表,寻找周围可以达到的点,加入到此表中,并保存中心点为父节点
关闭列表:列表中保存不需要再次检查的方格


                                               


路径评分:

G-与起始点的距离
H-与目标点的距离
F的值是G和H的和。F,G和H的评分被写在每个方格里。
正如在紧挨起始格右侧的方格所表示的,F被打印中间,G在左上角,H则在右上角。

选择路径中经过哪个方格的关键是 这个等式:F = G + H


                                                                                    


                                                           

开始搜索:


                                                          


1,把起始格添加到开启列表。     


                                                               
2,寻找起点周围所有可到达或者可通过的方格,把他们加入开启列表。


                                                             
3,从开启列表中删除点A,把它加     入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。

                                                              
4,把当前格子从开启列表中删除,然后添 加到关闭列表中。


                                                            
5,检查所有相邻格子。跳过那些已经在关  闭列表中的或者不可通过的,把他们添加进开启列表,把选中的方格作为新的方格的父节点。

6,如果某个相邻格已经在开启列表里了,检查现在的这条路径G值是否会更低一些。如果新的G值更低,那就把相邻方格的父节点改为目前选中的方格,重新计算F和G的值。


起始格下方格子的父节点已经和前面不同的。之前它的G值是,并且指向右上方的格子。现在它的G值是,指向它上方的格子。这在寻路过程中的某处发生,当应用新路径时,G值经过检查变得低了-于是父节点被重新指定,G和F值被重新计算。


步骤总结:

1,把起始格添加到开启列表。
2,重复如下的工作:
a) 寻找开启列表中F值最低的格子。我们称它为当前格。
b) 把它切换到关闭列表。
c) 对相邻的格中的每一个
* 如果它不可通过或者已经在关闭列表中,略过它。反之如下。
* 如果它不在开启列表中,把它添加进去。把当前格作为这一格的父节点。记录
这一格的F,G,和H值。
* 如果它已经在开启列表中,用G值为参考检查新的路径是否更好。更低的G值
意味着更好的路径。如果是这样,就把这一格的父节点改成当前格,并且重新计算这一格的
G和F值。
d) 停止,当你
* 把目标格添加进了关闭列表,这时候路径被找到
* 没有找到目标格,开启列表已经空了。这时候,路径不存在。
3.保存路径。从目标格开始,沿着每一格的父节点移动直到回到起始格。

算法实现:

开启集合
关闭集合
添加起始点到开始集合中
循环如下步骤:
当前点=开启集合中最小F_Cost的点
将当前点移出开启集合中
将当前点添加到关闭集合中
如果当前点是目标点,结束查询
遍历当前点的每个相邻点
如果相邻点不能访问或者相邻点在关闭集合中,跳过此相邻点
如果新路径到相邻点的距离更短,或者相邻点不在开启集合中
重新设置F_Cost
重新设置其父节点为当前点
如果相邻点不在开启集合中
添加相邻点到开启集合中


具体代码:


Node 类:

	using UnityEngine;
	using System.Collections;
	
	public class Node {
	    //能否行走
	    public bool _canWalk;
	    //世界坐标
	    public Vector3 _worldPos;
	    //位置
	    public int _gridX, _gridY;
	    //与目标节点和开始节点的距离
	    public int gCost;
	    public int hCost;
		//总距离
	    public int fCost
	    {
	        get { return gCost + hCost; }
	    }
	    //父节点
	    public Node parent;
	
	    //构造函数
	    public Node(bool canwalk,Vector3 position,int x,int y)
	    {
	        _canWalk = canwalk;
	        _worldPos = position;
	        _gridX = x;
	        _gridY = y;
	    }
	}

Grid类:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Grid : MonoBehaviour
{

    //节点
    private Node[,] grid;

    public Vector2 gridSize;
    public float nodeRadius;
    private float nodeDiameter;
    public LayerMask whatLayer;
    //玩家
    public Transform player;


    public List<Node> path = new List<Node>();
    public int gridCntX, gridCntY;

    // Use this for initialization
    void Start()
    {
        //初始化数据
        nodeDiameter = nodeRadius * 2;
        gridCntX = Mathf.RoundToInt(gridSize.x / nodeDiameter);
        gridCntY = Mathf.RoundToInt(gridSize.y / nodeDiameter);
        grid = new Node[gridCntX, gridCntY];
        CreateGrid();

    }
    //创建grid 
    private void CreateGrid()
    {
        Vector3 startPoint = transform.position - gridSize.x / 2 * Vector3.right - Vector3
            .forward * gridSize.y / 2;

        for (int i = 0; i < gridCntX; i++)
            for (int j = 0; j < gridCntY; j++)
            {
                Vector3 worldPoint = startPoint + Vector3.right * (i * nodeDiameter + nodeRadius) + Vector3.forward * (j * nodeDiameter + nodeRadius);
                bool walkable = !Physics.CheckSphere(worldPoint, nodeRadius, whatLayer);
                grid[i, j] = new Node(walkable, worldPoint, i, j);
            }
        Vector3 endPos = transform.position - gridSize.x / 2 * Vector3.right;
    }

    // Update is called once per frame
    void Update()
    {
    }
    /*
          根据位置获取node节点   
   */
    public Node getFromPosition(Vector3 postition)
    {
        float percentX = (postition.x + gridSize.x / 2) / gridSize.x;
        float percentY = (postition.z + gridSize.y / 2) / gridSize.y;
        //确保在0和1之间
        percentX = Mathf.Clamp01(percentX);
        percentY = Mathf.Clamp01(percentY);
        int x = Mathf.RoundToInt((gridCntX - 1) * percentX);
        int y = Mathf.RoundToInt((gridCntY - 1) * percentY);

        return grid[x, y];
    }
    //调用函数,进行初始化
    void OnDrawGizmos()
    {
        Gizmos.DrawWireCube(transform.position, new Vector3(gridSize.x, 1, gridSize.y));
        if (grid == null)
            return;
        Node playerNode = getFromPosition(player.position);
        //遍历
        foreach (var node in grid)
        {
            Gizmos.color = node._canWalk ? Color.white : Color.red;
            Gizmos.DrawCube(node._worldPos, Vector3.one * (nodeDiameter - .1f));
        }
        //遍历路径
        if (path != null)
        {
            foreach (var node in path)
            {
                Gizmos.color = Color.black;
                Gizmos.DrawCube(node._worldPos, Vector3.one * (nodeDiameter - .1f));
            }
        }
        //绘制玩家颜色
        if (playerNode != null && playerNode._canWalk)
        {
            Gizmos.color = Color.cyan;
            Gizmos.DrawCube(playerNode._worldPos, Vector3.one * (nodeDiameter - .1f));
        }


    }

    /*
     * 
        获取节点周围的八个节点
     
     */
    public List<Node> getNeibourhood(Node node)
    {
        List<Node> neibourhood = new List<Node>();

        for (int i = -1; i <= 1; i++)
        {
            for (int j = -1; j <= 1; j++)
            {
                if (i == 0 && j == 0)
                    continue;
                int tempX = node._gridX + i;

                int tempY = node._gridY + j;
                if (tempX < gridCntX && tempX > 0 && tempY > 0 && tempY < gridCntY)
                {
                    neibourhood.Add(grid[tempX, tempY]);
                }
            }

        }
        return neibourhood;

    }
}

FindPath 类,也是核心类:


using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class FindPath : MonoBehaviour
{
    //添加玩家和目标点

    public Transform player, endPoint;
    private Grid _grid;
    // Use this for initialization
   //初始化grid
    void Start()
    {
        _grid = GetComponent<Grid>();
    }

    // Update is called once per frame
    //调用获取路径的函数
    void Update()
    {
        findingPath(player.position, endPoint.position);
    }
    //发现路径
    void findingPath(Vector3 startPos, Vector3 endPos)
    {
        //开始节点和结束节点
        Node startNode = _grid.getFromPosition(startPos);
        Node endNode = _grid.getFromPosition(endPos);
        //开启列表和关闭列表
        List<Node> openSet = new List<Node>();
        HashSet<Node> closeSet = new HashSet<Node>();
        //添加到开启列表
        openSet.Add(startNode);
        while (openSet.Count > 0)
        {
            //获取第一个几点
            Node currentNode = openSet[0];
            //遍历所有在开启列表中的节点
            for (int i = 0; i < openSet.Count; i++)
            {
                //判断是否满足条件
                if (openSet[i].fCost < currentNode.fCost || openSet[i].fCost == currentNode.fCost
                    && openSet[i].hCost < currentNode.hCost)
                {
                    currentNode = openSet[i];
                }
            }
            //从开启列表移除
            openSet.Remove(currentNode);
           //添加到关闭列表
            closeSet.Add(currentNode);
            //如果当前节点是目标节点,完成查找
            if (currentNode == endNode)
            {
                //获取路径
                generatePath(startNode,endNode);
                return;
            }
            //根据当前节点判断周围的节点
            foreach (var node in _grid.getNeibourhood(currentNode))
            {
                if (!node._canWalk || closeSet.Contains(node))
                    continue;

                int newCost = currentNode.gCost + getDistanceNodes(currentNode, node);
                //判断fCost,并更新fCost
                if (newCost < node.gCost || !openSet.Contains(node))
                {
                    node.gCost = newCost;
                    node.hCost = getDistanceNodes(node, endNode);
                    node.parent = currentNode;
                    if (!openSet.Contains(node))
                    {
                        openSet.Add(node);
                    }
                }
            }
        }
    }
    //生成路径
    private void generatePath(Node startNode, Node endNode)
    {
        List<Node> path = new List<Node>();

        Node temp = endNode;
        while (temp != startNode)
        {
            path.Add(temp);
            temp = temp.parent;
        }
        //翻转
        path.Reverse();
        _grid.path=path;
    }

    //获取距离
    int getDistanceNodes(Node a, Node b)
    {
        int cntX = Mathf.Abs(a._gridX - b._gridX);
        int cntY = Mathf.Abs(a._gridY - b._gridY);
        //判断横优先还是竖优先
        if (cntX > cntY)
        {
            return 14 * cntY + 10 * (cntX - cntY);
        }
        else
        {
            return 14 * cntX + 10 * (cntY - cntX);
        }
    }
}


资源下载地址:http://download.csdn.net/detail/u012251421/8341159




  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值