unity A星寻路

发布于:2025-07-12 ⋅ 阅读:(36) ⋅ 点赞:(0)

算法 fCost = gCost + hCost
gCost 是当前节点到移动起始点的消耗,hCost是当前节点到终点的消耗
网格为变成为1的矩形,左右相邻的两个网格直接的gCost为1,斜对角相邻的两个网格的gCost为1.4
hCost 当前网格到终点网格的 水平距离 + 垂直距离
比如当前网格位置是(2,3),终点位置(10,8),则hCost = (10-2) + (8-3)
原始的算法是 fCost = gCost + hCost,均匀计算周围网格
对于一些比较复杂的散装障碍物场景,我们可以适当调整gCost和hCost的权重,比如 fCost = gCost x 4 + hCost x 6,这个修改会让寻路的时候,在消耗相同的网格点上,优先在距离终点位置更近的网格周围寻路。当我们从(1,1)向(3,10)位置寻路的时候,我们在店(3,1)和点(1,10)两个位置的 gCost + hCost 是相等的,但是点(1,10)其实距离终点更近一些,用调整过权重的算法,(1,10)我们得出来的的值会小一些,就可以优先从(1,10)位置寻找可移动路径

代码如下
寻路节点

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

public class AstarNode
{
     public int gridX;
     public int gridY;
     public Vector3 position;

     public float hCost;
     public float gCost;
     public float fCost {
          get
          {
               return gCost + hCost;
          }
     }
     
     /// <summary>
     /// 优先处理距离终点近的node
     /// </summary>
     public float fCostNew {
          get
          {
               return gCost*4 + hCost*6;
          }
     }
     public bool walkable;
     public AstarNode parent;

     public AstarNode()
     {
          
     }
     public AstarNode(int x, int y, Vector3 pos, bool canWalk)
     {
          gridX = x;
          gridY = y;
          position = pos;
          walkable = canWalk;
     }
     
     
 
}

地图网格生成

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using Random = System.Random;
public enum SearchType
{
    NeighborFour,
    NeighborEight,
}
public class MapGridsManager : MonoBehaviour
{
    public int width = 100;
    public int height = 100;
    public float cellSize = 1f;
    public Mesh mesh;
    private Vector3 originPoint;
    private AstarNode[,] grids;
    public AStartManager manager;
    public AstartMoveTest moveTarget;
    public bool isUseCache = false;
    public bool isUseNewCost = false;
    public SearchType searchType = SearchType.NeighborFour;
    [Header("障碍物生成范围") ]
    public Rect obstacleRect;
    void Awake()
    {
        originPoint = Vector3.zero;
        mesh = Resources.GetBuiltinResource<Mesh>("Quad.fbx");
    }

