如何使我的对象统一遵循 A* 路径

问题描述 投票:0回答:1

我遵循了 youtube 教程并确保我没有遗漏任何部分,但是当我运行项目时没有任何反应,我创建的单元对象应该请求通往目标的路径,然后使用 A* 遵循该路径,最烦人的部分是 unity 或 Visual Studio 都没有显示任何错误。

以下是我的脚本:

Pathfinding.cs 脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;
using System.IO;
using UnityEngine.AI;
using System;

public class Pathfinding : MonoBehaviour
{
    //public Transform seeker, target;
    PathRequestManager RequestManager;
    Grid grid;

    void Awake()
    {
        RequestManager = GetComponent<PathRequestManager>();
        grid = GetComponent<Grid>();
    }
 
    //void Update()
    //{
    //    findPath(seeker.position, target.position);
    //}

    public void StartFindPath(Vector3 startPos, Vector3 targetPos)
    {
        StartCoroutine(findPath(startPos, targetPos));
    }
    IEnumerator findPath (Vector3 startPos, Vector3 targetPos)
    {
        Stopwatch SW = new Stopwatch();
        SW.Start();

        Vector3[] waypoints = new Vector3[0];
        bool pathSuccess = false;

        Node startNode = grid.NodeFromWorldPoint(startPos);
        Node targetNode = grid.NodeFromWorldPoint(targetPos);

        if (startNode.walkable && targetNode.walkable)
        {
            Heap<Node> openSet = new Heap<Node>(grid.MaxSize);
            HashSet<Node> closedSet = new HashSet<Node>();
            openSet.Add(startNode);

            while (openSet.Count > 0)
            {
                Node currentNode = openSet.RemoveFirst();
                //replaced with line above for quicker speeds
                //for (int i = 1; i < openSet.Count; i++)
                // {
                //   if (openSet[i].fCost < currentNode.fCost || openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost)
                //   {
                //      currentNode = openSet[i];
                //   } 
                //  }
                // openSet.Remove(currentNode);   
                closedSet.Add(currentNode);

                if (currentNode == targetNode)
                {
                    SW.Stop();
                    print("Path found:" + SW.ElapsedMilliseconds + "ms");
                    pathSuccess = true;
                    break;
                }
                foreach (Node neighbor in grid.GetNeighbors(currentNode))
                {
                    if (!neighbor.walkable || closedSet.Contains(neighbor))
                    {
                        continue;
                    }

                    int newMovementCostToNeighbor = currentNode.gCost + GetDistance(currentNode, neighbor);
                    if (newMovementCostToNeighbor < neighbor.gCost || !openSet.Contains(neighbor))
                    {
                        neighbor.gCost = newMovementCostToNeighbor;
                        neighbor.hCost = GetDistance(neighbor, targetNode);
                        neighbor.parent = currentNode;

                        if (!openSet.Contains(neighbor))
                            openSet.Add(neighbor);
                    }
                }
            }
        }
        yield return null;
        if (pathSuccess)
        {
            waypoints = RetracePath(startNode, targetNode);
        } 
                RequestManager.FinishProcessingPath(waypoints, pathSuccess);
            

        

    }
    
    Vector3[] RetracePath (Node startNode, Node endNode)
    {
        List<Node> path = new List<Node>();
        Node currentNode = endNode;

        while (currentNode != startNode)
        {
            path.Add(currentNode);
            currentNode = currentNode.parent;
        }
        Vector3[] waypoints = SimplifyPath(path);
        Array.Reverse(waypoints);
        return waypoints;
    }

    Vector3[] SimplifyPath(List<Node> path)
    {
        List<Vector3> waypoints = new List<Vector3>();
        Vector2 directionOld = Vector2.zero;

            for (int i =1 ; i < path.Count; i++)
        {
            Vector2 directionNew = new Vector2(path[i-1].GridX - path[i].GridX, path[i - 1].GridY - path[i].GridY);
            if(directionNew!=directionOld)
            {
                waypoints.Add(path[i].worldPosition);
            }
            directionOld = directionNew;
        }
            return waypoints.ToArray();
    }

