在寻路过程中有许多的影响因素,例如时间,能量,金钱,地形,距离的美好。对于起始节点与目标节点之间的每一条可行路径,都可以用代价的大小来描述。而A*算法的任务就是选取代价最小的那条路径


先介绍 导航图(参考于Unity3D 人工智能编程精粹一书)


1.基于单元的导航图,寻路使以网格为单位进行的。如果单个正方形过大,网格很粗糙,那么很难得到好的路径,如果网格很精细,那么虽然会寻找到很好的路径,但这时需要存储和搜索大量的节点,对内存要求高,而且也很影响效率。


2.基于可视点导航图,一般由场景设计者在场景中手工放置一些路径点,然后由设计人员测试这些“路径点”之间的可视性


3.创建导航网格:导航网格将游戏场景中的可行走区域划分成凸多边形。导航网格表示出了可行走区域的真实几何关系,使一个非均匀网格。 Unity3D自带的寻路系统就建立在导航网格的基础上。利用A*算法,可以找到路径所经过的那么多边形。如果直接把这些多边形的中心连接起来,就会得到从起始点到目标点的一条路径。下面用Unity3D来实现。



首先介绍A*寻路算法:


点击打开链接


下面是在Unity中实现的效果






unity寻路绕开障碍物 unity寻路点_st表


绿色部分代表初始点,红色部分代表目标点,蓝色部分代表障碍物,灰色部分代表路径,白色部分代表整个虚拟网格



再给出他们的实现代码:



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

public class Point {
    public Point Parent { get; set; } //父节点
    //F=G+H
    public int F { get; set; }
    public int G { get; set; }
    public int H { get; set; }
    //Position
    public int X { get; set; }
    public int Y { get; set; }
    //障碍物
    public bool IsWall { get; set; }
    //非路径非障碍物,渲染成白色
    public bool IsWhite { get; set; }

    public Point(int x,int y , Point parent = null)
    {
        this.X = x;
        this.Y = y;
        this.Parent = parent;
        IsWall = false;
        IsWhite = true;
    }
    //更新父节点
    public void UpdateParent(Point parent,int g)
    {
        this.Parent = parent;
        this.G = g;
        F = G + H;
    }
}




AStar.cpp

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

public class AStar : MonoBehaviour {

    private const int mapWith = 8;
    private const int mapHeight = 8;

    private Point[,] map = new Point[mapWith, mapHeight];

	// Use this for initialization
	void Start () {
        InitMap();  //虚拟化地图
        //定义初试点和目的点
        Point start = map[2,3];
        Point end = map[7, 4];
        //寻找路径
        FindPath(start, end);
        //显示路径
        ShowPath(start,end);
        //List<Point> l= GetSurroundPoints(map[0, 0]);
        //foreach(Point p in l)
        //{
        //    Debug.Log(p.X + "-" + p.Y);
        //}
	}	
	
