【问题标题】:Unity - Running A Pathfinding Algorithm on A Seperate ThreadUnity - 在单独的线程上运行寻路算法
【发布时间】:2014-12-31 16:21:40
【问题描述】:

我在我的 Unity 2D 游戏中实现了 A* 寻路算法。一切正常,但在搜索大地图时可能会导致卡顿。

问题是由在主线程上执行的 While-Loop 引起的。我希望算法能够在单独的线程上运行,以在函数运行时阻止游戏发抖。

我对协程的理解是它们更适合用于顺序函数,而不是像这样的繁重计算。该函数必须返回一个值或使用引用来附加一个值。

如何在不阻塞主线程的情况下实现这种占用大量 CPU 的计算? IE。多线程?

编辑:

Heisenbug 指出的协程的当前实现。

从“繁重的计算功能”中提取的不完整,应该在多个帧中分散到均匀的工作负载。

//if the daemon is currently searching
public bool Searching;

//Create list for the algorithm
Pathfinding_Path Path = new Pathfinding_Path();
List<Pathfinding_Point> OpenList = new List<Pathfinding_Point>();
List<Pathfinding_Point> ClosedList = new List<Pathfinding_Point>();

//Agent is the object that shall pathfind, position is goal, callback
public IEnumerator Pathfind(GameObject Agent, Vector3 Position, Func<Pathfinding_Path,Vector3, bool,bool> Callback)
{
    //Abort if already searching
    if (Searching)
        yield break;

    Searching = true;

    //If the target position is not clear, abort
    if (!IsClear(Position))
    {
        Searching = false;
        yield break;
    }

    //Get the size of the agent
    Vector3 AgentSize = GetSize(Agent);

    //Start the algorithm
    Pathfinding_Point start = CreatePoint(AgentSize, Agent.transform.position, Position, 0);
    //Get possible steps from the first position
    CreateAdjacent(start, Position);
    //Add the node to the search tree
    OpenList.Add(start);

    //Keep track of how many iterations the function has ran (to not keep on going forever)
    int iterations = 0;

    //If there is an object to visit and the number of iterations is allowed
    while (OpenList.Count > 0 && iterations < 250)
    {
        iterations++;

        //Get the best node and visit it
        Pathfinding_Point point = GetBest(OpenList);
        OpenList.Remove(point);
        ClosedList.Add(point);    

        //Add all neighbors to the search tree
        foreach (Pathfinding_Point adjacent in point.Adjacent)
        {
            if (!ClosedList.Contains(adjacent))
            {
                if (!OpenList.Contains(adjacent))
                {
                    adjacent.Parent = point;


                    //The goal position is near, this is goal
                    if (Vector3.Distance(adjacent.Position, Position) <= AgentSize.sqrMagnitude * 0.5f)
                    {
                        //Add the final point to the path
                        Path.Add(adjacent);

                        //Get the last point
                        Pathfinding_Point step = Path.Points[0];
                        //Track backwards to find path
                        while(step.Parent != null){
                            Path.Add(step.Parent);
                            step = step.Parent;
                        }

                        Path.Finalize();

                        //Return the final path somehow (preferably using a callback method)
                        Callback(Path, Position, false);
                        Searching = false;
                        //Don't run the function no more
                        yield break;
                    } 
                    else if (IsClear(adjacent))
                    {
                        //Add to search tree
                        CreateAdjacent(adjacent, Position);
                        OpenList.Add(adjacent);
                    }
                }
                else
                {
                    //If the score is lower this way, re-calculate it
                    if (point.G + 1 < adjacent.G)
                    {
                        adjacent.G = point.G + 1;
                        adjacent.F = adjacent.G + adjacent.H;
                    }
                }
            }
        }
    }

    //If there are no more ways to go
    if(OpenList.Count == 0)
        yield break;

    //Here, the search has exceeded its limit on 250 iterations and shall continue after a small delay
    yield return new WaitForSeconds(0.005f);
    //The callback will run this function again, until the goal is reached or if there are no more nodes to visit
    Callback(Path, Position, true);
}

应该处理搜索功能可能到达的不同情况的回调

