unity中的一个简单易用的A*寻路算法类

10 篇文章 0 订阅
6 篇文章 0 订阅

以前项目中用到了寻路,就写了个A*寻路的算法类,感觉还不错,这里分享出来。

这里有可运行的测试项目:https://github.com/LiuFeng1011/Test/tree/master/Assets/AStar


A*算法的原理网上有很多,这里只简单说一下我这个算法的逻辑:


*我的这个算法里面没有关闭列表,因为我会根据地图数据创建一个对应的节点数据的数组,每个节点数据记录自己当前的状态,是开启还是关闭的。节点数据只有在寻找周围点被找到的时候才会建立并放到数组中,这样在每次添加节点时,只要在数组中取出对应位置的节点数据,就能知道节点是开启的还是关闭的或者还没有被添加,不用去遍历open和close列表。

*开放列表直接使用List来保存,为了使列表保持有序,每次添加节点时直接把节点按照从小到大的顺序插入到对应位置。这里可以使用其它数据结构或者排序算法来进行优化。

*路径可以行进的方向使用一个数组来表示:

//附近的格子 4方向
    int[,] nearArray = new int[,]{
        {0, 1},
        {1, 0},
        {0, -1},
        {-1, 0},
    };
    //附近的格子 8方向
    int[,] nearArray= new int[,]{
        {0, 1},   
        {1, 1},   
        {1, 0},   
        {1, -1},  
        {0, -1},  
        {-1, -1}, 
        {-1, 0},  
        {-1, 1}   
    };

这里可以修改成任意个方向和距离,我只对1个距离以内的移动进行过测试。其它距离不能保证正确性。

*G和H值的计算可以使用曼哈顿距离或者欧式距离,曼哈顿距离效率上会更高。

    float GetNodeG(MapNode parent,MapNode node){
        //曼哈顿距离
        float dis = Mathf.Abs(parent.p.x - node.p.x) + Mathf.Abs(parent.p.y - node.p.y);
        //欧式距离
        //float dis = Vector2.Distance(parent.p, node.p);
        return parent.g + dis;
    }

    float GetNodeH( MapNode node)
    {
        //曼哈顿距离
        return Mathf.Abs(endPosition.x - node.p.x) + Mathf.Abs(endPosition.y - node.p.y);
        //欧式距离
        //return Vector2.Distance(endPosition,node.p);
    }

最终的结果只有在4方向时表现上会有些许不同:

   

左图为曼哈顿距离的结果,右图为欧式距离的结果,虽然移动距离是一样的,但是很明显欧式距离更符合我们的习惯。

*使用方式:

调用AStar类的StratAStar方法即可

    /// <summary>
    /// 开始寻路
    /// </summary>
    /// <returns>路径点数据,从起始点到结束点路径的有序vector数组.</returns>
    /// <param name="map">地图数据 二维数组,0为可移动路径,1为不可移动路径.</param>
    /// <param name="startPosition">开始位置.</param>
    /// <param name="endPosition">结束位置.</param>
    public List<Vector2> StratAStar(int[,] map,Vector2 startPosition,Vector2 endPosition)

*示例:

定义一个二维数据作为我们的地图数据:

int[,] map = 
    { 
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,1,1,1,1,1,1,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,1,0,0,0,0},
        {0,0,0,1,1,1,1,1,1,1,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    };

设置起始和结束位置:

        Vector2 startPosition = new Vector2(5,6);
        Vector2 endPosition = new Vector2(8, 12);

创建AStar类并调用开始方法:

        AStar astar = new AStar();
        List<Vector2> path = astar.StratAStar(map,startPosition,endPosition);

看下效果:



下面使用随机数组:

int[, ] map = new int[mapWidth,mapHight];

设定起始点和结束点,然后随机向数组中填充数据

        Vector2 startPosition = new Vector2(Random.Range(0, mapWidth),Random.Range(0, mapHight));
        Vector2 endPosition = new Vector2(Random.Range(0, mapWidth), Random.Range(0, mapHight));

        for (int i = 0; i < mapWidth;i ++){
            for (int j = 0; j < mapHight; j++)
            {
                //如果是起点或者终点 跳过
                if((i == (int)startPosition.x && j == (int)startPosition.y) ||
                   (i == (int)endPosition.x && j == (int)endPosition.x) ){
                    continue;
                }
                if (Random.Range(0f, 1f) < 0.2f){
                    map[i, j] = 1;
                }
            }
        }

