Search Unity

Passing in a Monobehaviour to run a Coroutine

Discussion in 'Scripting' started by opennoise, Nov 25, 2018.

  1. opennoise

    opennoise

    Joined:
    Jun 4, 2018
    Posts:
    19
    Hey! So I'm in the middle of creating the event that will fire when the player leaves a room, unloading the current scene and loading the new scene. Since the methods subscribed to this event are IEnumerators, I have to run them as coroutines, so I have to invoke each method manually through a foreach loop.

    Code (CSharp):
    1.  
    2.     public delegate IEnumerator LeavingRoom(string input);
    3.     public static event LeavingRoom SceneSwitch;
    4.  
    5.     public static IEnumerator WhenLeavingRoom(string sceneName)
    6.     {
    7.         if(SceneSwitch != null)
    8.         {
    9.             foreach(LeavingRoom method in SceneSwitch.GetInvocationList())
    10.             {
    11.                 LeavingRoom leavingRoomCoroutine = method;
    12.                 yield return StartCoroutine(leavingRoomCoroutine(sceneName));
    13.             }
    14.         }    
    15.     }
    The problem is that I get an error on
    StartCoroutine
    that says I need an object reference. Apparently, I need to pass in a Monobehaviour that will run the coroutine. That leads me to this, which compiles correctly:

    Code (CSharp):
    1.  public static IEnumerator WhenLeavingRoom(string sceneName, MonoBehaviour mono)
    2.     {
    3.         if(SceneSwitch != null)
    4.         {
    5.             foreach(LeavingRoom method in SceneSwitch.GetInvocationList())
    6.             {
    7.                 LeavingRoom leavingRoomCoroutine = method;
    8.                 yield return mono.StartCoroutine(leavingRoomCoroutine(sceneName));
    9.             }
    10.         }    
    11.     }
    The thing is, what do I pass into the inspector that will make this all run correctly? I've never dealt with monobehaviours directly like this before. It all seems so simple, but I just can't quite grasp what to do next.

    Any help would be greatly appreciated! Thanks!!
     
  2. Do not use static and you don't need this instance.
     
  3. opennoise

    opennoise

    Joined:
    Jun 4, 2018
    Posts:
    19
    That would be perfect, but the problem is that this event manager is sitting in a persistent background scene, and the script that calls the method is in a different active scene, so I can't create an object reference. Making it static clears this up, but gives me this issue instead.

     
  4. Then wherever you're calling WhenLeavingRoom you just pass in the this as a second parameter.
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    If you need a coroutine to run from outside the context of MonoBehaviours, it's common to use a static coroutine helper that's got a hidden MonoBehaviour that you start the coroutines on. Here's a subset of what we have going:

    Code (csharp):
    1. public class StaticCoroutine {
    2.     private static StaticCoroutineRunner runner;
    3.  
    4.     public static Coroutine Start(IEnumerator coroutine) {
    5.         EnsureRunner();
    6.         return runner.StartCoroutine(coroutine);
    7.     }
    8.  
    9.     private static void EnsureRunner() {
    10.         if (runner == null) {
    11.             runner = new GameObject("[Static Coroutine Runner]").AddComponent<StaticCoroutineRunner>();
    12.             Object.DontDestroyOnLoad(runner.gameObject);
    13.         }
    14.     }
    15.  
    16.     private class StaticCorutineRunner : MonoBehaviour {}
    17. }
    Note that since the object's set to DontDestroyOnLoad, you can do nasty things like have a coroutine that references things in the current scene that keeps going after that scene is unloaded, causing errors. We solve this by having two different coroutine runners - one that keeps going past scene loads, and one that doesn't.

    With that setup, your code would be the same except:
    yield return StaticCoroutine.Start(leavingRoomCoroutine(sceneName));



    The nitty-gritty about coroutines and how they run are:
    A coroutine is attached to a MonoBehaviour, and lives together with it. This has some consequences:
    - If the MonoBehaviour is destroyed - either directly, or by closing the scene - the coroutine stops running
    - If the MonoBehaviour's gameObject becomes deactivated, the coroutine stops running. It will not "keep going" if the object's re-activated.
    - The coroutine doesn't care about the MonoBehaviour's .enabled property, so it keeps running if the Monobehaviour's disabled.
     
    amitwins and angrypenguin like this.
  6. opennoise

    opennoise

    Joined:
    Jun 4, 2018
    Posts:
    19
    Do you mean to literally pass
    this
    into the parameter so it reads
    public static IEnumerator WhenLeavingRoom(string sceneName, this)
    ? Unfortunately, this doesn't compile and adds a bunch of new errors.
     
  7. No, change it back.

    Wherever you call this function you pass this in:
    Code (CSharp):
    1. WhateverisyourstaticObject.WhenLeavingRoom("mynextscene", this);
    Or use @Baste's solution.
     
  8. opennoise

    opennoise

    Joined:
    Jun 4, 2018
    Posts:
    19
    Interesting! I was thinking I'd have to make a separate script for my event manager to reference. I do have a few questions about your script though:

    1. This is all written as a separate script than my event manger script, but attached to the same game object, right?
    2. When I tried copy-pasting this into the script, it gives me a compiler error on
    StaticCoroutineRunner
    , saying that the type or namespace cannot be found. I'm guessing this has to do with establishing a Monobehavior class inside of a non-Monobehaviour class, but I'm not sure. How do you get this to work?
     
  9. opennoise

    opennoise

    Joined:
    Jun 4, 2018
    Posts:
    19
    Sooo, calling the function like that requires I add a parameter to
    WhenLeavingRoom
    , so that it can take two overloads. And because the coroutine needs to run on a monobehaviour, this is what I did.

    When the function is called, it looks like this:
    EventManager.WhenLeavingRoom(this, sceneName);


    And the function itself looks like this:
    Code (CSharp):
    1. public static IEnumerator WhenLeavingRoom(MonoBehaviour mono, string sceneName)
    2.     {
    3.         if(SceneSwitch != null)
    4.         {
    5.             foreach(LeavingRoom method in SceneSwitch.GetInvocationList())
    6.             {
    7.                 LeavingRoom leavingRoomCoroutine = method;
    8.                 yield return mono.StartCoroutine(leavingRoomCoroutine(sceneName));
    9.             }
    10.         }  
    11.     }
    The good news: This all compiles.
    The bad news: When my player opens the door, nothing happens at all, and the scenes don't switch.
     
  10. opennoise

    opennoise

    Joined:
    Jun 4, 2018
    Posts:
    19
    Hey, I just wanted to bump this thread again because I still never got this to work.

    How were you able to get the script your replied with to work? It throws a few errors for me on
    StaticCoroutineRunner
    , but the idea of getting a routine to work outside of monobehaviours sounds like just what I need.

     
  11. daxiongmao

    daxiongmao

    Joined:
    Feb 2, 2016
    Posts:
    412
    If you just copied and pasted baste’s code you need to add the correct usings to the file as well.

    using UnityEngine; etc

    Also using baste’s code you do not need a reference to a mono.

    If you want in the function you have you would replace your mono.StartCoroutine with StaticCoroutine.StartCoroutine.

    Most likely since you are wanting to do them in order you would actually need to make everything after your sceneswitch null check be a coroutine that you start with his code.
     
  12. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Not that easy to help you with "a few errors". Compiler errors? Runtime errors?
     
    angrypenguin likes this.
  13. opennoise

    opennoise

    Joined:
    Jun 4, 2018
    Posts:
    19
    Compiler errors! Like I mentioned in another post above, I get a compiler error each time
    StaticCoroutineRunner
    appears in your code, saying that the definition can't be found, and it needs an assembly reference or using directive.

    I created an
    internal class
    for it, which solves that issue, but then I get a similar compiler error for
    StartCoroutine
    .

    I also get a compiler error for
    new GameObject("[Static Coroutine Runner]").AddComponent<StaticCoroutineRunner>();
    , saying I can't use
    StaticCoroutineRunner
    as <T>.

    What were your solutions to these problems? Or is there some other part of my code that your code isn't playing nice with?
     
  14. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    This is because StaticCoroutineRunner is not a MonoBehaviour or other type of Component, and AddComponent<...> needs a Component type.

    Basically, AddComponent(...) only works with things that you could add to an object via the Inspector.

    P.S: When you have "a few errors" then copy-paste them in here so we can see what they are. We can't help much if we have to guess what went wrong.

    Also, always focus on the first error first. It will often clear up the ones afterwards.
     
  15. opennoise

    opennoise

    Joined:
    Jun 4, 2018
    Posts:
    19
    Yeah, sorry, I posted the error in my first reply to Baste a couple weeks ago earlier in the thread -- I just didn't hear back after that and figured people would read the earlier replies when I bumped the thread.

    And yeah, that's the other thing I mentioned above. I assume these issues have to do with creating the runner outside of the context of MonoBehaviours, but it's Baste's code, and he said he got it to work, so I'm just asking him how he was able to because it seems like it would be really helpful if I could get it to work too!
     
  16. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    If I copy-paste the code from my post, it compiles and works, as long as I import System.Collections and UnityEngine.

    Here's a full script that you should literally be able to paste into an empty project and have work. It's the same as before, just with imports and an example.

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using UnityEngine;
    4.  
    5. public class TestScript : MonoBehaviour {
    6.     void Start() {
    7.         StaticCoroutine.Start(TestRoutine());
    8.         Destroy(gameObject);
    9.     }
    10.  
    11.     public IEnumerator TestRoutine() {
    12.         var i = 0;
    13.         while (true) {
    14.             Debug.Log(++i);
    15.             yield return new WaitForSeconds(1f);
    16.         }
    17.     }
    18. }
    19.  
    20. public class StaticCoroutine {
    21.     private static StaticCoroutineRunner runner;
    22.  
    23.     public static Coroutine Start(IEnumerator coroutine) {
    24.         EnsureRunner();
    25.         return runner.StartCoroutine(coroutine);
    26.     }
    27.  
    28.     private static void EnsureRunner() {
    29.         if (runner == null) {
    30.             runner = new GameObject("[Static Coroutine Runner]").AddComponent<StaticCoroutineRunner>();
    31.             Object.DontDestroyOnLoad(runner.gameObject);
    32.         }
    33.     }
    34.  
    35.     private class StaticCorutineRunner : MonoBehaviour { }
    36. }
    On nomenclature: "throwing" is used for runtime exceptions, as in "throwing exceptions". When your code's compiling, the term would be "doesn't compile". When you write "It throws a few errors for me", the assumption is that you're getting runtime exceptions.
     
  17. opennoise

    opennoise

    Joined:
    Jun 4, 2018
    Posts:
    19
    Okay, so! Long story short, the code works now, and I can finally move on from this issue of mine!

    The weird thing is that I can't explain why it works now suddenly. I pasted your code in, and I got the same compiler errors once again. This time, though, I told Visual Studio to generate its own nested
    StaticCoroutineRunner
    class identical to yours as a solution to the problem....and the error just went away. It was the same code, but for some reason, because it wasn't copy-pasted in, it decided to work.

    Regardless, thanks so much!

     
  18. Saucyminator

    Saucyminator

    Joined:
    Nov 23, 2015
    Posts:
    61
    Why the following code doesn't run
    Code (CSharp):
    1. private static StaticCoroutineRunner runner;
    is because
    Code (CSharp):
    1. private class StaticCorutineRunner : MonoBehaviour { }
    is misspelled.
     
    edwiz7 likes this.