【问题标题】:Pause execution of a function using a coroutine untiy使用协程统一暂停函数的执行
【发布时间】:2020-03-11 10:25:48
【问题描述】:

我正在尝试在 untiy 中实现行为树,现在我正在为每个节点/复合/动作使用 Tick() 函数

我要做的是按顺序排列,例如:

  1. 开始循环遍历节点列表

  2. 在当前节点启动tick函数

  3. 等到节点tick返回失败/成功

  4. 转到下一个节点,继续进行第1步

    我遇到的问题与第 3 步有关。我正在尝试使用协程来完成此操作。

问题在于断言为真,nodeStat var 为 None。当 WaitFor Seconds 被执行时,控制返回到Debug.Assert(nodeStat != Status.None);

public class Sequence : Composite
{
    protected override void Start()
    {
        status = Status.Running;
    }
    Status nodeStat = Status.None;
    IEnumerator Recursive(Node nod)
    {
        while (true)
        {
            Debug.Log(nod + "is running");
            Status status = nod.Tick();

            Debug.Log(Time.time + " " + status);
            if (status == Status.Running)
            {
                Debug.Log("start coroutine at : " + Time.time);
                yield return new WaitForSeconds(0.2f);
                Debug.Log("Comtinue coroutine at : " + Time.time);
            }
            else
            {

                Debug.Log("has finished coroutine");
                nodeStat = status;
                break;
            }
        }
    }
    protected override Status Execute()
    {
        foreach(Node nod in ListaNoduri)
        {
            GameManager.Instance.StartCoroutine(Recursive(nod));
            Debug.Assert(nodeStat != Status.None);
            if (nodeStat == Status.Failure)
                return Status.Failure;
        }
        return Status.Success;
    }

    protected override void Exit()
    {
        base.Exit();
    }
    public override Status Tick()
    {
        //Debug.Log(status);
        return base.Tick();
    }
}

【问题讨论】:

  • 你应该使用 yield break 来中断协程
  • 你不是在等待RecursiveExecute 中完成,@NikolayGonza 他在if 语句中是yielding,他正在使用break 退出true有条件的while
  • 我也尝试让递归函数递归:)。结果比 while 更糟糕
  • tick 是否使用多线程?如果不是,则调用中的执行将不会继续,直到 tick 方法的执行完成。
  • 不,tick 不是多线程。我遇到的问题是断言,断言永远不应该达到。从协程 nosStat 应该是 Success/Failure

标签: c# unity3d artificial-intelligence


【解决方案1】:

考虑将Execute() 设为协程:

IEnumerator Execute()
{
    foreach(Node nod in ListaNoduri)
    {
        //wait for the current node to be done before continuing
        yield return GameManager.Instance.StartCoroutine(Recursive(nod));
        Debug.Assert(nodeStat != Status.None);
        if (nodeStat == Status.Failure){
            //do something on failure 
            yield break;
        }
    }
    //do something on success
}

【讨论】:

  • 不错的解决方案!不幸的是,“执行”函数必须具有相同的返回类型(状态),因为它继承自复合材料,也继承自 Node 类。在 Tick 函数中,我使用了 Exit 的返回类型。这对我来说似乎是一个没有解决方案的问题
  • @nickk2002 此执行将不会再处​​理任何失败的节点,因此下一个刻度将看到的唯一StatusStatus.Success。如果你真的需要,你可以查看nodeStat,它已经有状态值了。我不知道你到底想做什么,但你不能指望立即从 Execute 获得结果,因为它可能需要几秒钟才能完成实际产生该结果。
  • 是的,该函数有效,但我不允许更改“执行函数”的返回类型,它继承自 Node 等,并且必须保持原样。我所做的是这个 [链接] ideone.com/ClvD6v。如果你想查看整个项目在 github [link] github.com/nickk2002/Behaviour-Trees/blob/master/BehaviourTrees/…
  • @nickk2002 我认为您的问题始于Node 设计为同步工作而Sequence 应该异步工作。不幸的是,异步是“传染性的”(请参阅​​Async All the Way),因此您必须将 Start、Execute、Tick 更改为协程。或者你可以走另一条路,让ExecuteRecursive 不是协程,但这意味着你必须每帧调用Tick 才能更新。 (有点像单体行为更新)