    private void ShowPath(Point start,Point end)
    {
        Point temp = end;
        //从目的点倒回出发点来显示路径
        while (true)
        {
            //Debug.Log(temp.X + "," + temp.Y);
            Color c = Color.gray;
            if (temp == start)
            {
                c = Color.green;
            }else if (temp == end)
            {
                c = Color.red;
            }
            CreateCube(temp.X, temp.Y, c);
            temp.IsWhite = false;
            if (temp.Parent == null)
                break;
            temp = temp.Parent;
            
        }
        for (int x = 0; x < mapWith; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                if (map[x, y].IsWall)
                {
                    CreateCube(x, y, Color.blue);
                }
                if (map[x, y].IsWhite==true )
                {
                    CreateCube(x, y, Color.white);
                }
            }
        }
    }
    
   
    
    private void CreateCube(int x,int y,Color color)
    {
        GameObject go =  GameObject.CreatePrimitive(PrimitiveType.Cube);
        go.transform.position = new Vector3(x, y, 0);
        go.GetComponent<Renderer>().material.color = color;
    }

    private void InitMap()
    {
        for(int x = 0; x < mapWith; x++)
        {
            for(int y = 0; y < mapHeight; y++)
            {
                map[x, y] = new Point(x,y);
            }
        }
        map[4, 2].IsWall = true;
        map[4, 3].IsWall = true;
        map[4, 4].IsWall = true;
        map[4, 5].IsWall = true;
        map[4, 6].IsWall = true;
        map[4, 2].IsWhite = false;
        map[4, 3].IsWhite = false;
        map[4, 4].IsWhite = false;
        map[4, 5].IsWhite = false;
        map[4, 6].IsWhite = false;
    }

    private void FindPath(Point start,Point end)
    {
        List<Point> openList = new List<Point>();//openList表,等待考察的节点的优先级队列
        List<Point> closeList = new List<Point>();//已经考察过,无须再考察的节点列表
        openList.Add(start);
        while (openList.Count > 0)
        {
            Point point = FindMinFOfPoint(openList); //在openList表中选择F最小的值加入closeList表中
            openList.Remove(point);
            closeList.Add(point);
            List<Point> surroundPoints = GetSurroundPoints(point); //寻找周围点
            PointsFilter(surroundPoints, closeList); //过滤掉close表中已有的节点
            foreach(Point surroundPoint in surroundPoints)
            {
                if (openList.IndexOf(surroundPoint) > -1) //如果在openList已经存在,比较
                {
                    int nowG =(int) CalcG(surroundPoint, point);
                    if(nowG< surroundPoint.G)
                    {
                        surroundPoint.UpdateParent(point,nowG);
                    }
                }
                else
                {
                    surroundPoint.Parent = point;
                    CalcF(surroundPoint, end);
                    openList.Add(surroundPoint);
                }
            }
            //判断一下
            if (openList.IndexOf(end) > -1) //如果到达了目的地,break
            {
                break;
            }
        }

    }

    private void PointsFilter(List<Point> src,List<Point> closeList)
    {
        foreach(Point p in closeList)
        {
            if (src.IndexOf(p) > -1)
            {
                src.Remove(p);
            }
        }
    }

    private List<Point> GetSurroundPoints(Point point)
    {
        Point up = null, down = null, left = null, right = null;
        Point lu = null, ru = null, ld = null, rd = null;
        if (point.Y < mapHeight- 1)
        {
            up = map[point.X, point.Y + 1];
        }
        if (point.Y > 0)
        {
            down = map[point.X, point.Y - 1];
        }
        if (point.X > 0)
        {
            left = map[point.X - 1, point.Y];
        }
        if(point.X <mapWith-1)
        {
            right = map[point.X + 1, point.Y];
        }
        if (up != null && left != null)
        {
            lu = map[point.X - 1, point.Y + 1];
        }
        if (up != null && right != null)
        {
            ru = map[point.X + 1, point.Y + 1];
        }
        if (down != null && left != null)
        {
            ld = map[point.X - 1, point.Y - 1];
        }
        if (down != null && right != null)
        {
            rd = map[point.X + 1, point.Y - 1];
        }
        List<Point> list = new List<Point>();
        if (down != null && down.IsWall == false)
        {
            list.Add(down);
        }
        if (up != null && up.IsWall == false)
        {
            list.Add(up);
        }
        if (left != null && left.IsWall == false)
        {
            list.Add(left);
        }
        if (right != null && right.IsWall == false)
        {
            list.Add(right);
        }
        if (lu != null && lu.IsWall == false && left.IsWall == false && up.IsWall == false)
        {
            list.Add(lu);
        }
        if (ld != null && ld.IsWall == false && left.IsWall == false && down.IsWall == false)
        {
            list.Add(ld);
        }
        if (ru != null && ru.IsWall == false && right.IsWall == false && up.IsWall == false)
        {
            list.Add(ru);
        }
        if (rd != null && rd.IsWall == false && right.IsWall == false && down.IsWall == false)
        {
            list.Add(rd);
        }
        return list;
    }

    private Point FindMinFOfPoint(List<Point> openList)
    {
        int f = int.MaxValue;
        Point temp = null;
        foreach(Point p in openList)
        {
            if (p.F < f)
            {
                temp = p;
                f = p.F;
            }
        }
        return temp;
    }

    private float CalcG(Point now,Point parent)
    {
        return  Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(parent.X, parent.Y)) + parent.G;
    }

    private void CalcF(Point now,Point end)
    {
        //F = G + H
        int h = Mathf.Abs(end.X - now.X) + Mathf.Abs(end.Y - now.Y);
        int g = 0;
        if (now.Parent == null)
        {
            g = 0;
        }
        else
        {
            g=(int)Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(now.Parent.X, now.Parent.Y)) + now.Parent.G;
        }
        int f = g + h;
        now.F = f;
        now.G = g;
        now.H = h;
    }
}