    private void Start()
    {
        grids = new AstarNode[width, height];
        Dictionary<int, AstarNode> nodeDic = GetCacheData();
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                int keyId = i * 100000 + j;
               
                AstarNode node;
                if (isUseCache && nodeDic.ContainsKey(keyId))
                {
                    node = nodeDic[keyId];
                }
                else
                {
                    bool isObstacal = UnityEngine.Random.Range(1, 100) > 50;
                    isObstacal = i >= obstacleRect.xMin && i < obstacleRect.xMax && j >= obstacleRect.yMin && j < obstacleRect.yMax  && isObstacal;
                    bool warkAble = !isObstacal;
                    node = new AstarNode(i, j, originPoint + new Vector3(i * cellSize, 0, j * cellSize), warkAble);    
                }
                
                grids[i, j] = node;
            }
        }

        serilizeNode();
    }

    void serilizeNode()
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                var node = grids[i, j];
                int warkState = node.walkable ?1:0;
                if (i>0 || j > 0)
                {
                    sb.Append("|");
                }

                var data = $"{node.gridX}_{node.gridY}_{warkState}_{node.position.x}_{node.position.y}_{node.position.z}";
                sb.Append(data) ;
            }
        }

        string res = sb.ToString();
        UnityEngine.PlayerPrefs.SetString("grid_data", res);
    }

    Dictionary<int, AstarNode> GetCacheData()
    {
        Dictionary<int, AstarNode> nodeDic = new Dictionary<int, AstarNode>(width * height);
        string data = UnityEngine.PlayerPrefs.GetString("grid_data", "");
        string[] arr = data.Split("|");
        for (int i = 0; i < arr.Length; i++)
        {
            string itemStr = arr[i];
            string[] itemArr = itemStr.Split("_");
            if (itemArr.Length < 6)
            {
                continue;
            }
            AstarNode node = new AstarNode();
            node.gridX = int.Parse(itemArr[0]);
            node.gridY = int.Parse(itemArr[1]);
            node.walkable = int.Parse(itemArr[2]) == 1 ? true : false;
            float positionX = float.Parse(itemArr[3]);
            float positionY = float.Parse(itemArr[4]);
            float positionZ = float.Parse(itemArr[5]);
            node.position = new Vector3(positionX, positionY, positionZ);
            int keyId = node.gridX * 100000 + node.gridY;
            nodeDic.Add(keyId, node);
        }
        return nodeDic;
    }
    
    public AstarNode GetNode(int x, int y)
    {
        if (x < 0 || x >= width || y < 0 || y >= height)
        {
            return null;
        }
        return grids[x, y];
    }
    
    

    public List<AstarNode> GetNeighborNodes(AstarNode node)
    {
        if (searchType == SearchType.NeighborFour)
            return GetNeighborFourNodes(node);
        else
        {
            return GetNeighborEightNodes(node);
        }
    }

    public List<AstarNode> GetNeighborFourNodes(AstarNode node)
    {
        List<AstarNode> nodeList = new List<AstarNode>();
        int nodeX = node.gridX;
        int nodeY = node.gridY;
        for (int i = -1; i <= 1; i++)
        {
            for (int j = -1; j <= 1; j++)
            {
                if (i == 0 && j == 0)
                {
                    continue;
                }

                if (i == 0 || j == 0)
                {
                    int itemX = nodeX + i;
                    int itemY = nodeY + j;
                    AstarNode itemNode = GetNode(itemX, itemY);
                    if (itemNode != null)
                    {
                        nodeList.Add(itemNode);
                    }
                }
            }
        }

        return nodeList;
    }
    
    public List<AstarNode> GetNeighborEightNodes(AstarNode node)
    {
        List<AstarNode> nodeList = new List<AstarNode>();
        int nodeX = node.gridX;
        int nodeY = node.gridY;
        for (int i = -1; i <= 1; i++)
        {
            for (int j = -1; j <= 1; j++)
            {
                if (i == 0 && j == 0)
                {
                    continue;
                }
                int itemX = nodeX + i;
                int itemY = nodeY + j;
                AstarNode itemNode = GetNode(itemX, itemY);
                if (itemNode != null)
                {
                    nodeList.Add(itemNode);
                }
            }
        }

        return nodeList;
    }








    public AstarNode GetByWorldPositionNode(Vector3 pos)
    {
        int x = Mathf.FloorToInt(pos.x / cellSize);
        int y = Mathf.FloorToInt(pos.y / cellSize);
        if (x < 0 || y < 0 || x>=width || y >= height)
        {
            return null;
        }
        return grids[x, y];
    }

    void OnDrawGizmos()
    {
        Gizmos.color = Color.gray;
        if (grids != null)
        {
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    AstarNode node = grids[i, j];
                    
                    if (manager != null)
                    {
                        if (manager.rodeList.Contains(node))
                        {
                            Gizmos.color = Color.white;
                        }
                        else if(manager.openList.Contains(node))
                        {
                            Gizmos.color = Color.black;
                        }
                        else if (manager.closeList.Contains(node))
                        {
                            Gizmos.color = Color.green;
                        }
                        else
                        {
                            Gizmos.color = node.walkable?  Color.blue: Color.red;
                        }
                        if (manager.moveEndNode != null && manager.moveEndNode == node)
                        {
                            Gizmos.color = Color.yellow;
                        }
                        
                        if (manager.moveStartNode != null && manager.moveStartNode == node)
                        {
                            Gizmos.color = Color.gray;
                        }
                    }
                    else
                    {
                        Gizmos.color = node.walkable?  Color.blue: Color.red;
                    }
                    
                    
                    // Gizmos.DrawCube(node.position, Vector3.one);
                    var rotation = Quaternion.Euler(new Vector3(90, 0, 0));
                    Gizmos.DrawMesh(mesh, node.position, rotation, Vector3.one);
                }
            }
        }

        Gizmos.color = Color.black;
        var halfCellSize = cellSize / 2;
        for (int i = 0; i <= width; i++)
        {
            Gizmos.DrawLine(new Vector3(i-halfCellSize,0,0), new Vector3(i-halfCellSize, 0 ,height-halfCellSize));
        }
        
        for (int i = 0; i <= height; i++)
        {
            Gizmos.DrawLine(new Vector3(0,0,i-halfCellSize), new Vector3(width-halfCellSize, 0 ,i-halfCellSize));
        }
         
    }

    public void ResetNodeCost()
    {
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                var node = grids[i, j];
                node.gCost = 0;
                node.hCost = 0;
            }
        }
    }

    public void BeginMove(List<AstarNode> rodeNodeList)
    {
        moveTarget.BeginMove(rodeNodeList);
    }
}