【解决方案2】:

请记住,协程的工作方式类似于线程(异步执行)。

请注意,作为 cmets 上的 @akaBase 点,问题是您在调用协程后才评估 nodeStat,而不是等待协程完成。

因此,您正在调度多个异步调用(每个节点一个),但您并没有等待所有这些调用。

一种方法可能是在递归中断之前添加一个事件/委托/动作,您存储一个节点的结果,然后在另一个协程(应该是您当前的执行)上评估,如果某个委托是false,结果为假。

类似:

    private List<bool> nodeExecutionResultsList = new List<bool>();
    private Status statusResult = Status.Failure;
    private Action OnAllNodesEnd = null;

    protected override void Execute()
    {
        OnAllNodesEnd = () => { Debug.Log("Every Node Ends!"); };
        foreach (Node nod in ListaNoduri)
        {
            GameManager.Instance.StartCoroutine(Recursive(nod));            
        }
    }

    IEnumerator Recursive(Node nod)
    {
        while (true)
        {
            Debug.Log(nod + "is running");
            Status status = nod.Tick();

            Debug.Log(Time.time + " " + status);
            if (status == Status.Running)
            {
                Debug.Log("start coroutine at : " + Time.time);
                yield return new WaitForSeconds(0.2f);
                Debug.Log("Comtinue coroutine at : " + Time.time);
            }
            else
            {

                Debug.Log("has finished coroutine");
                nodeStat = status;
                //Do the assert NOW, and register on the list the result
                Debug.Assert(nodeStat != Status.None);
                nodeExecutionResult.Add(nodeStat == Status.Failure);
                break;
            }
        }
    }

    private IEnumerator EvaluateAllNodes()
    {
        while(evaluating)
        {
            //there are the same number of results as nodes
            if(nodeExecutionResultsList.Count == ListaNoduri.Count)
            {
                //check if someone fails
                statusResult = (nodeExecutionResult.Contains(false)) ? Status.Failure : Status.Success;
            }
        }
        OnAllNodesEnd?.Invoke();
    }

【讨论】:

  • 所以我的 Execute 函数应该是协程的?有没有办法使用同步执行?这就是我在这种情况下所需要的。如果在“递归”函数中进行递归调用/使用一段时间,那么我会得到一个无限循环,这就是该协程背后的原因
  • 您的评估方法应该是异步的,是的,但是您可以使用同步方法启动所有协程。如果我的代码无法理解,请告诉我,如果某些内容与您当前的目标不匹配,我可以对其进行调整。
  • 我的主要目标是如果他的一个孩子失败(序列复合)使 Execute 函数失败,所以我不应该处理所有节点,然后检查它们是否失败。遇到故障我应该立即停止。
  • 如果你不想同时启动所有节点,你应该忘记协程,并在一个同步的方法中一个接一个地评估。但请记住,这将冻结您其余可能的同步执行!您也可以检查而不是等待所有节点结果,只需检查是否有人添加为假,然后退出评估例程。 (将 nodeExecutionResultsList.Count == ListaNoduri.Count 替换为 nodeExecutionResult.Contains(false))。
  • 如果我确实使用了同步功能,我猜我会陷入无限循环,Unity 会崩溃。 'code'void Recursive(Node nod) { Status status = nod.Tick(); while(status == Status.Running) { status = nod.Tick(); } 节点状态 = 状态; }
猜你喜欢
  • 2019-10-05
  • 1970-01-01
  • 2010-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-19
  • 1970-01-01
  • 2015-03-05
相关资源
最近更新 更多