Unity中,2D寻路

2023-10-29  本文已影响0人  全新的饭

示意

点击某处,让角色向该处移动。


2D寻路示意.gif

用法

将2个代码文件(NavPathSys.cs、Pathfinder.cs)放入工程中。
调用NavPathSys的GetPath方法,获取一组路径点。

获取路径后,自行实现从当前点移动至目标点的逻辑:如在Update中,让角色依次移动至路径中的各点(详见具体实现)

实现

MoveTest.cs
示意用法

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

public class MoveTest : MonoBehaviour
{
    private NavPathSys _navPathSys;

    private List<Vector2> _path;
    private Vector2 PathFirstPos { get { return _path[0]; } }
    [SerializeField]
    private LayerMask _obstaclesLayer;
    private float _agentRadius = 0.5f;
    [SerializeField]
    private bool _shouldDrawPath = false;
    [SerializeField]
    private Color _drawPathColor = Color.blue;

    private bool CanMove { get { return _path.Count > 0; } }

    [SerializeField]
    private Transform _moveTrans;
    [SerializeField, Header("应是MoveTrans的子节点")]
    private Transform _dirTrans;
    [SerializeField]
    private Vector2 Dir { get { return _dirTrans.right; } }
    private void SetDir(Vector2 dir)
    {
        _dirTrans.right = dir.normalized;
    }
    [SerializeField]
    private float _moveSpeed = 5f;
    private float ActualMoveSpeed { get { return _moveSpeed * Time.deltaTime; } }
    private Vector2 Pos { get { return transform.position; } set { transform.position = value; } }
    private void Move(Vector2 offset)
    {
        transform.position += new Vector3(offset.x, offset.y, 0);
    }

    private bool Is2DCam { get { return Camera.main.orthographic; } }


    private void Start()
    {
        Init();
    }

    private void Update()
    {
        MyUpdate();
    }

    private void OnDestroy()
    {
        MyDestroy();
    }

    private void Init()
    {
        _navPathSys = new NavPathSys();
        _path = new List<Vector2>();
    }

    private void MyUpdate()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (Is2DCam)
            {
                MoveToTarget(Camera.main.ScreenToWorldPoint(Input.mousePosition));
            }
            else
            {
                MoveToTarget(Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, _moveTrans.position.z - Camera.main.transform.position.z)));
            }
        }

        if (CanMove)
        {
            SetDir(PathFirstPos - Pos);
            Move(Dir * ActualMoveSpeed);
            // 若到达了当前的目标点
            if ((Pos - PathFirstPos).sqrMagnitude < ActualMoveSpeed * ActualMoveSpeed)
            {
                Pos = PathFirstPos;
                _path.RemoveAt(0);
            }
            // 画出路径
            if (_shouldDrawPath)
            {
                for (int i = 0; i < _path.Count - 1; i++)
                {
                    Debug.DrawLine(_path[i], _path[i + 1], _drawPathColor);
                }
            }
        }
    }

    private void MyDestroy()
    {
        _path.Clear();
        _path = null;
        _navPathSys.MyDestroy();
        _navPathSys = null;
    }

    private void MoveToTarget(Vector2 targetPos)
    {
        if (targetPos == Pos)
        {
            Debug.Log("当前位置就是目标位置,无需移动");
            return;
        }

        // 生成路径点
        _path = _navPathSys.GetPath(Pos, targetPos, _obstaclesLayer, _agentRadius);

        if (_path.Count == 0)
        {
            Debug.Log("CatMove生成路径失败");
        }
    }
}

NavPathSys.cs

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

public class NavPathSys
{
    // 网格大小
    private float GridSize { get { return 0.5f; } }
    // 设为false时,路径点必然是各Grid。(设为true会更耗,因为使用了Physics2D.Linecast)
    private bool ShouldSearchShortcut { get { return false; } }
    // 是否要求移动物体必须对齐到grid的位置。
    private bool ShouldSnapToGrid { get { return false; } }

    public NavPathSys()
    {

    }
    public void MyDestroy()
    {

    }

    public List<Vector2> GetPath(Vector2 from, Vector2 to, LayerMask obstacleLayer, float agentRadius)
    {
        var info = new MyPathFinderInfo()
        {
            GridSize = this.GridSize,
            ObstacleLayer = obstacleLayer,
            ShouldSearchShortcut = this.ShouldSearchShortcut,
            ShouldSnapToGrid = this.ShouldSnapToGrid,
            AgentRadius = agentRadius
        };
        var myPathFinder = new MyPathFinder(info);
        return myPathFinder.GetPath(from, to);
    }

    private class MyPathFinderInfo
    {
        public float GridSize;
        public LayerMask ObstacleLayer;
        public bool ShouldSearchShortcut;
        public bool ShouldSnapToGrid;
        public float AgentRadius;
    }
    private class MyPathFinder
    {

        private MyPathFinderInfo _info;
        // 寻路器:提供获取路径点的方法等
        private Pathfinder<Vector2> _pathFinder;

        public MyPathFinder(MyPathFinderInfo info)
        {
            _info = info;
            // 地图越大,迭代次数需配置得越大
            _pathFinder = new Pathfinder<Vector2>(GetDistance, GetNeighbourNodes, 1000);
        }
        public void MyDestroy()
        {
            _pathFinder = null;
            _info = null;
        }

