Search Unity

Running StartCoroutine from a non Monobehaviour

Discussion in 'Scripting' started by StewVanB, Mar 12, 2014.

  1. StewVanB

    StewVanB

    Joined:
    Apr 12, 2011
    Posts:
    74
    Hello All, I am programming a system to track special game missions/objectives. I want to use coroutines to track progress of a given objective as a timer runs down. I also want to be able to do something like this:

    Code (csharp):
    1.  
    2. AttackEvent theEvent = new AttackEvent(attackTarget, eventTime);
    3. theEvent.StartEvent();
    4.  
    Here is the code I have so far:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. namespace GameEvent {
    6.  
    7.     #region GameEvent Base: effects all events
    8.  
    9.     public class GameEvent{
    10.         protected float timeLimit = 0.0f;  
    11.         protected bool eventComplete = false;
    12.        
    13.         public GameEvent(){
    14.            
    15.         }
    16.        
    17.         public void StartEvent(){
    18.             //Setup event parameters
    19.             MonoBehaviour.StartCoroutine("TrackEvent");
    20.         }
    21.        
    22.         public void EndEvent(){
    23.            
    24.         }
    25.        
    26.         IEnumerator TrackEvent(){
    27.            
    28.         }
    29.     }
    30.    
    31.     #endregion
    32. //-----------------------------------------------------------------------------------------------------
    33.     #region AttackEvent: Attack an enemy or boss
    34.    
    35.     public class AttackEvent : GameEvent{
    36.         protected GameObject attackTarget;
    37.        
    38.         public AttackEvent(GameObject _attackTarget, float _timeLimit){
    39.             this.attackTarget = _attackTarget;
    40.             this.timeLimit = _timeLimit;
    41.         }
    42.        
    43.         public void StartEvent(){
    44.             StartCoroutine("TrackEvent");
    45.         }
    46.        
    47.         public void EndEvent(){
    48.            
    49.         }
    50.        
    51.         IEnumerator TrackEvent(){
    52.             while(this.timeLimit > 0.0f  !eventComplete){
    53.                 this.timeLimit -= Time.deltaTime;
    54.                 yield return new WaitForEndOfFrame();
    55.             }
    56.         }
    57.     }
    58.    
    59.     #endregion
    60. //-----------------------------------------------------------------------------------------------------
    61.     #region DefendEvent: Defend a position
    62.    
    63.     public class DefendEvent : GameEvent{
    64.         protected GameObject defendTarget;
    65.        
    66.         public DefendEvent(){
    67.            
    68.         }
    69.        
    70.         public void StartEvent(){
    71.             //Setup event parameters
    72.             //Start event coroutine
    73.         }
    74.        
    75.         public void EndEvent(){
    76.            
    77.         }
    78.        
    79.         IEnumerator TrackEvent(){
    80.            
    81.         }
    82.     }
    83.    
    84.     #endregion
    85. //-----------------------------------------------------------------------------------------------------
    86.     #region DestroyEvent: Destroy an object
    87.    
    88.     public class DestroyEvent : GameEvent{
    89.         protected GameObject destroyTarget;
    90.        
    91.         public DestroyEvent(){
    92.            
    93.         }
    94.        
    95.         public void StartEvent(){
    96.             //Setup event parameters
    97.             //Start event coroutine
    98.         }
    99.        
    100.         public void EndEvent(){
    101.            
    102.         }
    103.        
    104.         IEnumerator TrackEvent(){
    105.            
    106.         }
    107.     }
    108.    
    109.     #endregion
    110. //-----------------------------------------------------------------------------------------------------
    111.     #region EscapeEvent: Escape an area before the time runs out
    112.    
    113.     public class EscapeEvent : GameEvent{
    114.         protected GameObject escapeGoalLocation;
    115.        
    116.         public EscapeEvent(){
    117.            
    118.         }
    119.        
    120.         public void StartEvent(){
    121.             //Setup event parameters
    122.             //Start event coroutine
    123.         }
    124.        
    125.         public void EndEvent(){
    126.            
    127.         }
    128.        
    129.         IEnumerator TrackEvent(){
    130.            
    131.         }
    132.     }
    133.    
    134.     #endregion
    135. //-----------------------------------------------------------------------------------------------------
    136.     #region EscortEvent: Protect object while mving it to new location
    137.    
    138.     public class EscortEvent : GameEvent{
    139.         protected GameObject escortTarget;
    140.        
    141.         public EscortEvent(){
    142.            
    143.         }
    144.        
    145.         public void StartEvent(){
    146.             //Setup event parameters
    147.             //Start event coroutine
    148.         }
    149.        
    150.         public void EndEvent(){
    151.            
    152.         }
    153.        
    154.         IEnumerator TrackEvent(){
    155.            
    156.         }
    157.     }
    158.    
    159.     #endregion
    160. //-----------------------------------------------------------------------------------------------------
    161.     #region InfiltrateEvent: Get from A to B without being detected
    162.    
    163.     public class InfiltrateEvent : GameEvent{
    164.         protected GameObject infiltrateGoalLocation;
    165.        
    166.         public InfiltrateEvent(){
    167.            
    168.         }
    169.        
    170.         public void StartEvent(){
    171.             //Setup event parameters
    172.             //Start event coroutine
    173.         }
    174.        
    175.         public void EndEvent(){
    176.            
    177.         }
    178.        
    179.         IEnumerator TrackEvent(){
    180.            
    181.         }
    182.     }
    183.    
    184.     #endregion
    185. //-----------------------------------------------------------------------------------------------------
    186. }
    187.  
     
    DragonCoder likes this.
  2. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    You need an instance of some MonoBehaviour in order to run a coroutine.
     
    Benawtopia likes this.
  3. StewVanB

    StewVanB

    Joined:
    Apr 12, 2011
    Posts:
    74
    I figured it out! This is here for anyone who needs to know how it can work. I had to make a separate script to access the objects coroutines.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using GameEvent;
    5.  
    6. public class ObjectiveEventHandler : MonoBehaviour {
    7.    
    8.     public GameObject hammer = null;
    9.     public float maxTime = 60.0f;
    10.    
    11.     public void StartTestEvent(){
    12.         AttackEvent newEvent = new AttackEvent(hammer,maxTime);
    13.         StartCoroutine(newEvent.TrackEvent());
    14.     }
    15.    
    16.     void Update(){
    17.         if(Input.GetKeyDown(KeyCode.B)){
    18.             StartTestEvent();
    19.         }
    20.     }
    21. }
    22.  
    I also had to modify the GameEvent classes so they could be accessed via this outside handler script.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. namespace GameEvent {
    6.  
    7.     #region GameEvent Base: effects all events
    8.  
    9.     public class GameEvent{
    10.         protected float timeLimit = 0.0f;  
    11.         protected bool eventComplete = false;
    12.        
    13.         public GameEvent(){
    14.            
    15.         }
    16.        
    17.         public void EndEvent(){
    18.            
    19.         }
    20.        
    21.         public IEnumerator TrackEvent(){
    22.             yield return new WaitForEndOfFrame();
    23.         }
    24.     }
    25.    
    26.     #endregion
    27. //-----------------------------------------------------------------------------------------------------
    28.     #region AttackEvent: Attack an enemy or boss
    29.    
    30.     public class AttackEvent : GameEvent{
    31.         protected GameObject attackTarget;
    32.        
    33.         public AttackEvent(GameObject _attackTarget, float _timeLimit){
    34.             this.attackTarget = _attackTarget;
    35.             this.timeLimit = _timeLimit;
    36.         }
    37.        
    38.         public void EndEvent(){
    39.            
    40.         }
    41.        
    42.         public IEnumerator TrackEvent(){
    43.             while(this.timeLimit > 0.0f  !eventComplete){
    44.                 this.timeLimit -= Time.deltaTime;
    45.                 Debug.Log(this.timeLimit);
    46.                 yield return new WaitForEndOfFrame();
    47.             }
    48.         }
    49.     }
    50.    
    51.     #endregion
    52. //-----------------------------------------------------------------------------------------------------
    53.     #region DefendEvent: Defend a position
    54.    
    55.     public class DefendEvent : GameEvent{
    56.         protected GameObject defendTarget;
    57.        
    58.         public DefendEvent(){
    59.            
    60.         }
    61.        
    62.         public void StartEvent(){
    63.             //Setup event parameters
    64.             //Start event coroutine
    65.         }
    66.        
    67.         public void EndEvent(){
    68.            
    69.         }
    70.        
    71.         public IEnumerator TrackEvent(){
    72.             yield return new WaitForEndOfFrame();
    73.         }
    74.     }
    75.    
    76.     #endregion
    77. //-----------------------------------------------------------------------------------------------------
    78.     #region DestroyEvent: Destroy an object
    79.    
    80.     public class DestroyEvent : GameEvent{
    81.         protected GameObject destroyTarget;
    82.        
    83.         public DestroyEvent(){
    84.            
    85.         }
    86.        
    87.         public void StartEvent(){
    88.             //Setup event parameters
    89.             //Start event coroutine
    90.         }
    91.        
    92.         public void EndEvent(){
    93.            
    94.         }
    95.        
    96.         public IEnumerator TrackEvent(){
    97.             yield return new WaitForEndOfFrame();
    98.         }
    99.     }
    100.    
    101.     #endregion
    102. //-----------------------------------------------------------------------------------------------------
    103.     #region EscapeEvent: Escape an area before the time runs out
    104.    
    105.     public class EscapeEvent : GameEvent{
    106.         protected GameObject escapeGoalLocation;
    107.        
    108.         public EscapeEvent(){
    109.            
    110.         }
    111.        
    112.         public void StartEvent(){
    113.             //Setup event parameters
    114.             //Start event coroutine
    115.         }
    116.        
    117.         public void EndEvent(){
    118.            
    119.         }
    120.        
    121.         public IEnumerator TrackEvent(){
    122.             yield return new WaitForEndOfFrame();
    123.         }
    124.     }
    125.    
    126.     #endregion
    127. //-----------------------------------------------------------------------------------------------------
    128.     #region EscortEvent: Protect object while mving it to new location
    129.    
    130.     public class EscortEvent : GameEvent{
    131.         protected GameObject escortTarget;
    132.        
    133.         public EscortEvent(){
    134.            
    135.         }
    136.        
    137.         public void StartEvent(){
    138.             //Setup event parameters
    139.             //Start event coroutine
    140.         }
    141.        
    142.         public void EndEvent(){
    143.            
    144.         }
    145.        
    146.         public IEnumerator TrackEvent(){
    147.             yield return new WaitForEndOfFrame();
    148.         }
    149.     }
    150.    
    151.     #endregion
    152. //-----------------------------------------------------------------------------------------------------
    153.     #region InfiltrateEvent: Get from A to B without being detected
    154.    
    155.     public class InfiltrateEvent : GameEvent{
    156.         protected GameObject infiltrateGoalLocation;
    157.        
    158.         public InfiltrateEvent(){
    159.            
    160.         }
    161.        
    162.         public void StartEvent(){
    163.             //Setup event parameters
    164.             //Start event coroutine
    165.         }
    166.        
    167.         public void EndEvent(){
    168.            
    169.         }
    170.        
    171.         public IEnumerator TrackEvent(){
    172.             yield return new WaitForEndOfFrame();
    173.         }
    174.     }
    175.    
    176.     #endregion
    177. //-----------------------------------------------------------------------------------------------------
    178. }
    179.  
     
  4. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,698
    Adding to KelsoMRK's reply, you can pass that MonoBehaviour instance to StartEvent():
    Code (csharp):
    1.  
    2.     AttackEvent theEvent = new AttackEvent(attackTarget, eventTime);
    3.     theEvent.StartEvent(myMonoBehaviour);
    4. ...
    5. public class GameEvent {
    6.         ...
    7.         public void StartEvent(MonoBehaviour myMonoBehaviour){
    8.             //Setup event parameters
    9.             myMonoBehaviour.StartCoroutine("TrackEvent");
    10.         }
    11.  
    In the worst case, if you don't have an appropriate GameObject with a MonoBehaviour, you could always use a Singleton for this purpose.
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    I've got several different classes in my framework I've been putting together that need access to various game events and monobehaviour stuff.

    I have components that allow me to hook into game events like Update and the sort. I call them 'EventHooks'. Here's a simple UpdateEventHook:

    Code (csharp):
    1.  
    2.     [AddComponentMenu("SpacePuppy/Hooks/UpdateEventHooks")]
    3.     public class UpdateEventHooks : com.spacepuppy.SPComponent
    4.     {
    5.  
    6.         public event System.EventHandler UpdateHook;
    7.  
    8.         // Update is called once per frame
    9.         void Update()
    10.         {
    11.             if (this.UpdateHook != null) this.UpdateHook(this.gameObject, System.EventArgs.Empty);
    12.         }
    13.  
    14.         protected override void OnDestroy()
    15.         {
    16.             UpdateHook = null;
    17.         }
    18.     }
    19.  
    Now some classes I have like a timer class that I use for various things need to jab into this event. To which I create a gameobject that doesn't get destroyed ever that it hooks into. My InputManager as well needs this. I have multiple classes that are like this that I don't want to have to create a GameObject for in a specific scene... mainly because 1) it means I have to remember to add the gameobject to the scene. And 2, it means if I want to debug that scene I have to have one in it, but the final build won't need it because one would already exist in a previous scene.

    Thing is I was creating multiples of these objects that I was like... why have all of them? Why not just have one that is shared amongst them... so I created a class I call 'GameLoopEntry'. It's intended for limited use by my framework and hands out hooks to run Coroutines and listen to update events.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. using com.spacepuppy.Hooks;
    7.  
    8. namespace com.spacepuppy
    9. {
    10.  
    11.     /// <summary>
    12.     /// This class is really only for internal use by com.spacepuppy, avoid using it outside of it.
    13.     /// </summary>
    14.     internal static class GameLoopEntry
    15.     {
    16.  
    17.         #region Events
    18.  
    19.         public static event System.EventHandler EarlyUpdate
    20.         {
    21.             add
    22.             {
    23.                 _earlyUpdateHook.UpdateHook += value;
    24.             }
    25.             remove
    26.             {
    27.                 _earlyUpdateHook.UpdateHook -= value;
    28.             }
    29.         }
    30.         public static event System.EventHandler Update
    31.         {
    32.             add
    33.             {
    34.                 _updateHook.UpdateHook += value;
    35.             }
    36.             remove
    37.             {
    38.                 _updateHook.UpdateHook -= value;
    39.             }
    40.         }
    41.  
    42.         #endregion
    43.  
    44.         #region Fields
    45.  
    46.         private static GameObject _gameObject;
    47.         private static EarlyExecutionUpdateEventHooks _earlyUpdateHook;
    48.         private static UpdateEventHooks _updateHook;
    49.  
    50.         #endregion
    51.  
    52.         #region CONSTRUCTOR
    53.  
    54.         static GameLoopEntry()
    55.         {
    56.             _gameObject = new GameObject("SpacePuppy.GameLoopEntryObject");
    57.             _earlyUpdateHook = _gameObject.AddComponent<EarlyExecutionUpdateEventHooks>();
    58.             _updateHook = _gameObject.AddComponent<UpdateEventHooks>();
    59.             GameObject.DontDestroyOnLoad(_gameObject);
    60.         }
    61.  
    62.         #endregion
    63.  
    64.         #region Properties
    65.  
    66.         #endregion
    67.  
    68.         #region Methods
    69.  
    70.         //Regular
    71.  
    72.         public static Coroutine StartCoroutine(System.Collections.IEnumerator routine, bool operatesEarly = false)
    73.         {
    74.             if (operatesEarly)
    75.             {
    76.                 return _earlyUpdateHook.StartCoroutine(routine);
    77.             }
    78.             else
    79.             {
    80.                 return _updateHook.StartCoroutine(routine);
    81.             }
    82.         }
    83.  
    84.         public static Coroutine StartCoroutine(System.Collections.IEnumerable routine, bool operatesEarly = false)
    85.         {
    86.             if (operatesEarly)
    87.             {
    88.                 return _earlyUpdateHook.StartCoroutine(routine);
    89.             }
    90.             else
    91.             {
    92.                 return _updateHook.StartCoroutine(routine);
    93.             }
    94.         }
    95.  
    96.         public static Coroutine StartCoroutine(System.Delegate routine, bool operatesEarly = false, params object[] args)
    97.         {
    98.             if (operatesEarly)
    99.             {
    100.                 return _earlyUpdateHook.StartCoroutine(routine, args);
    101.             }
    102.             else
    103.             {
    104.                 return _updateHook.StartCoroutine(routine, args);
    105.             }
    106.         }
    107.  
    108.         //Radical
    109.  
    110.         public static RadicalCoroutine StartRadicalCoroutine(System.Collections.IEnumerator routine, bool operatesEarly = false)
    111.         {
    112.             if (operatesEarly)
    113.             {
    114.                 return _earlyUpdateHook.StartRadicalCoroutine(routine);
    115.             }
    116.             else
    117.             {
    118.                 return _updateHook.StartRadicalCoroutine(routine);
    119.             }
    120.         }
    121.  
    122.         public static RadicalCoroutine StartRadicalCoroutine(System.Collections.IEnumerable routine, bool operatesEarly = false)
    123.         {
    124.             if (operatesEarly)
    125.             {
    126.                 return _earlyUpdateHook.StartRadicalCoroutine(routine);
    127.             }
    128.             else
    129.             {
    130.                 return _updateHook.StartRadicalCoroutine(routine);
    131.             }
    132.         }
    133.  
    134.         public static RadicalCoroutine StartRadicalCoroutine(System.Delegate routine, bool operatesEarly = false, params object[] args)
    135.         {
    136.             if (operatesEarly)
    137.             {
    138.                 return _earlyUpdateHook.StartRadicalCoroutine(routine, args);
    139.             }
    140.             else
    141.             {
    142.                 return _updateHook.StartRadicalCoroutine(routine, args);
    143.             }
    144.         }
    145.  
    146.         #endregion
    147.  
    148.     }
    149. }
    150.  
    You may notice that I have two UpdateEventHooks, one is flagged to be the first script to fire out of all scripts so I can hook into the first update of every frame.

    Also you may notice that they inherit from SPComponent instead of MonoBehaviour, this is an abstract component I have that implements versions of Coroutines and Invoke. RadicalCoroutines are just coroutines I made that allow me to implement my own custom yield statements using a IRadicalYieldInstruction interface. And my custom Invoke allows passing in the method I want as a Delegate instead of by string name of the method.
     
    Last edited: Mar 12, 2014
  6. Paragon99

    Paragon99

    Joined:
    Nov 23, 2013
    Posts:
    54
    You can achieve an effect similar to coroutines without using any Unity code...

    Code (csharp):
    1.  
    2. IEnumerator psuedoCorotuineEnumerator;
    3. void psuedoCoroutineConsumer()
    4. {
    5.     if(<condition>)
    6.          psuedoCorotuineEnumerator = myPsuedoCoroutine().GetEnumerator();
    7.       if(psuedoCorotuineEnumerator != null)
    8.           if(!psuedoCorotuineEnumerator.MoveNext())
    9.               psuedoCorotuineEnumerator=null;
    10. }
    11.  
    12.  
    13. IEnumerable myPsuedoCoroutine()
    14. {
    15.      while(condition)
    16.      {
    17.          //doActions
    18.         yield return null;
    19.      }
    20. }
    21.  
     
    Last edited: Mar 12, 2014
    YafaceX likes this.
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Yes, but the coroutine operates on the main thread, so you'd have to hook into that somewhere.

    If you want the enumerable aspect of a coroutine, you can have that any way you like. Really, unity is exploiting the anonymous enumerator feature of .Net/mono to make coroutines.
     
  8. Paragon99

    Paragon99

    Joined:
    Nov 23, 2013
    Posts:
    54
    @lordofduct
    can you please explain what you mean? I'm not sure what you mean by anonymous enumerator... so I tried looking it up and found http://blogs.msdn.com/b/ericlippert/archive/2009/08/24/iterator-blocks-part-seven-why-no-anonymous-iterators.aspx which says .net doesn't have this feature, unless you are using it in a different sense.

    Also, Isn't everything running in the main thread unless a new thread is specifically spawned?
    The myPsuedoCoroutine function will be processed in the same thread the calling function is running on... in what instances would that not be the main thread?
    Are all scripts run on a thread other than the main thread?
    Also, if your not working with gameobjects in the psuedoCoroutine it should be thread indifferent... is operating on gameobjects not thread safe?

    Sorry, lots of questions but I have little knowledge of the nature of the threading scheme in unity.
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    1) I'm referring to creating enumeator functions. A function that returned an enumerable or enumerator, and you use yield in the function.

    2) everything runs on the main thread. But it won't run as a coroutine unless it's return chain some how makes it back to a coroutine in some way. You can't just say 'yield return null' and have it wait another frame. It has to be done from within a monobehaviour in a coroutine (note game messages can be made implicit coroutines). Or the offending method needs to be called from a coroutine and returned as a yield statement.

    the OP is asking for operating a coroutine when not in a monobehaviour.

    Your code that you posted is merely just looping over an enumerator... the enumerator just being a function that enumerates (which is an anonymous enumerator... an anonymous object that implements IEnumerator is created when that method is called). This feature isn't unique to unity, it's the very feature of .Net/Mono that unity exploits to make coroutines function.

    A coroutine is just unity loop over an enumerator every frame. If a YieldInstruction is returned by the enumerator it operates that YieldInstruction. Otherwise it waits one more frame to tell the enumerator to loop to its next entry.

    You actually could call StartCoroutine with an array. And it'd just loop over every object in the array, and it'd take the same number of frames as there are elements in the array (unless you have a YieldInstruction in that array... then it'll be the number of frames as there are elements plus whatever the YieldInstruction does).



    Note - I used the name anonymous for anonymous enumerator not because it's an offical Microsoft term. They actually don't have a workable term for the feature. I call it that because .Net DOES have anonymous types and anonymous objects. These are objects that are generated at runtime, and allow for inline data type generation so you don't have to define classes/structs explicitly. It's basically syntax sugar.

    The yield feature uses this anonymous type system. When it finds a funciton with a yield instruction in it, it generates an anonymous object of an anonymous type that implements the IEnumerator interface that will then act as the object that is looped over.
     
    Last edited: Mar 12, 2014
    edwiz7 likes this.
  10. Bubsavvy

    Bubsavvy

    Joined:
    Sep 18, 2017
    Posts:
    48
    This is brilliant! Can't believe I didn't see this. This opens a world of possibilities. Have been trying my damndest to figure out a way of making something leverage StartCoroutine without everything being a monobehavior. In a sense we can now make a system that needs to be serialized and saved to a file while using StartCoroutine to do parallel work on the data. Effectively making a "ScriptableObject" a slave to a Monobehavior's coroutine. Thanks you are awesome.
     
    edwiz7 likes this.
  11. PaulJohnJeffs

    PaulJohnJeffs

    Joined:
    Sep 7, 2017
    Posts:
    2
    I know this is an old thread but for anyone looking here in future, you can create a MonoBehaviour class within your non-MonoBehaviour class, and attach it to an un-instantiated GameObject reference, then run coroutines from it, from within the non-MonoBehaviour class.

    Code (CSharp):
    1.  
    2.     public class ClassA
    3.     {
    4.         private class ClassB : MonoBehaviour { }
    5.         private ClassB _coroutineRunner { get { return _coroutineRunner ?? InitCoroutineRunner(); } set { } }
    6.  
    7.         private ClassB InitCoroutineRunner()
    8.         {  
    9.             // Gameobject is not instantiated so it's not in the scene. It's just an object in memory.
    10.             GameObject instance = new GameObject();
    11.             instance.isStatic = true;
    12.             _coroutineRunner = instance.AddComponent<ClassB>();
    13.             return _coroutineRunner;
    14.         }
    15.  
    16.         private IEnumerator MyCoroutine(float delay)
    17.         {
    18.             // Do a thing..
    19.             yield return new WaitForSeconds(delay);
    20.             // Maybe do another thing..
    21.         }
    22.  
    23.         private void RunCoroutine()
    24.         {
    25.             _coroutineRunner.StartCoroutine(MyCoroutine(1f));
    26.         }
    27.     }
    28.  
    This way its all packed up within the class and you can run from inside the Non-Monobehaviour without having to call from outside.

    Also if you already have a MonoBehaviour in your scene, like a GameManager or something with an instance, and you have a static reference to it, you don't even need to create a GameObject and MonoBehaviour; you can simply call something like:

    GameManager.instance.StartCoroutine(MyCoroutine());

    Which is nice and neat.
     
    Last edited: Jan 25, 2020
  12. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Code (csharp):
    1.  
    2. // Gameobject is not instantiated so it's not in the scene. It's just an object in memory
    3. GameObject instance = new GameObject();
    4.  
    Not true. This creates a new GameObject in the active scene. Unless something drastic has changed in the latest versions of Unity.
     
  13. rmadsen

    rmadsen

    Joined:
    Jan 3, 2013
    Posts:
    3
    I have a case where I want to use a MonoBehavior class without requiring that it be added to any scene. A general solution in that case is:
    Code (CSharp):
    1. GameObject gameObject = new GameObject();
    2. T instance  = gameObject.AddComponent<T>();
    where T is the MonoBehavior class you need to create an instance of.
     
  14. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    As stated literally in the post above yours, this adds it to the active scene.
     
    Treegemmer likes this.
  15. mikejm_

    mikejm_

    Joined:
    Oct 9, 2021
    Posts:
    346
    This script looks useful to me because it allows you to write something self contained but I am getting a stack overflow when i try to run it.

    I think there was an error in the original method posted causing a feedback loop. I modified it to the following which seems to work fine:

    Code (csharp):
    1.  
    2.  private class ClassB : MonoBehaviour { }
    3.     private ClassB _coroutineRunnerWorker;
    4.     private ClassB _coroutineRunner {
    5.         get {
    6.             Debug.Log("GET RUN");
    7.             if (_coroutineRunnerWorker != null) {
    8.                 return _coroutineRunnerWorker;
    9.             }
    10.             return InitCoroutineRunner();
    11.         }
    12.         set { }
    13.     }
    14.  
    15.     private ClassB InitCoroutineRunner() {
    16.         // Gameobject is not instantiated so it's not in the scene. It's just an object in memory.
    17.         GameObject instance = new GameObject();
    18.         instance.isStatic = true;
    19.         _coroutineRunnerWorker = instance.AddComponent<ClassB>();
    20.         return _coroutineRunnerWorker;
    21.     }
    22.  
    23.     private IEnumerator MyCoroutine(float delay) {
    24.         // Do a thing..
    25.         yield return new WaitForSeconds(delay);
    26.         // Maybe do another thing..
    27.     }
    28.  
    29.     private void RunCoroutine() {
    30.         _coroutineRunner.StartCoroutine(MyCoroutine(1f));
    31.     }
     
    Last edited: Jan 1, 2022
    langinteger likes this.