[C#] Function that can work in multiple instances?

I’m implementing some code that allows an object to blend to a specific color over a short period of time.
Here is an example of how the coroutine is called:

StartCoroutine(ColorChange(obj.GetComponent<Renderer>(), Color.cyan));

The color is the color to blend to.

Here is the coroutine:

IEnumerator ColorChange(Renderer rend, Color col) {
        var startTime = Time.time;
        var colStart = rend.material.color;
        float t = 0f;
        while (t < 1f) {
            t = (Time.time - startTime) / 0.25f;
            rend.material.color = Color.Lerp (colStart, col, t);
            yield return null;
        }
    }

This was working fine when a single object was using this bit of code, and it didn’t have to worry about it being called while it was already changing.

The objects that get this color fade are only temporarily referenced and then (the reference) is discarded, making way for the next object. Multiple objects are being fed through this coroutine from inside the same script, causing obvious problems.

Is there a way to have a function like this that can be called by multiple objects at anytime without all the objects needing their own copy of the code? There has to be an efficient way of doing this, without having to copy and paste this code so that every single object gets it’s own copy.

Thanks as always.

Couldn’t you just write a helper class? And have the IEnumerator be static and just call it in itself?

I did some research on your suggestion, but was unsuccessful in my attempts.
I learned that I must apparently make an instance of the monobehaviour or something, which half makes sense for my situation. Anyways, here’s the changes I made:

this.StartCoroutine(ColorChange(obj.GetComponent<Renderer>(), Color.cyan));
public static IEnumerator ColorChange(Renderer rend, Color col) {

These changes had zero effect. Probably due to the example I used not being a solution to my exact problem.
Could you possibly elaborate on how the helper class would help? To my knowledge those just help with inspector organization.

Thanks for the help.

I don’t think the idea above will work for this.

You can create a script deriving from MonoBehaviour and make it a singleton.

public class CoroutineHelper : MonoBehaviour
{
     public static Instance { get; private set; }

     void Awake()
     {
          Instance = this;
     }

     public IEnumerator ColorChange(Renderer rend, Color col)
     {
          // Your code...
     }
}

Then you just call it from another class like this

StartCoroutine(CoroutineHelper.Instance.ColorChange(rend, col));

Make sure you always have a GameObject in your scene with the CoroutineHelper script attached.

@Ecocide
Thanks!
For some reason, I’m getting a parser error on this line where the first bracket is.:

public static Instance { get; private set; }

It’s missing the type:

public static CoroutineHelper Instance { get; private set }

@Ecocide has the right idea, but you want the code on the calling side to look like this:

CoroutineHelper.Start(ColorChange(rend, col))

That’s pretty easy to achieve:

//in CoroutineHelper:

public static void Start(IEnumerator coroutine) {
    Instance.StartCorotuine(coroutine);
}

You also don’t want to have to place the coroutine helper in every scene - you’re going to forget, and or it’s going to be destroyed, or something. It’ll also clutter your scene.

A much better version is to have the CorotuineHelper be responsible for creating itself when needed. A nice pattern is this:

private static CoroutineHelper instanceBacker;
private static CoroutineHelper instance {
    get {
        if (instanceBacker == null) {
            instanceBacker = new GameObject("[Corotine Helper]").AddComponent<CoroutineHelper>();
        }
        return instanceBacker;
    }
}

Note that both are private - in my opinion, you shouldn’t be accessing the object from outside, as that’ll clutter the API. The end result would be this:

public class CoroutineHelper : MonoBehaviour {

    private static CoroutineHelper instanceBacker;
    private static CoroutineHelper instance {
        get {
            if (instanceBacker == null) {
                instanceBacker = new GameObject("[Corotine Helper]").AddComponent<CoroutineHelper>();
            }
            return instanceBacker;
        }
    }

    public static void Start(IEnumerator coroutine) {
        instance.StartCoroutine(coroutine);
    }
}

You could make it a lot more functional and prettier - I would not have CorotineHelper inherit from MonoBehaviour, but rather have a private, inner class that does, and put that as the instanceBacker. That would prevent users from mistakenly putting CorotuineBacker on a GameObject, when that clearly shouldn’t happen. CoroutineHelper should actually probably be static.

Our game is doing this very thing to handle enemies flashing red when they’re damaged. We’re calling the class “StaticCoroutine”, because that’s a better name for what’s going on, but it’s pretty much the same thing.

1 Like

@Baste Wow thanks!
If you haven’t been able to tell yet, I’m not super strong in the programming area.

From what I can gather, I need to leave my coroutine in the script that is calling the coroutine, right?
So that the script that calls this line:

CoroutineHelper.Start(ColorChange(obj.GetComponent<Renderer>(), Color.cyan));

Also has this bit of code somewhere else in the script:

public static IEnumerator ColorChange(Renderer rend, Color col) {
        var startTime = Time.time;
        var colStart = rend.material.color;
        float t = 0f;
   
        while (t < 1f) {
            t = (Time.time - startTime) / 0.1f;
            rend.material.color = Color.Lerp (colStart, col, t);
            yield return null;
        }
    }

And has this at the top?

public static CoroutineHelper Instance { get; private set; }

And the code below goes on a script that I never attach to anything?

public class CoroutineHelper : MonoBehaviour {
    private static CoroutineHelper instanceBacker;
    private static CoroutineHelper instance {
        get {
            if (instanceBacker == null) {
                instanceBacker = new GameObject("[Coroutine Helper]").AddComponent<CoroutineHelper>();
            }
            return instanceBacker;
        }
    }
    public static void Start(IEnumerator coroutine) {
        instance.StartCoroutine(coroutine);
    }
}

Was there a step I missed? I get the same results as I did initially.
Maybe what’s happening will help give you a clearer idea of what might be going wrong:

It’s a VR game, and this code is to highlight objects that the player puts the controller in.
The code with the coroutine in it is applied to each controller.
Each controller makes a list of objects that are inside of its trigger, and runs the coroutine with properties varying on whether they are entering or exiting the trigger (fade to cyan if in trigger, fade to white if leaving).
The code works fine when the controller enters one box. The color fades and everything works as it should.
If the controller exits the box, it snaps to the target color.
If the controller enters another box while currently in another box, the second box snaps to the target color.

Did I follow the steps incorrectly, or must the problem lie somewhere else in my code?
Thank you everyone for your help btw.

I would actually suggest starting the coroutine on the object doing the colour changing. That way you won’t need to handle the object being destroyed mid coroutine.

You don’t need that, no.

The idea with the CorotuineHelper is that it’s a single, global object that runs your coroutines. In my example, it’s the instance field in CoroutineHelper. Since the method CorotuineHelper.Start is static, it’s available from anywhere.

You should read up on the difference between static and non-static things, but the tl;dr version is that static ones are global*.

My answer was mostly about how to do a good, static coroutine runner. If it’s the correct way to do things here depends on how it’s being used.

Reading from your comments, it looks like coroutines might be a bad idea, because of this:

Now, what happens if you quickly enter and exit the trigger a bunch of times? You’ll have a bunch of simultaneous coroutines all messing with the same object. You’re also making the assumption that the start color is the opposite one of the one you want to come from - that’s not the case if you’re in the middle of a fade.

What I’d do is this: Put a script on each thing that needs to change color. The script just needs the renderer, and a target color, and should then always move towards the target:

public Renderer renderer;
public float speed;

private Color targetColor;
private Material targetMaterial;

void Awake() {
    targetMaterial = renderer.material;
}

void Update() {
    targetMaterial.color = Color.Lerp(targetMaterial.color, targetColor, Time.deltaTime * speed);
}

public void SetTargetColor(Color color) {
    targetColor = color;
}

Note that the Lerp method is “wrong” - it lerps from the current value to the target value with a semi-constant time. This means that it will change color the further the colors are apart, and take a very long time getting to the target color. That effect generally looks really good, though.

If you do this, you can drop all of the coroutine stuff. Instead of starting the coroutine on the renderer, just call SetTargetColor on this script.