看下效果:


完整的AStar类:

using System.Collections.Generic;
using UnityEngine;

public class AStar
{
    //节点
    private class MapNode{
        //节点状态 
        public enum enNodeState{
            normal,//待机
            open,//开启
            close //关闭
        }

        public MapNode parent = null;//父节点
        public float g, h, f;
        public Vector2 p;//位置
        public enNodeState state = enNodeState.normal;
    }

    //附近的格子 8方向
    int[,] nearArray= new int[,]{
        {0, 1},   
        {1, 1},   
        {1, 0},   
        {1, -1},  
        {0, -1},  
        {-1, -1}, 
        {-1, 0},  
        {-1, 1}   
    };
    //附近的格子 4方向
    //int[,] nearArray = new int[,]{
    //    {0, 1},
    //    {1, 0},
    //    {0, -1},
    //    {-1, 0},
    //};
    Vector2 startPosition, endPosition;//起始点和结束点

    //开放列表,在插入时根据MapNode的f值进行排序,即优先队列
    List<MapNode> openList = new List<MapNode>();

    //所有点
    MapNode[,] mapList;

    //向开放列表中加入节点,这里需要进行排序
    void PushNode(MapNode node)
    {
        node.state = MapNode.enNodeState.open;
        for (int i = 0; i < openList.Count; i++){
            if (openList[i].f > node.f){
                openList.Insert(i,node);
                return;
            }
        }
        openList.Add(node);
    }

    //创建节点时即对节点进行估价
    MapNode CreateNode(Vector2 p,MapNode parent){
        MapNode node = new MapNode();
        node.parent = parent;
        node.p = p;

        //f = g+h
        //g和h直接使用曼哈顿距离
        //--------g------
        if(parent != null){
            node.g = GetNodeG(parent,node);
        }else {
            node.g = 0;
        }

        //--------h------
        node.h =GetNodeH(node);

        //--------f------
        node.f = node.g + node.h;

        //创建的节点加入到节点列表中
        mapList[(int)node.p.x,(int)node.p.y] = node;

        return node;
    }

    float GetNodeG(MapNode parent,MapNode node){
        //曼哈顿距离
        //float dis = Mathf.Abs(parent.p.x - node.p.x) + Mathf.Abs(parent.p.y - node.p.y);
        //欧式距离
        float dis = Vector2.Distance(parent.p, node.p);
        return parent.g + dis;
    }

    float GetNodeH( MapNode node)
    {
        //曼哈顿距离
        //return Mathf.Abs(endPosition.x - node.p.x) + Mathf.Abs(endPosition.y - node.p.y);
        //欧式距离
        return Vector2.Distance(endPosition,node.p);
    }

