0
\$\begingroup\$

I have written a simple generic implementation of the BFS algorithm, which I intend to use in several places in my Unity game. A simplified version of the class is:

class BFS {
     public abstract void OnNewNodeDiscovered(Node node);

     public void FindPath(Node start, Node end) { 
         // Implementation of the algorithm.
         // Calls OnNewNodeDiscovered whenever a new node is discovered.
     }
}

I added the method OnNewNodeDiscovered so that users of this class can do custom actions, such as logging, changing the colors of things in the scene, etc., during the algorithm.

This works fine, but now I want to create a sub-class which also, in the function OnNewNodeDiscovered, adds a time-delay, so that the progress of the algorithm can be displayed to the player. The problem is that, the only way I know for adding a time delay in Unity is to convert the function to a coroutine, which returns IEnumerator, and then "yield return new WaitForSecods(delay)". But this breaks my design of having a generic OnNewNodeDiscovered function.

My quesion is: is there an alternative way to add a time-delay inside a function that will be called from Unity? Can't I just say something like "sleep(duration)" to sleep for the specified duration and return later to the next statement point, without having to change the return type to IEnumerator?

\$\endgroup\$
3
  • \$\begingroup\$ To me it's unclear what you are trying to achieve here. Why do you want to have a delay inside OnNewNodeDiscovered? What progress of the algorithm are you trying to display? (do you want to run FindPath and have all nodes it found do something X seconds after you ran FindPath?) And why would returning an IEnumerator break your design in this example? As answer in the meantime, you could also just start a new coroutine (by calling StartCoroutine(myCoroutine(node)); inside the OnNewNodeDiscovered implementation) which does something after x seconds. \$\endgroup\$
    – troien
    Feb 4, 2020 at 9:58
  • \$\begingroup\$ To add a question based on your other comment on the answer, do you instead want FindPath to wait for OnNewNodeDiscovered to finish before continuing finding other nodes? \$\endgroup\$
    – troien
    Feb 4, 2020 at 10:01
  • 1
    \$\begingroup\$ @troien I use this BFS in a grid world, and I want to find a path from grid location X to grid location Y. during the execution of the algorithm, I want to put a certain color on the grid locations that the algorithm looks at, so that the player sees how the algorithm works (for educational purposes). If I just run this without any delay, it runs very fast, and all grid locations are colored almost immediately. So I want to add a delay so that the progression of the algorithm is seen more clearly. So yes, I want FindPath to wait for OnNewNodeDiscovered to finish. \$\endgroup\$ Feb 4, 2020 at 10:01

2 Answers 2

1
\$\begingroup\$

Based on your comments, I think you are actually looking for the thing you said you were not looking for. Below an example of how you could implement something you want, the idea is that FindPath is run like a CoRoutine, and it yields OnNewNodeDiscovered (which is an IENumerator)

Now I used an example that draws a line that alternates colors every 0.1 seconds to test whether it works as intended, but I think you should be able to convert this to your own needs.

public class Example : MonoBehaviour
{
    public void Start()
    {
        StartCoroutine(FindPath(Vector3.zero, new Vector3(100, 0, 0), 100));
    }

    public virtual IEnumerator FoundNode(Vector3 from, Vector3 to)
    {
        Debug.DrawLine(from, to, Color.red, 0.1f);
        yield return new WaitForSeconds(0.1f);
        Debug.DrawLine(from, to, Color.green, 0.1f);
        yield return new WaitForSeconds(0.1f);
    }

    public IEnumerator FindPath(Vector3 start, Vector3 end, int steps)
    {
        Vector3 step = (end - start) / steps;

        Vector3 pos = start;

        for (int i = 0; i < steps; i++)
        {
            Vector3 next = pos + step;
            yield return FoundNode(pos, next);
            pos = next;
        }
    }
}

And if you in some cases don't want a delay in OnNewNodeDiscovered, calling yield break; instead of yield return something; makes it yield immediatly (so without a delay)

\$\endgroup\$
2
  • \$\begingroup\$ This looks good. But what do I do if I want, in a different instance, to disable the line-drawing in FoundNode? I cannot just replace the method body with "return", since it is not void.. \$\endgroup\$ Feb 4, 2020 at 10:22
  • 1
    \$\begingroup\$ I think thats what my edit with yield break is about. You can use that to 'return' from the IEnumerator immediatly \$\endgroup\$
    – troien
    Feb 4, 2020 at 10:26
0
\$\begingroup\$

My method would be to add a separate function in your subclass to achieve this.

class BFS {
     public abstract void OnNewNodeDiscovered(Node node);

     public void FindPath(Node start, Node end) { 
         // Implementation of the algorithm.
         // Calls OnNewNodeDiscovered whenever a new node is discovered.
     }
}

class BFS_SubClass {
    public void OnNewNodeDiscovered(Node node) {
        ...
        // Implementation
        ...
        TimeDelay(time);
        ...
        // More implementation
    }

    private IEnumerator TimeDelay(float time) {
        yield return new waitforseconds(time);
    }
}

Calling the IEnumerator as a function should allow you to retain single thread-ness and achieve a time delay.

Disclaimer, it's been a while since I did unity c#

\$\endgroup\$
2
  • \$\begingroup\$ This does not work: the coroutine TimeDelay is called, but then immediately returns, and the algorithm continues immediately. I would like to insert a time-delay in the algorithm, so that the player can see how new vertices are found. \$\endgroup\$ Feb 4, 2020 at 9:56
  • \$\begingroup\$ Try removing the 'yield' statement \$\endgroup\$
    – Natalo77
    Feb 4, 2020 at 13:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .