A星寻路的实现,基于Unity C#

依然范特西╮ 提交于 2020-01-17 07:52:45

算法简介

A*简单高效,可用于许多寻路算法中。
在这里我们共实现Node类,PriorityQueue类,NodeManager类和AStar类。
在Node类中,我们管理每个地图块的信息,包括是否为障碍、估值等。
PriorityQueue类用于储存处理完毕的和待处理的节点。
NodeManager类用来管理我们的Node类,在这里我们使用二维数组来管理。
AStar类则是我们的算法实现类。

步骤

  1. 将起始节点放置于OpenList中。
  2. 判断OpenList中是否还有元素,如有则继续。
  3. 从OpenList中拾取首个元素,使其成为当前节点,并获取它的相邻节点。
  4. 针对各个相邻节点,判断这些节点是否位于CloseList中,若否,则使用下列公式计算整体估值 (F) : F = G + H (其中G为起始节点到当前节点的整体估值,H为当前节点到目标节点的整体估值)
  5. 在相邻节点中储存这些估值并将当前节点设为相邻节点的父节点。
  6. 将相邻节点放入OpenList中并按照F进行排序。
  7. 将当前节点放入CloseList中并从OpenList中移除。
  8. 若当前节点不位于目标节点处则返回步骤2.。

算法实现

此处将按照顺序实现Node类、PriorityQueue类、NodeManager类及主类AStar类。

Node类

using System;
using UnityEngine;
using UnityEngine.UI;

public class Node : MonoBehaviour, IComparable
{
    public float nodeTotalCost = 1.0f;  //G
    public float estimateCost = 0.0f;   //F
    public bool bObstacle;
    public Node parent = null;

    public void MarkAsObstacle()
    {
        bObstacle = true;
    }

    public int CompareTo(object obj)
    {
        Node node = (Node)obj;
        if(estimateCost < node.estimateCost)
              return -1;
        else if(estimateCost > node.estimateCost)
            return 1;
        else
            return 0;
    }

    public void ShowCost()
    {
        var text = GetComponentInChildren<Text>();
        if (bObstacle)
        {
            text.text = "Obs";
            text.color = Color.red;
        }
        else
            text.text = "F: " + estimateCost + "\n" +
                        "G: " + nodeTotalCost + "\n";
        
    }

}

Node类中有变量nodeTotalCost和estimateCost即前文提到的G(起始节点到此用的步数)和F(G+当前节点到目标节点的预估步数)。以及bObstacle用来标记这个节点是否为障碍物。parent则用来记录当前节点的上一个节点是什么。
在这里我们使用了CompareTo(object obj)这个方法并且继承了IComparable这个接口,这个是用来在把Node加入队列中进行比较排序使用的。微软官方文档,关于IComparable接口

PriorityQueue类

using System.Collections.Generic;

public class PriorityQueue
{
    private List<Node> nodes = new List<Node>();

    public int Count
    {
        get
        {
            return nodes.Count;
        }
    }

    public bool Contains(Node node)
    {
        return nodes.Contains(node);
    }

    public Node First()
    {
        if(nodes.Count > 0)
        {
            return nodes[0];
        }
        return null;
    }

    public void Add(Node node)
    {
        nodes.Add(node);
        nodes.Sort();
        
    }

    public void Remove(Node node)
    {
        nodes.Remove(node);
        nodes.Sort();
    }
}

构建后文要用的优先队列,可添加、删除元素,并在操作后对队列进行排序。可获取队列第一个元素,即最小值。以及获取队列中的元素总数和是否包含某元素。

NodeManager类

using System.Collections.Generic;
using UnityEngine;

public class NodeManager : MonoBehaviour
{
    private static NodeManager instance = null;
    public static NodeManager Instance
    {
        get
        {
            if (instance == null)
                instance = FindObjectOfType(typeof(NodeManager)) as NodeManager;
            if (instance == null)
                Debug.LogError("Could not find a NodeManager. Please add one NodeManager in the scense");
            return instance;
        }
    }

    public const float X_START = -8.5f;
    public const float Y_START = -3.0f;

    private Node[,] nodes;
    private int numOfColumns;
    private int numOfRows;
    private List<GameObject> obstacleList = new List<GameObject>();

    private void SetColumn(int col)
    {
        if (col <= 2)
            col = 2;
        else if (col > 7)
            col = 7;
        numOfColumns = col;
    }

