寻路算法查找每个部分是否连接到特定对象

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

我正在制作一个玩家必须旋转管道以将它们连接到水源的游戏,但是我在某些时候会出现堆栈溢出,我不知道在哪里以及为什么。是否有适合这种情况的寻路算法?

到目前为止,第一级看起来像这样:

“网格”是9x9。蓝色十字架是水源,其他管道必须检查它们是否有一条通往源头的有效路径。

每个管道对象如下所示:

它包括一个父级game object来容纳一切,浇水管道对象,一个collider来检测鼠标点击和3个circle colliders来检测与其他管道的碰撞。所有这些碰撞器的这种设置是我设法做的工作。空管和填充管都有一个polygon collider,以防止奇怪的角度与circe colliders碰撞。由于管道入口不同,需要3个circle collider物体。

现在关于代码:

我试图自己创建一个寻路算法来检查每个瓷砖是否有一个有效的水源路径。我不知道它为什么导致堆栈溢出。

这是寻路方法:

    public bool FindSourceOfWater() {
        foreach (var item in collidedList) {
            if (!checkedObjectsList.Contains(item)) {
                Pipe targetObjectScript = item.GetComponent<Pipe>();
                checkedObjectsList.Add(item);
                if (item.CompareTag("Pipes_WaterSource")) {
                    checkedObjectsList.Clear();
                    return true;
                } else {
                    targetObjectScript.checkedObjectsList.Add(gameObject);
                    if (targetObjectScript.FindSourceOfWater()) {
                        checkedObjectsList.Clear();
                        return true;
                    }
                }
            }
        }
        checkedObjectsList.Clear();
        return false;
    }

代码的作用:

  • 每个当前碰撞的项目都会添加到列表中。 foreach贯穿该列表。
  • 如果目标对象不在已检查对象的列表中,请继续。
  • 在目标对象中获取相同的脚本,并将该对象添加到已检查的对象列表中。
  • 如果来自目标对象的标记为math,则清除已检查的对象列表并返回true。这意味着调用该方法的对象连接到水源。
  • 如果标记不匹配,请将此对象添加到目标的已检查对象列表中(以防止无限循环)。
  • 现在它调用目标FindSourceOfWater方法。
  • 如果调用的方法返回true,则此实例也返回true。
  • 如果没有,则继续进行下一个碰撞对象。

在更新期间调用这些方法:

    private void Update() {
        if (collidedList.Count != 0) {
            isConnectedToWaterSource = FindSourceOfWater();
        } else {
            isConnectedToWaterSource = false;
        }
        if (isConnectedToWaterSource && !filledPipe.activeSelf) {
            filledPipe.SetActive(true);
        } else if (!isConnectedToWaterSource && filledPipe.activeSelf) {
            filledPipe.SetActive(false);
        }
    }

StackOverflow错误链接到此行:

if (item.CompareTag("Pipes_WaterSource")) {

Its intended to return true if it has a valid connection to the water source tile. But i guess its调用该方法的次数太多了。也许是因为它在更新中被调用了?所以每个人都在同时检查水源。

c# unity3d path-finding
2个回答
1
投票

对于上下文,这个问题空间被称为Graph Traversal(如果你想进一步研究这些类型的东西),这里似乎没有需要递归。另外,你的变量名称中包含“list”意味着你正在使用List<T>,但是HashSet<T>在O(1)时间内执行Contains()(而不是在O(n)时间内执行List<T>)除了确保其内容是唯一的;它更适合您的问题。

要解决您的问题,您可以使用HashSet<T>Stack<T>;您已经检查过的项目之一,还有一项待检查的项目。虽然仍有待检查的项目,但请弹出一个并进行评估。如果它已连接到尚未检查的任何内容,请将其添加到选中的集合并将其推入堆栈。

这是你的算法,略有修改:

public bool FindSourceOfWater() {
    //Prep collections with this object's connections
    var checkedSet = new HashSet<ItemType>(collidedList);
    var remainingStack = new Stack<ItemType>(collidedList);

    //Are there items left to check?
    while (remainingStack.Count > 0) {
        //Reference the next item and remove it from remaining
        var item = remainingStack.Pop();
        Pipe targetObjectScript = item.GetComponent<Pipe>();

        //If it's the source, we're done
        if (item.CompareTag("Pipes_WaterSource")) {
            return true;
        } else {
            //Otherwise, check for new items to evaluate
            //(You'll have to publicly expose collidedList for this)
            foreach (var newItem in targetObjectScript.collidedList) {
                //HashSet.Add returns true if it's added and false if it's already in there
                if (checkedSet.Add(newItem)) {
                    //If it's new, make sure it's going to be evaluated
                    remainingStack.Push(newItem);
                }
            }
        }
    }

    return false;
}

注意:你也可以使用Queue<T>进行广度优先搜索而不是Stack<T>(这使得这是一个深度优先遍历)。


1
投票

我会创建一个管理器对象,为你处理这个,而不是在你拥有的每个管道对象的更新中运行它,你只需要在管理器对象中运行一次,并让管理器更新所有管道,或者轮询管道更新方法中的管理员类。

免责声明:这只是一个示例算法,当然可以进行代码改进。

public class WaterConnectionManager
{
    static IList<Pipe> WaterConnectedPipes = new List<Pipe>();
    static IList<Pipe> AllPipes = new List<Pipe>();

    static void UpdatePipes()
    {
        //get the starting point for this algorithm
        Pipe waterSource = GetWaterSource();

        //recurse for all connected pipes
        UpdateWaterConnectedPipesList(waterSource);

        //Update each pipe with its current status
        foreach(Pipe pipe in AllPipes)
        {
            pipe.IsWaterConnected = WaterConnectedPipes.Contains(pipe);
        }
    }

    static void UpdateWaterConnectedPipesList(Pipe sourcePipe)
    {
        //create a method that returns connected pipes on your Pipe script.
        IEnumerable<Pipe> connectedPipes = sourcePipe.GetConnectedPipes();

        foreach(Pipe connectedPipe in connectedPipes)
        {
            //prevent infinite recursion
            if (WaterConnectedPipes.Contains(connectedPipe))
            {
                continue;
            }

            //store these connected pipes for later recursions/iterations
            WaterConnectedPipes.Add(connectedPipe);

            //recurse into the connected pipe, to find its connected pipes.
            UpdateWaterConnectedPipesList(connectedPipe);
        }
    }

    static Pipe GetWaterSource()
    {
        return AllPipes.First(p => p.IsWaterSource);
    }

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