Unity A*寻路算法

看了下蛮牛教育的A*寻路视频后整理的代码,下面就直接贴代码了

首先是Node类的声明:定义一些节点所需的变量

using UnityEngine;
using System.Collections;

public class Node  
{
    //节点索引
    public int gridX, gridY;

    //节点所在位置
    public Vector3 worldPos;

    //节点是否可以行走
    public bool walkable;
    
    //评估参数: gConst-到起点的距离  hCost-到终点 的距离
    public int gCost;
    public int hCost;

    //评估和:越小则最优
    public int fConst
    {
        get { return gCost + hCost; }
    }

    //用来寻路结束后反向寻找路径节点
    public Node parent;

    public Node(bool walkable, Vector3 pos, int x, int y)
    {
        this.walkable = walkable;
        this.worldPos = pos;
        this.gridX = x;
        this.gridY = y;
    }
}


然后是Grid类,这是用来画节点的,以及存储节点信息的

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

public class Grid : MonoBehaviour 
{
    //平面的大小
    public Vector2 gridSize;

    //节点集合,会把平面划分成节点分布
    private Node[,] grid;

    //节点的半径
    public float nodeRadius;

    //节点的直径
    private float nodeDiameter;

    //障碍物所在的层级
    public LayerMask whatLayer;

    //根据平面的大小以及节点的半径可以算出平面上一行和一列的节点的个数
    public int gridCntX, gridCntY;

    public Transform player;

    //路径节点数列
    public List<Node> path = new List<Node>();

    void Start()
    {
        //直径可以根据半径得出
        nodeDiameter = nodeRadius * 2;

        //算出平面中节点的行数和列数
        gridCntX = Mathf.RoundToInt(gridSize.x / nodeDiameter);
        gridCntY = Mathf.RoundToInt(gridSize.y / nodeDiameter);

        //初始化节点集合
        grid = new Node[gridCntX, gridCntY];

        creatGrid();
    }

    void OnDrawGizmos()
    {
        //画边框
        Gizmos.DrawWireCube(transform.position, new Vector3(gridSize.x, 1, gridSize.y));

        if (grid == null)
            return;

        //把所有的节点画出来
        foreach (var node in grid)
        {
            Gizmos.color = node.walkable ? Color.white : Color.red;
            Gizmos.DrawCube(node.worldPos, Vector3.one * (nodeDiameter - 0.1f));
        }

        //画出路径
        if (path != null)
        {
            foreach (var node in path)
            {
                Gizmos.color = Color.black;
                Gizmos.DrawCube(node.worldPos, Vector3.one * (nodeDiameter - 0.1f));
            }
        }

        //标出玩家的位置
        Node playerNode = GetFromPostion(player.position);
        if (playerNode != null && playerNode.walkable)
        {
            Gizmos.color = Color.black;
            Gizmos.DrawCube(playerNode.worldPos, Vector3.one * (nodeDiameter - 0.1f));
        }
    }

    /// <summary>
    /// 创建节点
    /// </summary>
    private void creatGrid()
    {
        //首先计算出起始点位置
        Vector3 startPoint = transform.position - (gridSize.x / 2) * Vector3.right - (gridSize.y / 2) * Vector3.forward;

        for(int i =0;i<gridCntX;i++)
        {
            for(int j = 0;j<gridCntY;j++)
            {
                //获取每个节点的位置
                Vector3 worPos = startPoint + Vector3.right * (i * nodeDiameter + nodeRadius) + Vector3.forward * (j * nodeDiameter + nodeRadius);

                //检测该节点是否可以行走
                bool walkable = !Physics.CheckSphere(worPos, nodeRadius, whatLayer);

                //初始化每个节点
                grid[i, j] = new Node(walkable, worPos, i, j);
            }
        }
    }

    /// <summary>
    /// 根据position获取该位置的节点
    /// </summary>
    /// <param name="pos"></param>
    /// <returns></returns>
    public Node GetFromPostion(Vector3 pos)
    {
        //因为grid的中心点是原点(0,0,0),所以(pos.x + gridSize.x / 2)为相对grid的长度
        float percentX = (pos.x + gridSize.x / 2) / gridSize.x;
        float percentY = (pos.z + gridSize.y / 2) / gridSize.y;

        percentX = Mathf.Clamp01(percentX);
        percentY = Mathf.Clamp01(percentY);

        //总长度为gridCntX,因为x为索引,范围为0 - gridCntX-1,所以要gridCntX-1
        int x = Mathf.RoundToInt((gridCntX - 1) * percentX);
        int y = Mathf.RoundToInt((gridCntY - 1) * percentY);

        return grid[x, y];
    }

    /// <summary>
    /// 获取某个节点的相邻的节点
    /// </summary>
    /// <param name="node"></param>
    /// <returns></returns>
    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;

    void Start()
    {
        _grid = GetComponent<Grid>();
    }

    void Update()
    {
        findingPath(player.position, endPoint.position);
    }
	
    /// <summary>
    /// 寻找路径
    /// </summary>
    /// <param name="startPos"></param>
    /// <param name="endPos"></param>
    private void findingPath(Vector3 startPos, Vector3 endPos)
    {
        //获取起点和终点的节点信息
        Node startNode = _grid.GetFromPostion(startPos);
        Node endNode = _grid.GetFromPostion(endPos);

        //建立开启列表和关闭列表,并把起点加到开启列表里面
        List<Node> openList = new List<Node>();
        HashSet<Node> closeList = new HashSet<Node>();
        openList.Add(startNode);

        while (openList.Count > 0)
        {
            //首先获取到开启列表里面最优的节点
            Node currentNode = openList[0];
            for (int i = 0; i < openList.Count; i++)
            {
                if (openList[i].fConst < currentNode.fConst ||
                    openList[i].fConst == currentNode.fConst && openList[i].hCost < currentNode.hCost)
                {
                    currentNode = openList[i];
                }
            }

            //然后把该节点加入到关闭列表中
            openList.Remove(currentNode);
            closeList.Add(currentNode);

            //如果当前节点为终点说明寻路完成
            if (currentNode == endNode)
            {
                generatePath(startNode, endNode);
                return;
            }

            //刷新该节点附近一圈的节点的估值信息
            foreach(var node in _grid.GetNeibourhood(currentNode))
            {
                if (!node.walkable || closeList.Contains(node))
                    continue;

                int newCont = currentNode.gCost + getDistanceNodes(currentNode, node);
                if(newCont < node.gCost || !openList.Contains(node))
                {
                    node.gCost = newCont;
                    node.hCost = getDistanceNodes(node, endNode);
                    node.parent = currentNode;

                    if (!openList.Contains(node))
                        openList.Add(node);
                }
            }
        }
    }


    /// <summary>
    /// 获取两节点之间距离,即估价
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    private 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);
    }

    /// <summary>
    /// 当寻路完成后反向获取路径节点信息
    /// </summary>
    /// <param name="startNode"></param>
    /// <param name="endNode"></param>
    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;
    }
}

不过上面的算法只是初级的,没有做过优化,当行走范围大了之后,就会遇到性能瓶颈,运算量以几何增长

至于优化有二叉堆,有兴趣的人可以先自行研究,等我了解透了优化之后再继续更新。。。


下面附上工程文件地址:http://download.csdn.net/download/liujunjie612/9784183


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值