寻路

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

[Serializable]
public class ObstacleRange
{
    public int x;
}
public class AStartManager : MonoBehaviour
{
    public MapGridsManager map;
    public Vector3 from;
    public Vector3 end;

    public List<AstarNode> openList = new List<AstarNode>();
    public List<AstarNode> closeList = new List<AstarNode>();
    public List<AstarNode> rodeList = new List<AstarNode>();

    public AstarNode moveStartNode;
    public AstarNode moveEndNode;
    private bool isInFindPath = false;//是否正在训练中
    
        
    void Update()
    {
        if (Input.GetKeyUp(KeyCode.A))
        {
            StartCoroutine(FindPath(from, end));
            
        }
        else if(Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                var pos = new Vector3(hit.point.x, hit.point.z, 0);
                Debug.Log($"{pos}--{Input.mousePosition}");
                var node = map.GetByWorldPositionNode(pos);
                if (node != null)
                { 
                    from = end;
                    end =  pos;
                    StartCoroutine(FindPath(from, end));
                }
            }
            
        }
        
    }

    void DoMove()
    {
        map.BeginMove(rodeList);
    }

    IEnumerator FindPath(Vector3 startPos, Vector3 endPos)
    {
        if (isInFindPath)
        {
            yield break;
        }

        isInFindPath = true;
        ResetNodeData();
        AstarNode startNode = map.GetByWorldPositionNode(startPos);
        startNode.gCost = 0;
        startNode.hCost = 0;
        startNode.parent = null;
        AstarNode endNode = map.GetByWorldPositionNode(endPos);
        endNode.gCost = 0;
        endNode.hCost = 0;
        endNode.parent = null;
        openList.Add(startNode);
        moveStartNode = startNode;
        moveEndNode = endNode;
        if (!endNode.walkable)
        {
            Debug.LogError("终点不可抵达");
            isInFindPath = false;
            yield break;
        }
        
        int index = 0;
        while (openList.Count > 0)
        {
            var targetNode = openList[0];
            Debug.Log($"{index}: ({targetNode.gridX} ,{targetNode.gridY})");
            openList.Remove(targetNode);
            if (targetNode.gridX == endNode.gridX && targetNode.gridY == endNode.gridY)
            {
                GenerateRote(targetNode);
                openList.Clear();
                break;
            }

            AddOpenList(targetNode, endNode);
            if (map.isUseNewCost)
            {
                openList.Sort((a, b)=>
                {
                    return a.fCostNew.CompareTo(b.fCostNew);
                });
            }
            else
            {
                openList.Sort((a, b)=>
                {
                    return a.fCost.CompareTo(b.fCost);
                });
            }
            
            
            yield return new WaitForSeconds(0.1f);
            index++;
            if (index > 5000)
            {
                Debug.LogError("循环超出上限");
                break;
            }
        }
        Debug.Log("寻路循环次数为:" + index);
        DoMove();
        isInFindPath = false;
    }

    void ResetNodeData()
    {
        openList.Clear();
        closeList.Clear();
        rodeList.Clear();
        map.ResetNodeCost();
    }

    private bool AddOpenList(AstarNode targetNode, AstarNode endNode)
    {
        var neighborNodes = map.GetNeighborNodes(targetNode);
        closeList.Add(targetNode);
        foreach (var item in neighborNodes)
        {
            if (item.walkable == false || closeList.Contains(item) || openList.Contains(item))
            {
                continue;
            }
                    
            float tempGCost = targetNode.gCost + GetDestance(targetNode, item);
            if (item.gCost <= 0 || tempGCost < item.gCost)
            {
                item.parent = targetNode;
                item.gCost = targetNode.gCost + GetDestance(targetNode, item);
                item.hCost = Mathf.Abs(endNode.gridX - item.gridX) + Mathf.Abs(endNode.gridY - item.gridY);
                openList.Add(item);
            }
        }

        return false;
    }

    List<AstarNode> GenerateRote(AstarNode curNode)
    {
        rodeList = new List<AstarNode>();
        rodeList.Add(curNode);
        while (curNode.parent != null)
        {
            curNode = curNode.parent;
            rodeList.Add(curNode);
        }
        rodeList.Reverse();
        return rodeList;
    }

    float GetDestance(AstarNode node1, AstarNode node2)
    {
        float distance  ;
        if (node1.gridX != node2.gridX && node1.gridY != node2.gridY)
        {
            distance = 1.4f;
        }
        else
        {
            distance = 1f;
        }
        return distance; 
    }
}