    int GetDistance(Node NodeA, Node NodeB)
    {
        int DstX = Mathf.Abs(NodeA.GridX - NodeB.GridX);
        int DstY = Mathf.Abs(NodeA.GridY - NodeB.GridY);

        if (DstX > DstY)
            return 14*DstY + 10* (DstX - DstY);
        return 14*DstX + 10* (DstY - DstX);
    }
}

PathRequestManager.cs 脚本

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

public class PathRequestManager : MonoBehaviour
{

    Queue<PathRequest> PathRequestQueue = new Queue<PathRequest>();
    PathRequest currentPathRequest;

    static PathRequestManager instance;
    Pathfinding pathfinding;

    bool isProcessingPath;

    void Awake()
    {
        instance = this;
        pathfinding = GetComponent<Pathfinding>();
    }
    public static void RequestPath(Vector3 pathStart, Vector3 pathEnd, Action<Vector3[], bool> callback)
    {
        PathRequest newRequest = new PathRequest(pathStart, pathEnd, callback);
        instance.PathRequestQueue.Enqueue(newRequest);
        instance.TryProcessNext();
    }

    void TryProcessNext()
    {
        if(!isProcessingPath && PathRequestQueue.Count > 0)
        {
            currentPathRequest = PathRequestQueue.Dequeue();
            isProcessingPath = true;
            pathfinding.StartFindPath(currentPathRequest.pathStart, currentPathRequest.pathEnd);
        }
    }

    public void FinishProcessingPath(Vector3[] path, bool success)
    {
        currentPathRequest.callback(path, success);
        isProcessingPath = false;
        TryProcessNext(); 
    }
    struct PathRequest
    {
        public Vector3 pathStart;
        public Vector3 pathEnd;
        public Action<Vector3[], bool> callback;

        public PathRequest(Vector3 _start, Vector3 _end, Action<Vector3[], bool> _callback)
        {
            pathStart = _start;
            pathEnd = _end;
            callback = _callback;
        }
    }
}

Grid.cs 脚本

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

public class Grid : MonoBehaviour
{
    public bool DisplayGridGizmos;
    public LayerMask unwalkableMask;
    public Vector2 gridWorldSize;
    public float nodeRadius;
    Node[,] grid;

    float nodeDiameter;
    int gridSizeX, gridSizeY;

    void Awake()
    {
        nodeDiameter = nodeRadius * 2;
        gridSizeX = Mathf.RoundToInt(gridWorldSize.x/nodeDiameter);
        gridSizeY = Mathf.RoundToInt(gridWorldSize.y/nodeDiameter);
        CreateGrid();
    }

    public int MaxSize
    {
        get
        {
            return gridSizeX * gridSizeY;
        }
    }

    void CreateGrid()
    {
        grid = new Node[gridSizeX, gridSizeY];
        Vector3 worldBottomLeft = transform.position - Vector3.right * gridWorldSize.x / 2 - Vector3.forward * gridWorldSize.y / 2;
        for (int x = 0; x < gridSizeX; x++)
        {
            for (int y = 0; y < gridSizeY; y++){
                Vector3 worldPoint = worldBottomLeft + Vector3.right * (x * nodeDiameter + nodeRadius) + Vector3.forward * (y * nodeDiameter + nodeRadius);
                bool walkable = !(Physics.CheckSphere(worldPoint,nodeRadius,unwalkableMask));
                grid[x,y] = new Node (walkable,worldPoint, x,y);
            }
        }
    }

