Static Coroutines?

Hey everyone!

I didn’t find anything in the Unity documentation on this specific topic.
There are some examples on static methods similar to this, though:

SomeMath.cs

public static class SomeMath {

public static int Add (int a, int b) {
    return a+b
}

}

So I did some testing with something similar to this:

BlinkObject.cs

IEnumerator Start () {
    yield return new WaitForSeconds(5.0f);
    Blink.Blink1(this.gameObject.GetComponent<Renderer>(),0.2f,1.0f);
}

Blink.cs

public static class Blink {

public static IEnumerator Blink1 (Renderer _Renderer, float brightness0, float brightness1) {
    _Renderer.material.SetFloat("_Emission",brightness1);
    yield return WaitForSeconds(1.0f);
    _Renderer.material.SetFloat("_Emission",brightness0);
}

}

, which worked fine.

But what about using this for a chain of lights that maybe uses different delay times and calculates data while the coroutine is running?

BlinkObject.cs

IEnumerator Start () {
    yield return new WaitForSeconds(5.0f);
    Blink.Blink1(this.gameObject.GetComponent<Renderer>(),0.2f,Random.Range(0.5f,2.0f));
}

Blink.cs

public static class Blink {

public static IEnumerator Blink1 (Renderer _Renderer, float brightness0, float brightness1, float waitTime) {
    YieldInstruction wait1 = new WaitForSeconds(waitTime);
    float brightness05 = brightness1/2.0f;
    _Renderer.material.SetFloat("_Emission",brightness05);
    yield return wait1;
    _Renderer.material.SetFloat("_Emission",brightness0);
    yield return wait1;
    _Renderer.material.SetFloat("_Emission",brightness1);
    yield return wait1;
    _Renderer.material.SetFloat("_Emission",brightness0);
}

}

Best wishes,
Shu

Coroutines have to be attached to a specific GameObject. They’ll be attached to whatever GameObject calls StartCoroutine on the coroutine. You can make the coroutine method (like Blink1) static, but you still have to call StartCoroutine from a MonoBehaviour in a non-static method so that it gets attached to a GameObject and runs as a Coroutine. Your example BlinkObject.cs won’t work correctly because your second line needs to use StartCoroutine() rather than calling the method directly. If you just call the method directly like that it will throw a warning in the console and will only run up to the first yield and then just stop.

1 Like

Or try More Effective Coroutines [FREE] | Animation Tools | Unity Asset Store

1 Like

Or simply make a singleton MonoBehaviour that ties into all of the various Unity events (Update, LateUpdate, FixedUpdate, etc) and invoke public events for each as they occur. This would let you tie into those loops from any static class by adding a listener to whatever event type you like, and stop by removing the listener.

By taking this a step further, you can use the same MonoBehaviour to add timed yield instructions to a sorted list which are invoked during whatever event cycle is occurring when the time is up- this isn’t functionally much different from the way Unity handles coroutines internally, and the GameObject instance it’s tied to can be set to DontDestroyOnLoad and you never have to worry about whether it’s enabled/disabled or destroyed. Taking it even further, you can have multiple time scale paradigms running simultaneously so the same “15 second yield instruction” actually takes less time, or more, depending on the time scale type you use for the instruction and its current state in the game.

Just some ideas. Look into @lordofduct 's Spacepuppy Framework (specifically the GameLoop, to start with) to see how this can be implemented.

1 Like

Both true, though keep in mind, the reason that Unity decided to attach Coroutines to GameObjects instead of making the developer manage them is because Coroutines automatically get stopped when their GameObject is destroyed, so you can spawn a creature that blinks with the coroutine and then destroy that creature and the coroutine will automatically stop. If you put your coroutines on a different object or use a library to create static ones, you need to make sure to always stop them manually before destroying any objects that they reference, or you’ll end up throwing “referencing destroyed object” exceptions everywhere.

1 Like

Thanks for your replies!

@makeshiftwings

“They’ll be attached to whatever GameObject calls StartCoroutine on the coroutine.”

I cannot confirm this when testing the script I just wrote. Of course, BlinkObject.cs is attached to GameObjects that start the coroutine Blink1. But there will be no Blink.cs component on the GameObject that calls Blink.cs’s coroutine Blink1.
I actually tried setting up something like the Blink scripts I originally posted, which I didn’t test running in the Editor, those were just examples. As you said, though, StartCoroutine() was missing.
Now I actually wrote the scripts so that they run in the Editor (using the Standard Shader):

BlinkObject.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlinkObject : MonoBehaviour {

IEnumerator Start () {
    yield return new WaitForSeconds(5.0f);
    Renderer _Renderer = this.gameObject.GetComponent<Renderer>();
    StartCoroutine(
        Blink.Blink1(_Renderer,
        _Renderer.material.GetColor("_EmissionColor"),
        new Color32(255,255,255,255), 0.5f
        )
    );
}

}