控制对象在路径点移动

using System.Collections.Generic;
using UnityEngine;

public class AstartMoveTest : MonoBehaviour
{
    public Transform moveTarget;//移动对象
    private List<AstarNode> rodeNodeList;//行走路径点
    private int moveIndex;//当前移动到第几个点
    private bool isMoveing = false;//是否正在移动过程中
    private float miniDistance = 0.1f;
    private Vector3 endPosition;
    private Vector3 moveDir;
    public float speed = 1;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    public void BeginMove(List<AstarNode> rodeNodeList)
    {
        if (rodeNodeList == null || rodeNodeList.Count < 2)
        {
            Debug.LogError("路径点异常,无法开始移动");
            return;
        }

        
        this.rodeNodeList = rodeNodeList;
        transform.position = rodeNodeList[0].position;
        isMoveing = true;
        moveIndex = 1;
        endPosition = rodeNodeList[moveIndex].position;
        moveDir = rodeNodeList[moveIndex].position - rodeNodeList[0].position;
        moveDir = moveDir.normalized;
    }

    // Update is called once per frame
    void Update()
    {
        if (!isMoveing)
        {
            return;
        }

        float distance = Vector3.Distance(transform.position, endPosition);
        // Debug.Log("distance = " + distance);
        if (distance < miniDistance)
        {
            moveIndex++;
            if (moveIndex >= rodeNodeList.Count)
            {
                isMoveing = false;
                Debug.Log("移动结束");
                return;
            }

            endPosition = rodeNodeList[moveIndex].position;
            transform.LookAt(endPosition);
            moveDir = endPosition - transform.position;
            moveDir = moveDir.normalized;
        }

        // transform.position = Vector3.Lerp(transform.position, endPosition, speed*Time.deltaTime);
        transform.position = transform.position + moveDir * Time.deltaTime * speed;
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

网格是用Gizmos中绘制的,需要调整为top视图
图中黄色网格是终点,灰色网格是起始点,白色是寻路结果,红色是障碍物点
在这里插入图片描述

资源下载地址 demo链接


网站公告

今日签到

点亮在社区的每一天
去签到