项目中涉及的A*寻路算法

    A*寻路算法已经在众多游戏开发中所涉及,目前开发的项目是基于U3D引擎制作的2D像素类型端游,地形在运行之后由脚本创建,其中不同类型的遮挡层级关系依靠生成的不同z轴高度区分,地形整体呈现斜面,地形上的一切位置信息都要先将空间坐标转成格子坐标。故而于地形上的所有移动物体的运动都要通过A*算法实现。如果对A*算法了解不深,可以参考一只探路猫来先汲取一下精华https://www.cnblogs.com/zhoug2020/p/3468167.html
      A*算法无非就是用来搜索两点之间最短有效路径。一般来讲我们都是讲地形分割成很多小格子。首尾两点格子信息获取到之后,在走的每一步上即需要进行判断,怎么走,走哪里才是最有效的路径。没走一个最有效的格子,当到达目标点以后,所有的格子串联,既是我们的最短路径。简单总结要点如下:

关键词:
G:从出发点到当前点的已经走过的路程(几个方块)(左下)
H:当前点到目标点的估计路程(几个方块)(右下)
F:F=G+H 每次都走F最小的下一个位置(左上)重点
Closed列表:用来存放不被考虑的路径(路障和当前位置坐标)
Open列表:用来存放当前位置可以被考虑的路径
过程:
1、地形分割:根据地形的大小和障碍物分割成二维数组个小方格 10*10~若干个像素点
2、把初始位置放进Closed列表中,之后查找周围可被考虑的位置,找到后把所有点放进Open列表,计算每一个位置的G/H/F
3、找到当前位置周围最小F值的方块,移动到当前方块,把当前位置放进Closed列表中,之后再查找周围可以被考虑的位置,放进Open列表,计算每一个位置的G/H/F值
4、重复1、2的过程,直到到达目的地,生成路径
  说到这里,不再啰嗦,直接上两个脚本:

    /// <summary>
    /// 这个class是寻路用的 上面的每个单位
    /// </summary>
    public class Grid
    {
        public MapGridType MapGridType;
        public int x;
        public int y;
        //格子A*三属性f-g-h f价值 g是 到其实点的代价 H是终点的代价
        public int gCost;
        // 与目标点的长度
        public int hCost;
        // 总的路径长度
        public int fCost
        {
            get { return gCost + hCost; }
        }
        //格子类型
        public bool iscanWalk = true;
        //格子的归属(父格子)
        public Grid parent;
        //构造赋值
        public Grid(int x, int y, bool iscanWalk,MapGridType mapGridType)
        {
            this.x = x;
            this.y = y;
            this.iscanWalk = iscanWalk;
            MapGridType = mapGridType;


        }
    }