    /// <summary>
    /// 开始寻路
    /// </summary>
    /// <returns>路径点数据,从起始点到结束点路径的有序vector数组.</returns>
    /// <param name="map">地图数据 二维数组,0为可移动路径,1为不可移动路径.</param>
    /// <param name="startPosition">开始位置.</param>
    /// <param name="endPosition">结束位置.</param>
    public List<Vector2> StratAStar(int[,] map,Vector2 startPosition,Vector2 endPosition){

        mapList = new MapNode[map.GetLength(0),map.GetLength(1)];

        //附近可移动点的数量
        int nearcount = nearArray.GetLength(0);

        this.startPosition = startPosition;
        this.endPosition = endPosition;

        //起始点加入开启列表
        MapNode startNode = CreateNode(startPosition,null);
        PushNode(startNode);

        MapNode endNode = null;//目标节点

        //开始寻找路径
        while(openList.Count > 0){
            //取出开启列表中f值最低的节点,由于我们在向开启列表中添加节点时已经进行了排序,所以这里直接取第0个值即可
            MapNode node = openList[0];
            //如果node为目标点则结束寻找
            if(node.p.x == endPosition.x && node.p.y == endPosition.y){
                endNode = node;
                break;
            }

            //设置为关闭状态并从开启列表中移除
            node.state = MapNode.enNodeState.close;
            openList.RemoveAt(0);

            //当前点坐标
            //相邻格子加入到开启列表
            for (int i = 0; i < nearcount; i ++){
                Vector2 nearPosition = node.p - new Vector2(nearArray[i,0], nearArray[i,1]);

                //位置是否超出范围
                if((nearPosition.x < 0 || nearPosition.x >= map.GetLength(0)) ||
                   (nearPosition.y < 0 || nearPosition.y >= map.GetLength(1))){
                    continue;
                }
                //该位置是否可以移动
                if(map[(int)nearPosition.x,(int)nearPosition.y] != 0){
                    continue;
                }

                //是否已经创建过这个点
                MapNode nearNode = mapList[(int)nearPosition.x, (int)nearPosition.y];
                if(nearNode != null){
                    //该节点已经创建过

                    //节点是否关闭
                    if(nearNode.state == MapNode.enNodeState.close){
                        continue;
                    }

                    //重新计算g
                    float newg = GetNodeG(node, nearNode);
                    if(newg < nearNode.g){
                        nearNode.parent = node;
                        nearNode.g = newg;
                        nearNode.f = nearNode.g + nearNode.h;

                        Debug.Log("==" + openList.Count);
                        //重新对开放列表排序
                        openList.Remove(nearNode);
                        Debug.Log(openList.Count);
                    }else{
                        continue;
                    }
                }else{
                    //创建节点
                    nearNode = CreateNode(nearPosition, node);
                }

                PushNode(nearNode);
            }
        }

        //路径数据
        List<Vector2> ret = new List<Vector2>();

        if (endNode == null)
        {
            Debug.Log("no path!");
            return ret;
        }

        //将路径保存到数组中
        while(endNode.parent != null){
            ret.Insert(0,endNode.p);
            endNode = endNode.parent;
        }

        return ret;
    }
}

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会尝试回答你的问题。 首先,要使用Unity脚本实现A*寻路算法,需要在Unity创建一个脚本,并将其添加到场景的游戏对象上。 以下是实现A*寻路算法Unity脚本示例代码: ```csharp using UnityEngine; using System.Collections; using System.Collections.Generic; public class AStarPathfinding : MonoBehaviour { public Transform seeker, target; //起点和终点 Grid grid; //寻路所需的网格 void Awake() { grid = GetComponent<Grid>(); } void Update() { FindPath(seeker.position, target.position); } void FindPath(Vector3 startPos, Vector3 targetPos) { Node startNode = grid.NodeFromWorldPoint(startPos); Node targetNode = grid.NodeFromWorldPoint(targetPos); List<Node> openSet = new List<Node>(); HashSet<Node> closedSet = new HashSet<Node>(); openSet.Add(startNode); while (openSet.Count > 0) { Node currentNode = openSet[0]; for (int i = 1; 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); closedSet.Add(currentNode); if (currentNode == targetNode) { RetracePath(startNode, targetNode); return; } foreach (Node neighbour in grid.GetNeighbours(currentNode)) { if (!neighbour.walkable || closedSet.Contains(neighbour)) { continue; } int newMovementCostToNeighbour = currentNode.gCost + GetDistance(currentNode, neighbour); if (newMovementCostToNeighbour < neighbour.gCost || !openSet.Contains(neighbour)) { neighbour.gCost = newMovementCostToNeighbour; neighbour.hCost = GetDistance(neighbour, targetNode); neighbour.parent = currentNode; if (!openSet.Contains(neighbour)) { openSet.Add(neighbour); } } } } } void RetracePath(Node startNode, Node endNode) { List<Node> path = new List<Node>(); Node currentNode = endNode; while (currentNode != startNode) { path.Add(currentNode); currentNode = currentNode.parent; } path.Reverse(); grid.path = path; } int GetDistance(Node nodeA, Node nodeB) { int dstX = Mathf.Abs(nodeA.gridX - nodeB.gridX); int dstY = Mathf.Abs(nodeA.gridY - nodeB.gridY); if (dstX > dstY) { return 14 * dstY + 10 * (dstX - dstY); } return 14 * dstX + 10 * (dstY - dstX); } } ``` 该脚本的A*寻路算法会在每次Update()函数调用时寻找从起点到终点的最短路径,并将其保存在网格的路径。 实现A*寻路算法需要一个网格,该网格由一系列节点组成。每个节点包含了该节点在网格的位置、该节点到起点的距离(gCost)、

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值