    public List<Node> GetNeighbors(Node node)
    {
        List<Node> neighbors = new List<Node>();

        for (int x = -1; x <= 1; x++)
        {
            for (int y = -1; y <= 1; y++)
            {
                if (x == 0 && y == 0)
                    continue;

                int checkX = node.GridX + x;
                int checkY = node.GridY + y;

                if (checkX >=0 && checkX < gridSizeX && checkY >=0 && checkY < gridSizeY)
                {
                    neighbors.Add(grid[checkX,checkY]);
                }
            }
        }
        return neighbors;
    }
    public Node NodeFromWorldPoint(Vector3 worldPosition)
    {
        float percentX = (worldPosition.x + gridWorldSize.x / 2) / gridWorldSize.x;
        float percenty = (worldPosition.z + gridWorldSize.y / 2) / gridWorldSize.y;
        percentX = Mathf.Clamp01(percentX);
        percenty = Mathf.Clamp01(percenty);

        int x = Mathf.RoundToInt((gridSizeX - 1) * percentX);
        int y = Mathf.RoundToInt((gridSizeY - 1) * percenty);
        return grid[x,y];
    }

    
    void OnDrawGizmos()
    {
        Gizmos.DrawWireCube(transform.position, new Vector3(gridWorldSize.x, 1, gridWorldSize.y));
        
            if (grid != null && DisplayGridGizmos)
            {

                foreach (Node n in grid)
                {
                    Gizmos.color = (n.walkable) ? Color.white : Color.red;
                    Gizmos.DrawCube(n.worldPosition, Vector3.one * (nodeDiameter - .1f));
                }
            }
        
    }
}   

Unit.cs 脚本

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

public class Unit : MonoBehaviour
{
    public Transform target;
    float speed = 20;
    Vector3[] path;
    int targetindex;

    void start()
    {
        PathRequestManager.RequestPath(transform.position, target.position, OnPathFound);
    }

    public void OnPathFound(Vector3[] newPath, bool pathSuccessful)
    {
        if (pathSuccessful)
        {
            path = newPath;
            StopCoroutine("FollowPath");
            StartCoroutine("FollowPath");
        }
    }

    IEnumerator FollowPath()
    {
        Vector3 currentWaypoint = path[0];

        while(true)
        {
            if (transform.position == currentWaypoint)
            {
                targetindex ++;
                if (targetindex >= path.Length)
                {
                    targetindex = 0;
                    path = new Vector3[0];
                    yield break;
                }
                currentWaypoint = path[targetindex];
            }
            transform.position = Vector3.MoveTowards(transform.position, currentWaypoint, speed * Time.deltaTime);
            yield return null;
        }
    }

    public void OnDrawGizmos()
    {
        if (path != null)
        {
            for (int i = targetindex; i < path.Length; i++)
            {
                Gizmos.color = Color.black;
                Gizmos.DrawCube(path[i], Vector3.one);

                if (i == targetindex)
                {
                    Gizmos.DrawLine(transform.position, path[i]);
                }
                else
                {
                    Gizmos.DrawLine(path[i - 1], path[i]);
                }
            }
        }
    }
}

Node.cs 脚本

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

public class Node : IHeapItem<Node> {

    public bool walkable;
    public Vector3 worldPosition;

    public int gCost;
    public int hCost;
    public int GridX;
    public int GridY;
    public Node parent;
    int heapIndex;

    public Node(bool _walkable, Vector3 _worldPos, int _Gridx, int _GridY)
    {
        walkable = _walkable;
        worldPosition = _worldPos;
        GridX = _Gridx;
        GridY = _GridY;
    }
    public int fCost
    {
        get
        {
            return gCost + hCost;
        }
    }
    public int HeapIndex
    {
        get { return heapIndex; }
        set { heapIndex = value; }
    }
    public int CompareTo(Node nodeToComare)
    {
        int compare = fCost.CompareTo(nodeToComare.fCost);
        if (compare == 0)
        {
            compare = hCost.CompareTo(nodeToComare.hCost);
        }
        return -compare;
    }
}

Heap.cs 脚本

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

//Creating a heap to make the algorithm run faster
//A heap is a binary tree where each node has 2 child nodes
//Each parent node must have a lower f.cost than the child nodes
//If a new node is added and is a lower value then the parent the values swap until the new node can no longer swap
public class Heap<T> where T : IHeapItem<T>
{
    T[] Items;
    int currentItemCount;

