A*寻路来解决什么问题?

A*寻路来计算玩家行进最短路径

A*基本原理

不停的找周围的点,选出一个点作为起点,再循环的找,知道找到终点

A*详细原理

1.寻路消耗公式
f(寻路消耗)=g(离起点距离 分为两种 上下左右为1 其他四个角为1.4 根号2)+ h(离终点距离 使用曼哈顿街区的思想)
2.开启列表(所有点的存放)
3.关闭列表(最优点的存放)
每次判断最优点是否为终点 如果是 停止寻路,否则继续寻路
4.格子对象的父对象(用来确定最终的路径)

需求分析

需要一个格子类 寻路管理器(管理所有的格子)
开启列表 关闭列表

格子类
成员变量:
寻路消耗 f g h
父亲格子father
坐标 x y
格子类型 type

方法:
构造函数

寻路管理器类
成员变量:
格子数组 nodes
开启列表 openlist
关闭列表 clostlist
地图的边界 mapx mapy

方法:
初始化地图格子方法
寻找路径方法(起点,终点)

源码

网格类

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

public enum E_node_type
{
    walk,//可以走
    stop//不能走
}

//A*格子类
public class AStarNode 
{
    public float f;//寻路消耗
    public float g;//距离起点距离
    public float h;//距离终点距离

    public AStarNode father;//父对象

    //格子对象坐标
    public int x;
    public int y;

    public E_node_type type;

    public AStarNode(int x,int y ,E_node_type type)
    {
        //传入坐标和类型
        this.x = x;
        this.y = y;
        this.type = type;
    }

}

网格管理器类

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

//管理器
public class AStarManager 
{
    private static AStarManager Instance;
    public static AStarManager instance
    {
        get
        {
            if(Instance == null){
                Instance = new AStarManager();
            }
            return Instance;
        }
    }

    private AStarNode[,] nodes;//地图相关对象容器
    private List<AStarNode> openList;//开启列表
    private List<AStarNode> closeList;//关闭列表

    private int mapx;
    private int mapy;
    //宽高

    public void initmapinfo(int x,int y)
    {
        //初始化地图信息
        //根据宽高 创建格子
        this.mapx = x;
        this.mapy = y;

        nodes = new AStarNode[x,y];//声明容器装多少个格子
        for(int i = 0; i < x; i++)
        {
            for(int j = 0; i < y; j++)
            {
                AStarNode node = new AStarNode(i, j, Random.Range(0, 100) < 20 ? E_node_type.stop : E_node_type.walk);
                //随机阻挡
            }
        }

    }
    //寻路方法
    public List<AStarNode> findpath(Vector2 startpos,Vector2 endpos)
    {
        //首先判断传入点是否合法 (不是阻挡点)
        //实际开发需要转换坐标
        if (startpos.x < 0 || startpos.x >= mapx || startpos.y < 0 || startpos.y > mapy||
            endpos.x < 0 || endpos.x >= mapx || endpos.y < 0 || endpos.y > mapy)
        {
            Debug.LogError("由于在地图边界外");
            return null;//不合法
        }
        
        AStarNode start = nodes[(int)startpos.x, (int)startpos.y];
        AStarNode end = nodes[(int)endpos.x, (int)endpos.y];
        if (start.type == E_node_type.stop || end.type == E_node_type.stop)
        {
            Debug.LogError("该点是阻挡点");
            return null;//不合法
        }
        openList.Clear();
        closeList.Clear();//清空数据 防止影响

        //把开始点初始化之后放入关闭列表中
        start.father = null;
        start.f = 0;
        start.g = 0;
        start.h = 0;

        closeList.Add(start);

        while (true)
        {
            //寻找周围的所有点
            FindnearNodeToopenList(start.x - 1, start.y - 1, 1.4f, start, end);
            FindnearNodeToopenList(start.x - 1, start.y, 1, start, end);
            FindnearNodeToopenList(start.x - 1, start.y + 1, 1.4f, start, end);

            FindnearNodeToopenList(start.x, start.y - 1, 1f, start, end);
            FindnearNodeToopenList(start.x, start.y + 1, 1f, start, end);

            FindnearNodeToopenList(start.x + 1, start.y - 1, 1.4f, start, end);
            FindnearNodeToopenList(start.x + 1, start.y, 1f, start, end);
            FindnearNodeToopenList(start.x + 1, start.y - 1, 1.4f, start, end);

            if (openList.Count == 0)//开始列表为空 还没有找到终点
            {
                Debug.LogError("死路!");
                return null;
            }


            //开启列表放入上边的八个点
            openList.Sort(sortopenlist);//自定义排序

            //放入关闭列表
            closeList.Add(openList[0]);

            start = openList[0];//找到的这个点变成新的起点 进行新的计算
            openList.RemoveAt(0);//移除

            if (start == end)
            {
                //寻路结束
                List<AStarNode> path = new List<AStarNode>();
                path.Add(end);
                while (end.father != null)
                {
                    path.Add(end.father);
                    end = end.father;
                }
                //翻转一下
                path.Reverse();
                return path;//找到路径
            }
        }

    }
    private int sortopenlist(AStarNode a,AStarNode b)//自定义排序
    {
        if (a.f > b.f)
        {
            return 1;
        }
        else
        {
            return -1;
        }
    }

    //把邻近的点放入开启列表中去
    private void FindnearNodeToopenList(int x,int y,float g,AStarNode father,AStarNode end)
    {
        //边间检测
        if (x < 0 || x >= mapx || y < 0 || y >= mapy)
        {
            return;
        }

        AStarNode node = nodes[x, y];
        //判断是否是边界,阻挡
        if (node == null||node.type==E_node_type.stop||openList.Contains(node)||closeList.Contains(node))
        {
            return;
        }

        //计算f值 f=g+h=起点距离+终点距离
        node.father = father;//记录父对象
        node.g = father.g + g;//父亲离起点距离 + 我离父亲距离=我离起点的距离
        node.h = Mathf.Abs(end.x - node.x) + Mathf.Abs(end.y - node.y);//曼哈顿街区算法
        node.f = node.g + node.h;


        openList.Add(node);//存入开启了列表中

    }
}

之后的测试实例我还会更新的 希望以上所写对大家有所帮助

2021.3.11更新

在上一场面试中 我和面试官聊到了很多A*算法相关的问题
其中有一个 面试官问 如果地图中有两种地形,一种是草地,一种是沙地
然后有一个角色优先考虑走沙地 问我怎么实现

我当时很快想到的就是 在A*算法中每个格子设置一个值 就作为草地和沙地的权重
优先选择权重较大的 比如设置草地为1 沙地为2 优先选择沙地

然后面试官还问了我一个问题:如何优化A*算法中角色移动的轨迹(使得路径尽量保持类似直线)
当时我没想到好的方法 只想到了将格子扩大 这样可以稍微优化
但是很明显这不是面试官想要的结果
然后面试结束之后 我回想起来 我们可以设置上一次路径的方向 优先选择方向相同的
比如上一步是向上走的 我们就给各个方向设置一个权重 离上一次的路线角度越小 权重越大 反之依然
最后我们还是找权重大的路线

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