A*(A星)寻路算法

首先非常感谢唐老师的课程,讲的非常好
添加链接描述

  • 三个变量:

    G值 = 父节点的G值 + 父节点到当前点的移动代价
    (父节点的G是0,如果斜着走就是根号2,直着走就是1)

    H值 = 当前点到结束点的曼哈顿距离

    寻路消耗公式 F:=G+H

  • 还有两个列表
    开放列表: 吧当前所有可以走的节点都放进开放列表
    关闭列表:每一次走过一个节点之后都把这个节点放到关闭列表里面

  • 具体步骤
    a、将开始点记录为当前点P
    b、将当前点P放入关闭列表
    c、搜寻点P所有邻近点,假如某邻近点既没有在开放列表或关闭列表里面,则计算出该邻近点的F值,并设父节点为P,然后将其放入开放列表
    d、判断开放列表是否已经空了,如果没有说明在达到结束点前已经找完了所有可能的路径点,寻路失败,算法结束;否则继续。
    e、从开放列表拿出一个F值最小的点,作为寻路路径的下一步。
    f、判断该点是否为结束点,如果是,则寻路成功,算法结束;否则继续。
    g、将该点设为当前点P,跳回步骤c。

  • 找出路径
    从终点节点开始,找其父节点,顺藤摸瓜,一直找到起始节点,这便是一条路径

总结:

  • A*并不是最短路,他是以最快的速度找到通往目标的路径
  • A*在死路情况下会找遍所有点

一下跟着写的代码,一个小demon:、

优化的地方:

  • 本demon是用的list存储,list很多操作个比较耗,可以用红黑树(堆)来优化排序过程
  • 判断是不是在开放或者关闭列表中,list会遍历所有,我们可以在节点里加一个属性做标记

格子类:

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

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

    //寻路消耗
    public float f;
    //距离起点距离
    public float g;
    //距离终点的距离
    public float h;
    //父对象
    public AStarNode fataher;
    //格子类型
    public ENodeType type;

    //构造函数,传入格子坐标和格子类型
    public AStarNode(int x,int y,ENodeType type)
    {
     
        this.x = x;
        this.y = y;
        this.type = type;
    }
}

/// 
/// 格子类型
/// 
public enum ENodeType
{
     
    Walk,
    Stop,
}

管理类:

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

public class AStartMgr
{
     
    #region 构造单例模式
    private AStartMgr() {
      }
    public static AStartMgr Instance
    {
     
        get
        {
     
            return Nested._instance;
        }
    }
    private class Nested
    {
     
        internal static readonly AStartMgr _instance = new AStartMgr();
    }
    #endregion

    //地图相关所有格子的对象容器
    private AStarNode[,] nodes;
    public AStarNode[,] Nodes
    {
     
        get {
      return nodes; }
    }
    //开启列表
    private List<AStarNode> openList=new List<AStarNode>();
    //关闭列表
    private List<AStarNode> closeList = new List<AStarNode>();
    //地图的宽高
    private int mapW;
    private int mapH;

    /// 
    /// 初始化地图信息
    /// 
    /// 
    /// 
    public void InitMapInfo(int w,int h)
    {
     
        this.mapW = w;
        this.mapH = h;
        nodes = new AStarNode[w, h];
        for(int i = 0; i < w; i++)
        {
     
            for(int j = 0; j < h; j++)
            {
     
                //15%的可能性是阻塞,
                AStarNode node = new AStarNode(i, j, Random.Range(0, 100) < 40 ? ENodeType.Stop : ENodeType.Walk);
                nodes[i, j] = node;
            }
        }
    }
   