    public Heap(int maxHeapSize)
    {
        Items = new T[maxHeapSize];
    }

    public void Add(T item)
    {
        item.HeapIndex = currentItemCount;
        Items[currentItemCount] = item;
        SortUp(item);
        currentItemCount++;
    }

    public T RemoveFirst()
    {
        T firstItem=Items[0];
        currentItemCount--;
        Items[0] = Items[currentItemCount];
        Items[0].HeapIndex = 0;
        SortDown(Items[0]);
        return firstItem;
    }
    //update item position in the heap
    public void UpdateItem(T item)
    {
        SortUp(item);
    }

    //get number of items in the heap
    public int Count
    {
        get
        {
            return currentItemCount;
        }
    }


    //check if heap contains a specific item
    public bool Contains(T item)
    {
        return Equals(Items[item.HeapIndex], item);
    }

    void SortDown(T item)
    {
        while (true)
        {
            int childIndexLeft = item.HeapIndex * 2 + 1;
            int childIndexRight = item.HeapIndex * 2 + 2;
            int swapIndex = 0;

            if (childIndexLeft < currentItemCount)
            {
                swapIndex = childIndexLeft;

                if (childIndexRight < currentItemCount)
                {
                    if (Items[childIndexLeft].CompareTo(Items[childIndexRight]) < 0)
                    {
                        swapIndex = childIndexRight;
                    }
                }
                //check if parent is lower priority than highest priority child
                if (item.CompareTo(Items[swapIndex]) < 0)
                {
                    Swap(item, Items[swapIndex]);
                }
                else
                {
                    return;
                }
            }
            else
            {
                return;
            }
        }
    }
    void SortUp(T item)
    {
        int parentIndex = (item.HeapIndex - 1) / 2;

        while (true)
        {
            T parentItem = Items[parentIndex];
            if (item.CompareTo(parentItem) > 0)
            {
                Swap (item, parentItem);
            }
     //when the item is no longer a higher priority than the parent item break the loop
            else
            {
                break;
            }
            parentIndex = (item.HeapIndex - 1) / 2;
        }
    }
    //method to swap child node values with parent
    void Swap(T itemA, T itemB)
    {
        Items[itemA.HeapIndex] = itemB;
        Items[itemB.HeapIndex] = itemA;
        int itemAIndex = itemA.HeapIndex;
        itemA.HeapIndex = itemB.HeapIndex;
        itemB.HeapIndex = itemAIndex;
    }

}


public interface IHeapItem<T> : IComparable<T>
{
    int HeapIndex
    {
        get;
        set;
    }
}

如果有人能看到我一直想念的东西,请告诉我,这样我才能最终解决这个问题

我尝试查看脚本但什么也没看到,然后返回视频但仍然无法找出问题

c# unity3d
1个回答
0
投票

主要问题

所以首先,我想问题出在

Unit.cs
。您代码中的方法称为
start()
,它与 Unity 所需的方法
Start()
(区分大小写)不同。

如何修复

更改

Unit
类中的代码:

public class Unit : MonoBehaviour
{
    ...

    void Start()
    {
        PathRequestManager.RequestPath(transform.position, target.position, OnPathFound);
    }

    ...

然后你的

PathRequestManager()
代码被调用。

说明

在运行时,Unity 会查找您的 MonoBehavior 对象并通过反射执行它们。此过程区分大小写,因此所有特殊的 MonoBehavior 方法(如 Awake、OnEnable 等)必须与 Unity API 中描述的名称完全相同。

如何预防这类问题

内置方法

Debug.Log()
是你最好的朋友。把它放在关键的地方,比如你的
PathRequestManager.cs
中的第 24 行,以确保有一条路径可以遵循。这将在运行时帮助您检查是否一切都在正确的时间正确调用。 👍

© www.soinside.com 2019 - 2024. All rights reserved.