//Path to use if it succeded, position that was the initial target, if the search is not yet finished and should be continued
bool GetPath(Pathfinding_Path Path, Vector3 pz, bool Continue)
{
    //Run the function again with the same parameters as the first time
    if (Continue)
    {
        StartCoroutine(Pathfinder.Pathfind(gameObject, pz, GetPath));
    }
    else if (Path.Points.Count > 0)
    {
        //A path has been found
        InvestigatePath = Path;
    }

    return true;
}

【问题讨论】:

  • 我不熟悉统一,如果你可以使用 Task 类,但如果你可以看看 here 看看如何使用 Task.Run()。如果您不能使用 Task 类,here 是 Unity 论坛上的一个问题,它介绍了使用 System.Threading.Thread 类的方法。
  • 如果您不发布代码,我们应该如何提供帮助?它从来没有像“把你所有的代码放在一个协程中,完成”那么简单。
  • @LearnCocos2D 抱歉不够清楚。这个问题没有涉及真正的代码。我想知道如何在统一、异步函数或任何你喜欢的方式中进行多线程处理。我可以给你代码,但它只会很混乱,因为它不会给问题增加任何内容。
  • 您的 A* 例程是否非常依赖 Unity 对象?我悲观地猜测 Unity 的方法/对象不是线程安全的,所以如果你可以将它的变量/逻辑与 Unity 的主线程分开,这基本上变成了一个非常简单的 C# 问题(并且 C# 有许多有用的多线程工具)
  • @Katana314 没有线程安全是正确的。我确实使用 Unity Physics 函数,我猜这些函数属于“统一对象”类别。

标签: c# unity3d path-finding coroutine


【解决方案1】:

您最终可以像往常一样在 C# 中使用 threads。关键是这不是一个方便的解决方案,因为您需要使线程与引擎循环保持同步。这可能不是小事。

我对协程的理解是它们更好地用于 顺序函数,而不是像这样的繁重计算。

这不是真的。协程的主要目标之一(它们只是iterator blocks)是随着时间的推移(多帧)传播计算以避免打嗝。它是一种协作式多任务处理形式,因此您将获得线程的几乎所有好处,而不会出现同步的复杂性,因为协程 will be executed 就在脚本的主循环 Update 完成之后。

使用协程,您负责每帧将执行多少计算,因此您可以自行组织代码以保持稳定的帧速率。在寻路的情况下可能是这样的:

IEnumerator PathFinding()
{
  while(!goalNodeReached)
  {
    VisitMaxNodes(maxNodesToVisit); // this function visit only a subset of the graph each frame
    yield return null;
  }
}

【讨论】:

  • 我将如何准确地拆分函数?我已经实现了一个回调参数,它负责处理是否应该再次运行搜索函数或者是否找到了最终路径。在搜索了固定数量的节点后,我告诉回调方法它应该再次运行搜索。但是我如何告诉它在这样做之前等待呢?在调用回调方法之前我是否会返回 waitforseconds?
  • @Alex:不看代码就无法回答。我的只是一个使用协程而不是线程的提示。我稍微更改了示例以使其更清楚。产生 null 将导致协程被中断并在下一帧恢复。
  • 问题已更新以显示当前的实现。
  • @Alex:快速查看您的代码,您似乎正在执行广告路径查找搜索,以在单帧中搜索最大深度为 250,如果失败,您在等待后再次运行它(你仍然会以这种方式没有解决方案)。我猜不是重新运行搜索,而是将计算分布到多个帧上的方式是在主 while 循环内进行给定次数的迭代后产生。
  • 这似乎有效。但是我 yield return waitforseconds 的事实仍然会在我告诉它的秒数内冻结主线程,这反过来也是可以理解的。我怎样才能等待一段时间而不冻结它?
【解决方案2】:

只要新线程不必与 Unity 本身交互,您应该能够以常规方式生成一个新线程并执行您的计算,有很多方法可以完成,但很难说使用哪一个。我上次使用它时,Unity 不支持某些 .NET 4.0 语言功能,例如 Tasks,但这可能已经改变。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-24
    • 1970-01-01
    • 2015-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多