    /// 
    /// 寻路方法 当openlist所有的点都完了的话就是思路一条
    /// 
    /// 
    /// 
    /// 
    public List<AStarNode> FindPath(Vector2 startPos, Vector2 endPos)
    {
     
        ///实际项目中 传入的点往往是坐标系的位置,我们这里省略换算的步骤,认为就是传进来的格子坐标

        ///首先判断传入的两个点是否合法      
        ///要是不合法直接return
        if (startPos.x < 0 || startPos.y >= mapW || startPos.y < 0 || startPos.y >= mapH
          || endPos.x < 0 || endPos.y >= mapW || endPos.y < 0 || endPos.y >= mapH)
        {
     
            return null;
        }
        ///在范围之内并且不是阻挡
        AStarNode start = nodes[(int)startPos.x, (int)startPos.y];
        AStarNode end = nodes[(int)endPos.x, (int)endPos.y];
        if (start.type == ENodeType.Stop || end.type == ENodeType.Stop)
        {
     
            return null;
        }
        ///还得清空脏数据
        openList.Clear();
        closeList.Clear();
        ///开始点放到关闭列表中
        start.fataher = null;
        start.f = 0;
        start.g = 0;
        start.h = 0;
        closeList.Add(start);

        while (true)
        {
     
            ///从起点开始找,找周围的点,       
            ///左上
            FindNearlyNodeToOpenList(start.x - 1, start.y - 1, 1.4f, start, end);
            ///上
            FindNearlyNodeToOpenList(start.x, start.y - 1, 1f, start, end);
            ///右上
            FindNearlyNodeToOpenList(start.x + 1, start.y - 1, 1.4f, start, end);
            ///左
            FindNearlyNodeToOpenList(start.x - 1, start.y, 1f, start, end);
            ///右
            FindNearlyNodeToOpenList(start.x + 1, start.y, 1f, start, end);
            ///左下
            FindNearlyNodeToOpenList(start.x - 1, start.y + 1, 1.4f, start, end);
            ///下
            FindNearlyNodeToOpenList(start.x, start.y + 1, 1f, start, end);
            ///右下
            FindNearlyNodeToOpenList(start.x + 1, start.y + 1, 1f, start, end);
            ///死路
            if (openList.Count == 0)
            {
     
                Debug.Log("死路一条");
                return null;
            }
            ///选出开启列表中寻路消耗最小的点
            openList.Sort((AStarNode a, AStarNode b) => {
     
                if (a.f > b.f)
                    return 1;
                else if (a.f == b.f)
                    return 1;
                return -1;
            });
            ///放入关闭列表,然后从开启列表中移除,这个点变成新的起点    
            start = openList[0];
            closeList.Add(openList[0]);
            openList.RemoveAt(0);
            ///如果这个点是终点,则得到结果,否则继续寻找
            if (start == end)
            {
     
                //找出路径
                List<AStarNode> path = new List<AStarNode>();
                path.Add(end);
                while (end.fataher != null)
                {
     
                    path.Add(end.fataher);
                    end = end.fataher;
                }
                path.Reverse();
                return path;
            }
        }
        return null;
    }

    /// 
    /// 吧临近附近的点放到开启列表之中
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    private void FindNearlyNodeToOpenList(int x, int y,float g, AStarNode fatherNode,AStarNode end)
    {
     
        //边界判断 是否阻挡,是否在开启列表或者关闭列表
        if (x < 0 || x >= mapW || y < 0 || y >= mapH)
            return;
        AStarNode node = nodes[x, y];
        if (node == null || node.type == ENodeType.Stop || closeList.Contains(node) || openList.Contains(node))
            return;
        //计算f 放到开启列表
        node.h = Mathf.Abs(end.x - node.x) + Mathf.Abs(end.y - node.y);
        node.g = fatherNode.g + g;
        node.f = node.g = node.h;
        node.fataher = fatherNode;
        openList.Add(node);
    }

}

测试类:吧这个类挂载一下运行就可以了

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

public class AStarTest : MonoBehaviour
{
     
    //左上角第一个位置
    public int beginX;
    public int beginY;
    //偏移量
    public int offsetX;
    public int offsetY;
    //地图格子的宽高
    public int mapW;
    public int mapH;
    //阻挡物材质
    public Material stopMaterial;
    public Material beginMaterial;
    public Material endMaterial;

    private Dictionary<string, GameObject> cubes = new Dictionary<string, GameObject>();
    private Vector2 beginPos = new Vector2(-1, -1);
    private Vector2 endPos = new Vector2(-1, -1);
    private List<AStarNode> _path = new List<AStarNode>();

    private void Start()
    {
     
        AStartMgr.Instance.InitMapInfo(mapW, mapH);
        for(int i = 0; i < mapW; i++)
        {
     
            for(int j = 0; j < mapH; j++)
            {
     
                GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
                obj.transform.position = new Vector3(beginX + i * offsetX, beginY + j * offsetY, 0);
                AStarNode node = AStartMgr.Instance.Nodes[i, j];
                obj.name = i + "_" + j;
                cubes.Add(obj.name, obj);
                //格子是否阻挡
                if (node.type == ENodeType.Stop)
                {
     
                    obj.GetComponent<MeshRenderer>().material = stopMaterial;
                }
            }
        }
    }

    public void Update()
    {
     
        if (Input.GetMouseButtonDown(0))
        {
     
            RaycastHit info;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if(Physics.Raycast(ray,out info, 100))
            {
     
                if (info.collider.gameObject == null)
                    return;
                //起点
                if (beginPos.x==-1 && beginPos.y == -1)
                {
     
                    string[] strs = info.collider.gameObject.name.Split('_');
                    beginPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
                    info.collider.gameObject.GetComponent<MeshRenderer>().material = beginMaterial;
                }
                //终点
                else if(endPos.x==-1&& endPos.y == -1)
                {
     
                    string[] strs = info.collider.gameObject.name.Split('_');
                    endPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
                    info.collider.gameObject.GetComponent<MeshRenderer>().material = endMaterial;
                    //路径
                    _path = AStartMgr.Instance.FindPath(beginPos, endPos);
                    if (_path != null)
                    {
     
                        foreach (AStarNode node in _path)
                        {
     
                            string itsNmae = node.x + "_" + node.y;
                            cubes[node.x + "_" + node.y].GetComponent<MeshRenderer>().material = endMaterial;
                            Debug.Log(itsNmae);
                        }
                    }

                }

            }
        }
    }

}

你可能感兴趣的:(unity,算法)