Blink.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class Blink {

public static IEnumerator Blink1 (
    Renderer _Renderer, Color32 col0, Color32 col1, float waitTime
    ) {
    YieldInstruction wait1 = new WaitForSeconds(waitTime);
    Color32 col0025 = Color32.Lerp(col0,col1,0.25f);
    _Renderer.material.SetColor("_EmissionColor",col0025);
    yield return wait1;
    _Renderer.material.SetColor("_EmissionColor",col0);
    yield return wait1;
    _Renderer.material.SetColor("_EmissionColor",col1);
    yield return wait1;
    _Renderer.material.SetColor("_EmissionColor",col0);
}

}

@Brathnann

Thanks, this looks quite interesting! Maybe I download it to take a look at the code! But I try to avoid dependencies by all means.

@DonLoquacious

Intersting! I actually want to use a singleton to activate the coroutines rather than the GameObjects themselves. I just didn’t post it like that since the other approach seemed easier for me to make clear what problems might show up when working like that.

My main question is whether or not there can be some sort of interference when calling Blink1 using different parameters at the same time.

Using a singleton, it would look rather like this (not tested):

```csharp
*using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlinkObjects : MonoBehaviour {

public GameObject blinkObjects = new GameObject[5];
float waitTimes = {0.5f,0.75f,1.0f,1.25f,1.5f};
IEnumerator Start () {
Renderer _Renderer = new Renderer();
while (true) {
yield return new WaitForSeconds(5.0f);
for (int i = 0; i < blinkObjects.Length; i++) {
_Renderer = blinkObjects[i].GetComponent();
StartCoroutine(
Blink.Blink1(
_Renderer,
_Renderer.material.GetColor(“_EmissionColor”),
new Color32(255,255,255,255),
waitTimes[i]
)
);
}
}
}

}
_
```_
I haven’t taken a look at events yet, since I thought there may be a performance drawback.
Actually, for 60fps animations, I want to use Update() in scripts that are attached to specific GameObjects instead of coroutines for performance reasons.
_
@makeshiftwings *_
When using a singleton, reference errors should not show up because of coroutines that were stopped by destroyed GameObjects, right?

Right, I don’t mean that it will attach a Blink component to the object, just that the coroutine itself and its state are invisibly tied to whatever GameObject calls StartCoroutine. You won’t be able to see it in the inspector if that’s what you’re looking for.

_*

[quote]_
When using a singleton, reference errors should not show up because of coroutines that were stopped by destroyed GameObjects, right?
_
[/quote]
*

[quote]_
*[/quote]
*

Well, it depends. In your example here, you are passing _Renderer to Blink1(). If you only ever pass the renderer of the same GameObject that you’re calling StartCoroutine on, like you do in BlinkObject, then you should be fine. Because the coroutine will automatically stop when you destroy the BlinkObject. But if you call StartCoroutine from SomeOtherObject and pass BlinkObject’s renderer, or you use a library from the asset store that gives you non-attached coroutines and pass BlinkObject’s renderer, then if you destroy BlinkObject before the coroutine finishes, you will get a destroyed reference error because the coroutine will not be automatically stopped; instead it will try to access that renderer after it’s already been destroyed.

1 Like

@makeshiftwings
Thanks for your reply!

I was indeed taking a look at the GameObjects components in the inspector.

by that, do you mean a copy of that coroutine remains in memory for each GameObject calling it? If so, there should be no interferences, since any call is specific and further calls won’t modify the previous ones, right?
If there is no interference, I could write a set of static coroutine tools that help animating in the whole project.

About the last part: I see. You’re right. Right now, for my current purposes, though, I guess there won’t be such errors. However, it’s good to know coroutines stay with the GameObject that called it, hence continuing if the GameObject itself keeps persisting.

Easy Fix honestly, Just have your coroutine do a quick check if its parent object exists before it tries to do anything, this way it is not trying to pass off to or read from or act on a mono object which may be in garbage collection. It only stops when absolutely destroyed, not on disable. So always good to be safe, that .000001 miliseconds wont hurt ya.

Another similar context which would explain what I mean, doing magic spells you check that the target is there before sending your particles/effects otherwise you’ll get those errors.

I am so happy to see someone learning about them and actually using them right for a change! Acting on game objects to run things with delays to keep overhead off the main game loop is perfect! So tired of seeing omg ima just do a coroutine to find out if my character can wave.

1 Like

@Xype

Thanks for your suggestion!
I thought of an approach like this:

IEnumerator coroutine;
StopCoroutine(coroutine);*

*This would stop the coroutine, right? So calling this before destroying the parent object would work, too?

StopCoroutine(coroutine) works for a coroutine with the name coroutine.

C# Coroutines in static functions

If you will look at that guys little example see how StartCoroutine is called, doing it like that he can also StopCoroutine(“PerformCoroutine”) because it has a name to reference.

There are 3 possible arguements for StopCoroutine which can be found in the Unity Manual easily.

So yes, but I wanted to make sure I was clear :slight_smile:

1 Like

Thanks for clarifying!
I actually prefer
public void StopCoroutine(IEnumerator routine); over
public void StopCoroutine(string methodName);,
but that’s just a question of taste, I guess!