【问题标题】:Dijkstra's Algorithm Ineffeciencies on a Hex Grid, C#, Unity3DDijkstra 在 Hex Grid、C#、Unity3D 上的算法效率低下
【发布时间】:2018-07-06 11:33:33
【问题描述】:

我正在尝试使用 3D HexGrid 地图创建一个基于回合的策略游戏,我已经实现了 dijkstra 的算法,但它的运行效率不是 100%,我不知道为什么。我也尝试实现 A*,但由于无法弄清楚如何正确实现它而不得不停止,因此我们也非常感谢您提供任何帮助。

我的单元将它的 GameObject 和它的目标的 Vector3 传递给生成路径函数,并且图表列表中的每个节点都填充了它的 x、y、z 和所有它的邻居。

移动时效率低下;在奇数 Z 平面上时沿 -X 方向,或在偶数 Z 平面上时沿 +X 方向,进行额外的步骤,如屏幕截图所示。另一个低效率是,当在 Z 平面上移动时,通常会采取额外的步骤,因为代码似乎更喜欢在接近 Z 平面之前尽可能长时间地保持它的 X 值相同。这导致该单位在开始 Z 方向移动时距离目标远 1 格,而不是在开始时负向移动 1 倍。

我将添加我的路径生成代码、邻居生成代码和我的节点类代码以及低效率发生位置的屏幕截图,因为我知道我的解释充其量是不清楚的。邻居代码确保最高的相邻瓦片是存储为邻居的瓦片(它还必须搜索类型,因为我有多种瓦片类型。

提前非常感谢您,感谢任何能够提供一些帮助或了解问题所在的人。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System;
using System.Linq;

public class UnitMasterScript : MonoBehaviour {

//  private int Number_of_neighbours = 6;
private int Number_of_neighbours = 6;
private GameObject[] Neighbours;
Node[,,] graph;

public bool MapMakerDone = false;

void Update()
{
    if (MapMakerDone == true)
    {
        // Wait for MapMaker to change MapMakerDone to true then allow rest of script to run

        GameObject Map = GameObject.Find("MapMaker");
        int WidthVal = Map.GetComponent<MapMakerFromFile>().WidthVal;
        int HeightVal = Map.GetComponent<MapMakerFromFile>().HeightVal;
        int DepthVal = Map.GetComponent<MapMakerFromFile>().DepthVal;

        // Graph of node generation code
        // Code to find which hex is to each side of this hex
        // Need to find hex to left, right, ul, ur, ll, lr
        // Need to find hex at the top of the stack adjacent
        // 0:L 1:R 2:UL 3:UR 4:LL 5:LR
        graph = new Node[WidthVal, HeightVal, DepthVal];

        for (int x = 0; x < WidthVal; x++)
        {
            for (int y = 0; y < HeightVal; y++)
            {
                for (int z = 0; z < DepthVal; z++)
                {
                    graph[x, y, z] = new Node();

                    graph[x, y, z].x = x;
                    graph[x, y, z].y = y;
                    graph[x, y, z].z = z;
                }
            }
        }

        for (int x = 0; x < WidthVal; x++)
        {
            for (int y = 0; y < HeightVal; y++)
            {
                for (int z = 0; z < DepthVal; z++)
                {

                    // Set up the x and y for the neighbour as the source so it can be used
                    int neighbourX = x;
                    int neighbourY = 0; // must always start from 0 to ensure a downstep isn't missed
                    int neighbourZ = z;
                    int neighbourType = 0;
                    int correct_type = 0;

                    GameObject Hex_Present_checker = null;
                    // First needs to check if there even is a tile at this coordinate location
                    for (neighbourType = 0; neighbourType < 5; neighbourType++)
                    {
                        Hex_Present_checker = GameObject.Find("Hex_" + x + "_" + y + "_" + z + "_" + neighbourType);
                        if (Hex_Present_checker != null)
                        {
                            correct_type = neighbourType;
                        }
                        Hex_Present_checker = null;
                    }

                    if (correct_type != 0)
                    {
                        neighbourType = correct_type;
                        // int Number_of_neighbours = 6;
                        // GameObject[] Neighbours;
                        Neighbours = new GameObject[Number_of_neighbours];

                        // For each value of each tile in neighbours find what the tile coordinates are in XYZ 
                        for (int q = 0; q < Number_of_neighbours; q++)
                        {
                            // Finds X and Z values of the neighbours
                            if (q < 2)
                            {
                                if (q == 0) { neighbourX = x + 1; }
                                if (q == 1) { neighbourX = x - 1; }
                            }
                            if (z % 2 == 1)
                            {
                                if (q == 2) { neighbourX = x; neighbourZ = z + 1; }
                                if (q == 3) { neighbourX = x + 1; neighbourZ = z + 1; }
                                if (q == 4) { neighbourX = x; neighbourZ = z - 1; }
                                if (q == 5) { neighbourX = x + 1; neighbourZ = z - 1; }
                            }
                            else
                            {
                                if (q == 2) { neighbourX = x - 1; neighbourZ = z + 1; }
                                if (q == 3) { neighbourX = x; neighbourZ = z + 1; }
                                if (q == 4) { neighbourX = x - 1; neighbourZ = z - 1; }
                                if (q == 5) { neighbourX = x; neighbourZ = z - 1; }
                            }
                            // Checks for the highest tile for the XZ coordinate and gets its Y value
                            GameObject potential_highest_ring;
                            int highest_Y = 0;
                            int correct_neighbour_type = 0;
                            for (neighbourY = 0; neighbourY < HeightVal; neighbourY++)
                            {
                                for (neighbourType = 0; neighbourType < 5; neighbourType++)
                                {
                                    potential_highest_ring = null;
                                    potential_highest_ring = GameObject.Find("Hex_" + neighbourX + "_" + neighbourY + "_" + neighbourZ + "_" + neighbourType);
                                    if (potential_highest_ring != null)
                                    {
                                        highest_Y = neighbourY;
                                        correct_neighbour_type = neighbourType;
                                    }
                                }
                            }
                            // Need to check if there is a neighbour at the given coordinates
                            // Debug.Log("Hex_" + neighbourX + "_" + highest_Y + "_" + neighbourZ + "_" + neighbourType);
                            Neighbours[q] = GameObject.Find("Hex_" + neighbourX + "_" + highest_Y + "_" + neighbourZ + "_" + correct_neighbour_type);
                            // While there is a neighbour in the neighbours array
                            // add it's coordinates to the graph node as a part of its neighbours sublist
                            if (Neighbours[q] != null)
                            {
                                graph[x, y, z].neighbours.Add(graph[neighbourX, highest_Y, neighbourZ]);
                            }
                        }
                    }
                }
            }
        }
        MapMakerDone = false;
    }
}

// List<Node> currentPath = null;


public List<Node> GeneratePathTo(GameObject SelectedUnit, Vector3 targetVec)
{
    // Dijkstra's Algorithm Implementation
    Dictionary<Node, float> dist = new Dictionary<Node, float>();
    Dictionary<Node, Node> prev = new Dictionary<Node, Node>();

    List<Node> unvisited = new List<Node>();


    Node source = graph[
        SelectedUnit.GetComponent<UnitBasicScript>().HexX,
        SelectedUnit.GetComponent<UnitBasicScript>().HexY,
        SelectedUnit.GetComponent<UnitBasicScript>().HexZ];


    // TargetNode float to int conversion
    int targetVecXInt = (int)targetVec.x;
    int targetVecYInt = (int)targetVec.y;
    int targetVecZInt = (int)targetVec.z;
    Node targetNode = graph[
        targetVecXInt,
        targetVecYInt,
        targetVecZInt];

    // Debug.Log(targetVecXInt + "_" + targetVecYInt + "_" + targetVecZInt);


    dist[source] = 0;
    prev[source] = null;


    // Initialise everything to have infinity distance since no other 
information available
    // Some nodes might not eb erachable therefore infinity is reasonable
    foreach (Node v in graph)
    {
        if (v != source)
        {
            dist[v] = Mathf.Infinity;
            prev[v] = null;
        }

        unvisited.Add(v);
    }

    while (unvisited.Count > 0)
    {
        // u is unvisited node with shortest distance
        Node u = null;
        foreach (Node possibleU in unvisited)
        {
            if (u == null || dist[possibleU] < dist[u])
            {
                u = possibleU;
            }
        }

        unvisited.Remove(u);


        if (u == targetNode)
        {
            break;
        }


        foreach (Node v in u.neighbours)
        {
            float alt = dist[u] + u.Distanceto(v);
            if (alt < dist[v])
            {
                dist[v] = alt;
                prev[v] = u;
            }
        }
    }

    if (prev[targetNode] == null)
    {
        // No route to target
        return null;
    }

    List<Node> currentPath = new List<Node>();

    Node curr = targetNode;

    while (prev[curr] != null)
    {
        currentPath.Add(curr);
        curr = prev[curr];
    }

    currentPath.Reverse();
    return currentPath;
} // End of generate path function

public class Node
{

    public int x = 0;
    public int y = 0;
    public int z = 0;

    public List<Node> neighbours;

    public Node()
    {
        neighbours = new List<Node>();
    }

    public float Distanceto(Node n)
    {
        if (n == null)
        {
            Debug.Log("Error");
        }

        return Vector2.Distance(
            new Vector3(x, y, z),
            new Vector3(n.x, n.y, n.z));
    }
}
}

代码到此结束,我知道 monobehaviour 中的所有内容都必须缩进,它在我的代码中,但是在复制到 stackoverflow 时它丢失了缩进。接下来是显示单位采取的错误路径的图片。

https://imgur.com/a/wEChdq3

如果需要任何其他信息,请告诉我,我将非常乐意提供。再次感谢您!

【问题讨论】:

  • 我浏览了您的代码,虽然我没有具体查看算法,但您的代码中有一些效率极低的部分:重复调用 GetComponent(当一个单独调用时,这很慢)和GameObject.Find。你绝对不能在这样的性能关键代码中使用GameObject.Find(它非常非常慢),或者依赖对象的(字符串)名称来查找邻居。我相信,如果您重新设计此代码以消除这些巨大的缺陷,您将看到性能的巨大提升。
  • 感谢您的建议,您完全正确,在时间效率方面需要显着提高,但现在我最担心寻路效率低下,同时我正在努力解决这个问题我将有效地重写我必须在时间方面提高其效率的内容,因为我很欣赏它还有很多不足之处。非常感谢您的意见,您会推荐什么方法在不使用 .find 的情况下定位游戏对象?再次感谢您!
  • 一些可能会有所帮助的链接:首先,您不知道如何调试自己的错误代码,而该技能至关重要。学习它:ericlippert.com/2014/03/05/how-to-debug-small-programs 其次,我对正确实施 a-star 的介绍可以在这里找到:blogs.msdn.microsoft.com/ericlippert/tag/astar(显然是从第一部分开始)。我看到初学者在使用 a-star 时最常犯的错误是不可接受的启发式方法。记住,零是一个有效的启发式,然后你就有了 Dijkstra,所以从零启发式开始,看看是否有帮助。
  • 您的代码将通过将其分解为许多小得多的方法而受益匪浅,每个方法都做好一件事,并且每个方法都有规范和测试用例。让每个方法可靠,然后你就可以依赖它。

标签: c# unity3d path-finding


【解决方案1】:

您正在使用List 而不是优先级队列,这非常低效。此外,由于您的网格具有简单的启发式,您应该考虑使用 A*,这样会更快。

【讨论】:

  • 感谢您的回复,非常感谢。抱歉,我应该解释一下,mapmakerdone 由另一个脚本设置为 true,因此我可以确保它仅在创建地图后运行,并且它在最后将自身设置为 false,因此它只能运行一次。我查看了实现 A* 但无法弄清楚,您知道任何可以提供帮助的资源吗?我进行了一些研究,但找不到与 3D 六角网格相关的任何内容?
  • @AlexB 在六角网格上和在任何其他网格上都是一样的。如果您有任何具体问题,您应该问另一个问题(可能在 cs.stackexchange.com 上)
  • @BlueRaja-DannyPflughoeft:我认为这个网站可以解决有关 A-star 的问题,但如果我要推荐另一个网站,那就是游戏开发网站。他们总是收到关于游戏寻路的问题。
【解决方案2】:

尽管我还没有解决所有明显的低效率问题,但我已经解决了算法实现的问题。我得到了瓷砖网格坐标之间的距离,这没有考虑到在六角网格上,对角线移动与水平移动具有完全相同的距离成本。因此,解决方案是在将节点网格坐标转换为世界坐标后获取距离,因为这将确保相邻图块之间的所有距离相等。

如果有人遇到同样的问题,希望这会有所帮助!

【讨论】:

  • 在您开始使用网站之前是学习如何使用该网站的好时机,但是,嘿,现在不是现在这样的好时机。首先阅读stackoverflow.com/tour 以获得简要概述,一定要阅读stackoverflow.com/help/how-to-ask。您的问题得到好的答案的机会将大大增加。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-22
  • 2012-10-09
  • 1970-01-01
  • 2016-12-04
  • 1970-01-01
相关资源
最近更新 更多