    private void SetRow(int row)
    {
        if (row <= 2)
            row = 2;
        else if (row > 18)
            row = 18;
        numOfRows = row;
    }

    public void SetObstacle(GameObject obs)
    {
        if (obstacleList.Contains(obs))
            return;
        obstacleList.Add(obs);
        obs.GetComponent<Node>().MarkAsObstacle();
        obs.GetComponent<Node>().ShowCost();
    }

    public void SetMap(int col, int row)
    {
        SetColumn(col);
        SetRow(row);
        nodes = new Node[numOfRows, numOfColumns];
        for(int i = 0; i < numOfRows; i++)
        {
            for(int j = 0; j < numOfColumns; j++)
            {
                var n = Resources.Load<GameObject>("Node");
                n = Instantiate(n) as GameObject;
                n.transform.position = new Vector3(X_START + i, Y_START + j, 0);
                Node thisNode = n.GetComponent<Node>();
                nodes[i, j] = thisNode;
                n.GetComponent<Node>().ShowCost();
            }
        }
    }

    //设置相邻节点,上下左右
    public List<Node> GetNeighbours(Node node)
    {
        List<Node> neighbours = new List<Node>();
        for (int i = 0; i < numOfRows; i++)
        {
            for (int j = 0; j < numOfColumns; j++)
            {
                if(nodes[i,j] == node)
                {
                    
                    neighbours = AssignNeighbours(i - 1, j, neighbours);//left
                    neighbours = AssignNeighbours(i + 1, j, neighbours);//right
                    neighbours = AssignNeighbours(i, j - 1, neighbours);//down
                    neighbours = AssignNeighbours(i, j + 1, neighbours);//up
                }
            }
        }
        return neighbours;
    }
    
    private List<Node> AssignNeighbours(int row, int col, List<Node> neighbours)
    {
        if (row >= 0 && col >= 0 && row < numOfRows && col < numOfColumns)
        {
            if(!obstacleList.Contains(nodes[row, col].gameObject))
            {
                neighbours.Add(nodes[row, col]);
            }
        }
        return neighbours;
    }

}

NodeManager这里使用了一个单例。这个类主要用于管理Node对象。包括设置地图、障碍物,以及设置某个节点的相邻节点。

AStar类

这个是主类,算法具体在这里实现。

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

public class AStar : MonoBehaviour
{

    public static PriorityQueue openQueue, closeQueue;

    private static float HeuristicEstimateCost(Node currentNode, Node goalNode)
    {
        return Vector3.Distance(currentNode.transform.position, goalNode.transform.position);
    }

    public static List<Node> FindPath(Node start, Node goal)
    {
        openQueue = new PriorityQueue();
        openQueue.Add(start);
        start.nodeTotalCost = 0;
        start.estimateCost = HeuristicEstimateCost(start, goal);
        
        closeQueue = new PriorityQueue();
        Node node = null;

        while (openQueue.Count != 0)
        {
            node = openQueue.First();
            if(node == goal)
            {
                node.GetComponentInChildren<Text>().text = "goal";
                break;
            }
            var neighbours = NodeManager.Instance.GetNeighbours(node);

            foreach(Node neighbour in neighbours)
            {
                if(!closeQueue.Contains(neighbour))
                {
                    neighbour.nodeTotalCost = node.nodeTotalCost + HeuristicEstimateCost(neighbour,node);
                    neighbour.estimateCost = HeuristicEstimateCost(neighbour, goal) + neighbour.nodeTotalCost;
                    neighbour.parent = node;
                    neighbour.ShowCost();
                    if (!openQueue.Contains(neighbour))
                    {
                        openQueue.Add(neighbour);
                        neighbour.GetComponentInChildren<Text>().color = Color.blue;
                    }
                }
            }
            closeQueue.Add(node);
            node.GetComponentInChildren<Text>().color = Color.green;
            openQueue.Remove(node);
            node.GetComponentInChildren<Text>().color = Color.black;
        }

        if (node != goal)
        {
            Debug.LogError("Cannot find Goal");
        }
        return CalculatePath(node);
    }

    private static List<Node> CalculatePath(Node node)
    {
        List<Node> path = new List<Node>();
        while (node != null)
        {
            path.Add(node);
            node = node.parent;
        }
        return path;
    }
}

算法思想在前文。这里有openQueue和closeQueue。openQueue用于放置所有的已探明的节点及其相邻节点。closeQueue用于放置已探测过的节点。

结果显示

红色为障碍物,黄色为路径

项目文件

提取码:fm50

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!