/// <summary>
/// 由于继承Mono,故挂到一个游戏物体即可
/// </summary>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Game.Map
{
    public class FindPath : MonoBehaviour
    {
        public static FindPath mIntance;
        private MapGrids mapGrids;
        public List<Grid> path;
        void Start()
        {
            mIntance = this;
            mapGrids = GetComponent<MapGrids>();
        }
        public void FindingPath(Vector3 startpos, Vector3 endpos)
        {
            Grid startgrid = mapGrids.getItem(startpos);
            Grid endgrid = mapGrids.getItem(endpos);
            if (!endgrid.iscanWalk)
            {
                path = new List<Grid>();
                return;
            }
            List<Grid> openSet = new List<Grid>();
            HashSet<Grid> closeSet = new HashSet<Grid>();
            openSet.Add(startgrid);
            while (openSet.Count > 0)
            {
                Grid curNode = openSet[0];
                for (int i = 0, max = openSet.Count; i < max; i++)
                {
                    if (openSet[i].fCost <= curNode.fCost &&
                        openSet[i].hCost < curNode.hCost)
                    {
                        curNode = openSet[i];
                    }
                }
                openSet.Remove(curNode);
                closeSet.Add(curNode);
                // 找到的目标节点
                if (curNode == endgrid)
                {
                    generatePath(startgrid, endgrid);
                    return;
                }
                // 判断周围节点,选择一个最优的节点
                foreach (var item in mapGrids.getNeibourhood(curNode))
                {
                    // 如果是墙或者已经在关闭列表中
                    if (!item.iscanWalk || closeSet.Contains(item))
                        continue;
                    // 计算当前相领节点现开始节点距离
                    int newCost = curNode.gCost + getDistanceNodes(curNode, item);
                    // 如果距离更小,或者原来不在开始列表中
                    if (newCost < item.gCost || !openSet.Contains(item))
                    {
                        // 更新与开始节点的距离
                        item.gCost = newCost;
                        // 更新与终点的距离
                        item.hCost = getDistanceNodes(item, endgrid);
                        // 更新父节点为当前选定的节点
                        item.parent = curNode;
                        // 如果节点是新加入的,将它加入打开列表中
                        if (!openSet.Contains(item))
                        {
                            openSet.Add(item);
                        }
                    }
                }
            }
            generatePath(startgrid,null);
        }
        public void generatePath(Grid startNode, Grid endNode)
        {
            path = new List<Grid>();
            if (endNode != null)
            {
                Grid temp = endNode;
                while (temp != startNode)
                {
                    path.Add(temp);
                    temp = temp.parent;
                }
                // 反转路径
                path.Reverse();
            }
            // 更新路径
        }
        int getDistanceNodes(Grid a, Grid b)
        {
            int cntX = Mathf.Abs(a.x - b.x);
            int cntY = Mathf.Abs(a.y - b.y);
            // 判断到底是那个轴相差的距离更远
            if (cntX > cntY)
            {
                return 14 * cntY + 10 * (cntX - cntY);
            }
            else
            {
                return 14 * cntX + 10 * (cntY - cntX);
            }
        }
    }
}

    需特殊说明的是,项目中于MapGrids脚本做了有关空间坐标转换格子坐标的方法,时间有限就不做展示,后续会跟进。我们在此通过主角来进行A*方法调用。
      1、首先在Update每帧执行的方法获取结束点:TargetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
      2 、在外界如果满足人物移动条件,调用下面的 CharacterWalk(TargetPos)此方法同样在Update函数中执行;
     3、定义 CharacterWalk方法,参数为Vector3类型目标点; 使用iTween动画实现2D人物行走
        /// <summary>
        /// 人物行走
        /// </summary>
        /// <param name="targetpos"></param>
        public void CharacterWalk(Vector3 targetpos)
        {
            Game.Map.FindPath.mIntance.FindingPath(transform.position, targetpos);
            if (mCharacterState == CharacterState.Walk)
            {
                if (gameObject.GetComponent<iTween>() != null)
                    Destroy(gameObject.GetComponent<iTween>());
            }
            CharacterWalkList(Game.Map.FindPath.mIntance.path);
        }
        private Vector3[] movePosArr;
        private List<GameObject> PathPointArr;//存路径点 运用对象池显示小黑块代表路径
        private int MoveIndex;
        GameObject go=null;
        /// <summary>
        /// 人物行走动画
        /// </summary>
        /// <param name="gridlist"></param>
        /// <returns></returns>
        public void CharacterWalkList(List<Game.Map.Grid> gridlist)
        {
            if (PathPointArr == null)
            {
                PathPointArr = new List<GameObject>();
            }
            isNearbyTarget = false;
            if (gridlist.Count > 1)
            {
                if (PathPointArr.Count > 0)
                {
                    for (int i = 0; i < PathPointArr.Count; i++)
                    {
                        //隐藏路径点
                        poolManager.Instance.HideObjet(PathPointArr[i]);
                    }
                }
                PathPointArr.Clear();
              
                MoveIndex = 0;
                movePosArr = new Vector3[gridlist.Count];
               
                int r = 0;
                foreach (var grid in gridlist)
                {
                    float dt = grid.y * 0.1f - 10.05f;
                    Vector3 pos = new Vector3(grid.x * 0.48f + 0.24f, grid.y * 0.48f + 0.24f, dt);
                    movePosArr[r++] = pos;
                    //显示路径点
                    go= poolManager.Instance.GetObject("point",null);
                    PathPointArr.Add(go);
                    go.transform.position = pos;
                    go.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
                 
                }
                StartMove();
            }
        }

        private void StartMove()
        {
            CharacterOrientation er = ChangeOrientation(movePosArr[MoveIndex]);

            ChangeOrientation(ChangeOrientation(movePosArr[MoveIndex]));
            float duration = Vector3.Distance(transform.position, movePosArr[MoveIndex]) * 0.1f;
            Hashtable args = new Hashtable();
            args.Add("easeType", iTween.EaseType.linear);
            args.Add("time", duration);
            args.Add("position", movePosArr[MoveIndex]);
            args.Add("oncomplete", "MoveOver");//回调 在此做判断,是否到目标点
            args.Add("speed",speed);//每一个格子的时间
            iTween.MoveTo(gameObject, args);
        }

  private void MoveOver()//每走一个格子,进回调来判断是否到达目标点,如果没有,就继续执行StartMove
        {
            PathPointArr[MoveIndex].SetActive(false);
            //PersonPointPool._instance.InitPool(PathPointArr[MoveIndex]);
            MoveIndex += 1;
            if (MoveIndex < movePosArr.Length)
            {              
                StartMove();
            }
            else//到达目标点了
            {
                 PathPointArr.Clear();
                Debug.Log("移动结束");
                mCharacterState = CharacterState.Idel;
                switch (mcharacterOrientation)
                {
                    case CharacterOrientation.BackLeft:
                    case CharacterOrientation.BackRight:
                        if (isHold)
                            CharacterObj.GetComponent<Animator>().Play("Back_HoldIdel");
                        else
                            CharacterObj.GetComponent<Animator>().Play("Back_Idel");
                        break;
                    case CharacterOrientation.FrontLeft:
                    case CharacterOrientation.FrontRight:
                        if (isHold)
                            CharacterObj.GetComponent<Animator>().Play("Front_HoldIdel");
                        else
                            CharacterObj.GetComponent<Animator>().Play("Front_Idel");
                        break;
                }
                isNearbyTarget = true;
            }
        }

       至此项目中即完整的通过A*来实现主角的移动。

猜你喜欢

转载自blog.csdn.net/qq_33589628/article/details/80541519