        public List<Vector2> GetPath(Vector2 from, Vector2 to)
        {
            var path = new List<Vector2>();
            if (_pathFinder.GenerateAstarPath(GetClosestNode(from), GetClosestNode(to), out path))
            {
                // 设置为当前要移动的路径
                // 寻找近路
                if (_info.ShouldSearchShortcut && path.Count > 0)
                    path = ShortenPath(path);
                else
                {
                    if (!_info.ShouldSnapToGrid)
                    {
                        // 将终点位置添加到路径最后
                        path.Add(to);
                    }
                }
            }
            else
            {
                // Debug.Log("获取路径失败!" + from + "-----" + to);
            }

            return path;
        }



        /// <summary>
        /// 获得2点的距离用于比较(为了性能,实际获得的是距离的平方)
        /// </summary>
        /// <param name="A"></param>
        /// <param name="B"></param>
        /// <returns></returns>
        float GetDistance(Vector2 A, Vector2 B)
        {
            return (A - B).sqrMagnitude;
        }

        /// <summary>
        /// 获得邻近的可到达(两点之间未被障碍物遮挡)的grid及与对方之间的距离(不包括自己)
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        Dictionary<Vector2, float> GetNeighbourNodes(Vector2 pos)
        {
            Dictionary<Vector2, float> neighbours = new Dictionary<Vector2, float>();
            for (int i = -1; i < 2; i++)
            {
                for (int j = -1; j < 2; j++)
                {
                    if (i == 0 && j == 0)
                    {
                        continue;
                    }

                    Vector2 dir = new Vector2(i, j) * _info.GridSize;
                    if (!Physics2D.Linecast(pos, pos + dir, _info.ObstacleLayer))
                    {
                        // 避免路径过于贴近障碍物导致“移动者穿墙”
                        if (Physics2D.OverlapCircle(pos + dir, _info.AgentRadius, _info.ObstacleLayer) != null)
                        {
                            continue;
                        }
                        neighbours.Add(GetClosestNode(pos + dir), dir.magnitude);
                    }
                }

            }
            return neighbours;
        }

        /// <summary>
        /// 获取与目标点位置最近的grid
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        Vector2 GetClosestNode(Vector2 target)
        {
            return new Vector2(Mathf.Round(target.x / _info.GridSize) * _info.GridSize, Mathf.Round(target.y / _info.GridSize) * _info.GridSize);
        }


        // 获得近路:原理是剔除部分(可省略的)路径点
        List<Vector2> ShortenPath(List<Vector2> path)
        {
            List<Vector2> newPath = new List<Vector2>();

            for (int i = 0; i < path.Count; i++)
            {
                newPath.Add(path[i]);
                for (int j = path.Count - 1; j > i; j--)
                {
                    if (!Physics2D.Linecast(path[i], path[j], _info.ObstacleLayer))
                    {

                        i = j;
                        break;
                    }
                }
                newPath.Add(path[i]);
            }
            newPath.Add(path[path.Count - 1]);
            return newPath;
        }
    }
}

Pathfinder.cs

using System;
using System.Collections.Generic;
using System.Linq;
public class Pathfinder<T>
{
    private struct pathData
    {
        public float g;

        public float h;

        public float f;
    }

    private int _calculatorPatience;

    private Func<T, T, float> _HeuristicDistance;

    private Func<T, Dictionary<T, float>> _ConnectedNodesAndStepCosts;

    public Pathfinder(Func<T, T, float> HeuristicDistance, Func<T, Dictionary<T, float>> ConnectedNodesAndStepCosts, int calculatorPatience = 9999)
    {
        _HeuristicDistance = HeuristicDistance;
        _ConnectedNodesAndStepCosts = ConnectedNodesAndStepCosts;
        _calculatorPatience = calculatorPatience;
    }

    public bool GenerateAstarPath(T startNode, T targetNode, out List<T> path)
    {
        float num = _HeuristicDistance(startNode, targetNode);
        int num2 = _calculatorPatience;
        HashSet<T> hashSet = new HashSet<T>();
        Dictionary<T, pathData> dictionary = new Dictionary<T, pathData> {
        {
            startNode,
            new pathData
            {
                g = 0f,
                h = num,
                f = num
            }
        } };
        Dictionary<T, T> dictionary2 = new Dictionary<T, T>();
        while (num2 > 0)
        {
            num2--;
            if (dictionary.Count == 0)
            {
                break;
            }

            T key = dictionary.Aggregate((KeyValuePair<T, pathData> l, KeyValuePair<T, pathData> r) => (l.Value.f < r.Value.f) ? l : r).Key;
            pathData pathData = dictionary[key];
            dictionary.Remove(key);
            hashSet.Add(key);
            if (key.Equals(targetNode))
            {
                List<T> list = new List<T>();
                T val = key;
                while (!val.Equals(startNode))
                {
                    list.Add(val);
                    val = dictionary2[val];
                }

                list.Reverse();
                path = list;
                return true;
            }

            foreach (KeyValuePair<T, float> item in _ConnectedNodesAndStepCosts(key))
            {
                if (Enumerable.Contains(hashSet, item.Key))
                {
                    continue;
                }

                float num3 = item.Value + pathData.g;
                if (!dictionary.ContainsKey(item.Key) || dictionary[item.Key].g > num3)
                {
                    float num4 = _HeuristicDistance(item.Key, targetNode);
                    float f = num4 + num3;
                    dictionary2[item.Key] = key;
                    if (!dictionary.ContainsKey(item.Key))
                    {
                        dictionary.Add(item.Key, new pathData
                        {
                            g = num3,
                            h = num4,
                            f = f
                        });
                    }
                    else
                    {
                        dictionary[item.Key] = new pathData
                        {
                            g = num3,
                            h = num4,
                            f = f
                        };
                    }
                }
            }
        }

        path = new List<T>();
        return false;
    }
}
上一篇 下一篇

猜你喜欢

热点阅读