Editor Update

Unity’s coroutines don’t run in the editor when the app is not playing, but MEC coroutines can.

Editor coroutines are generally used to give the developer a preview of an effect before the game runs, or to perform update or networking sync tasks that need to be done before the actual app is compiled. Editor coroutines will all be destroyed when the user hits the play button, and will not run while in play mode. Time.time and Time.deltaTime will not work while in editor mode, instead you should use Timing.LocalTime and Timing.DeltaTime.

Be conservative when using editor coroutines: An infinite loop or a very processor intensive function here can destroy your entire project. Always make sure to have a backup of your project available whenever you mess with these. Also keep in mind that any changes you make to an object in your scene in the editor will change the initial state of that object when you run or compile the app. For example, you could make an editor coroutine that made your character wink at the developer before play mode, but this would have the side effect of making the character randomly start mid-wink occasionally when they started or compiled the app.

In order to run in the editor, set the Segment to EditorUpdate or EditorSlowUpdate. Make sure your class has the [ExecuteInEditMode] tag attached, and then use them normally. Here is an example:

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

[ExecuteInEditMode]
public class EditorTesting : MonoBehaviour 
{

    void OnEnable()
    {
        Timing.RunCoroutine(_RunOverAndOver(), Segment.EditorUpdate);
    }

    IEnumerator<float> _RunOverAndOver()
    {
        while(true)
        {
            if(!enabled)
                yield break;

            Timing.RunCoroutine(_MoveThisObject(), Segment.EditorUpdate);

            yield return Timing.WaitForSeconds(1f);
        }
    }
        

    IEnumerator<float> _MoveThisObject()
    {
        double startTime = Timing.LocalTime;
        Vector3 direction = Random.onUnitSphere;

        while(Timing.LocalTime - startTime < 7d)
        {
            Vector3 tmp = transform.position;
            tmp += direction * Timing.DeltaTime;
            transform.position = tmp;

            yield return 0f;
        }
    }
}

Realtime Update

Another timing segment you can use in MEC Pro is RealtimeUpdate. This segment is just like update, but Timing.LocalTime and Timing.DeltaTime ignore Unity’s timescale. This can be useful while controlling menus that happen while your game is paused. (If you pause by turning the timescale to 0.)

Layers and Tags

The free version of MEC only has tags, but MEC Pro adds layers. Collectively tags and layers are called graffiti because they are both just ways to identify a particular instance of a coroutine. The only real difference between a layer and a tag is that a layer is an integer and a tag is a string, but in MEC Pro you have the option of supplying one, the other, or both. Once you supply graffiti for an instance, you can pause/resume, kill, or use that graffiti for RunCoroutineSingleton.

In Unity every GameObject is assigned a unique number, which can be accessed by calling gameObject.GetInstanceID(). The instance id can change every time you run the application, but it is guaranteed that no other gameObject instance will be assigned the same id during a single run. So even if you have a swarm of enemies that all have the same scripts attached to them you can still run them on a layer which is the instance id of the gameObject they were created on and then kill all coroutines attached to one particular enemy using

Timing.KillCoroutines(enemy.gameObject.GetInstanceID());

 
If you had, say, an amnesia gun you could graffiti all your enemy’s AI logic with both the instance id and a tag:

Timing.RunCoroutine(_EnemyAI(), gameObject.GetInstanceID(), "AI");

// Somewhere else in code:
void HitEnemyWithAmnesiaGun(EnemyController enemy)
{
    Timing.KillCoroutines(enemy.gameObject.GetInstanceID(), "AI");
}

 
KillCoroutines only kills the instance that match all the graffiti that you pass in, so if you coded it right then the above code would make the enemy stand there and do nothing until you killed it, but would also leave any other coroutines that might be running on that particular character untouched.

Unity’s coroutines will always cancel all of the coroutines that are associated with a GameObject whenever you disable that object. MEC coroutines don’t have this behavior by default (but you can add it using CancelWith). Sometimes you would rather just pause a coroutine while it’s disabled and then resume it as soon as the object is reenabled. That sort of pattern is really easy to set up in MEC like this:

void Start ()
{
    Timing.RunCoroutine(_MoveUpAndDown(), gameObject.GetInstanceID());
}

void OnEnable()
{
    Timing.ResumeCoroutines(gameObject.GetInstanceID());
}

void OnDisable()
{
    Timing.PauseCoroutines(gameObject.GetInstanceID());
}

RunCoroutineSingleton

You often want to run a coroutine, but you don’t want to run multiple copies of that coroutine. One common use case is to have a button that visibly pops onto the screen and back out as it is enabled and disabled. You might write a coroutine that expands the transform of that button every frame in a very pleasing lerp effect, but find that if the user clicks back and forth very quickly then more than one coroutine can be started at the same time in a very unpleasing way.

RunCoroutineSingleton allows you to fix that. The normal way to use it is to apply a unique tag to every coroutine in the set (for this example that would be every coroutine that moves that button). You can then call Timing.RunCoroutineSingleton(_ButtonPopIn(button.tranform, “HealthButtonMovementSet”);

There is also a boolean you can pass in at the end which determines whether RunCoroutineSingleton will avoid starting the new coroutine you are passing in or overwrite any matching coroutines with this new one. If you pass nothing in (like the line above) it will default to overwriting any matches.

Normally you would use tags and/or layers to identify conflicting coroutines, but if you want to control things more manually you can also store a handle to a coroutine and pass that into RunCoroutineSingleton. Below are some examples of usage:

Timing.RunCoroutineSingleton(_ButtonPopIn(button.transform), "HealthButtonMovementSet");
Timing.RunCoroutineSingleton(_ButtonPopIn(button.transform), "HealthButtonMovementSet", false);

CoroutineHandle handle; // This would normally be a private class variable.
handle = Timing.RunCoroutineSingleton(_ButtonPopIn(button.transform), handle);
handle = Timing.RunCoroutineSingleton(_ButtonPopIn(button.transform), Segment.FixedUpdate, handle);

Pause and Resume

In More Effective Coroutines Pro you can pause all coroutines that have a particular layer or tag and resume them later.

This could be useful, for instance, if you had a two layer menu system. If you tagged every movement coroutine with the string “layer1” and opened a menu on layer 2 you could easily stop all movement on layer1 with the command “Timing.PauseCoroutines(“layer1″);”. Later, when your layer 2 menu closed you could resume all movement on layer 1 right where it left off by calling “Timing.ResumeCoroutines(“layer1″);”

Remember that with tags the string has to match exactly every time, so “layer1” is not the same as “Layer1”, “layer 1”, or “laier1”. Always make sure you can spell all tags consistently and watch out for capitalization and spaces (both are ok, but you have to use them the same way every time).

Controlling Coroutines by Handle

Whenever you call Timing.RunCoroutine(…) a CoroutineHandle object is returned. That object can be used to make one coroutine wait for another using “yield return Timing.WaitUntilDone(handle);”. However, in MEC Pro the CoroutineHandle can do more.

Using Tag or Layer you can set or retrieve the associated graffitti.

CoroutineHandle handle = Timing.RunCoroutine(_TestCoroutine());

string oldTag = handle.Tag;
handle.Tag = "newTag"; 

if(handle.Layer == null) // No layer is assigned
    handle.Layer = gameObject.GetInstanceID();

Note: Layer is a nullable int (int?) so it may require casting into a regular int in your code, and you should check whether the value is null when you query either tags or layers (a null value means that there is no tag or layer assigned.)

Segment can retrieve or change the timing segment that the coroutine is running in.

handle.Segment = Segment.SlowUpdate;

 
IsRunning, IsPaused, and IsValid
IsRunning returns true until the coroutine terminates, and then it returns false. Paused and/or locked coroutines are considered to be running.

IsPaused returns true if the coroutine is paused or locked.

IsValid returns true if the coroutine handle has ever pointed to a valid coroutine (regardless of whether that coroutine is currently running).

Extension Methods

Extension methods can be extremely powerful tools. They allow you to run the same coroutine, but change the way it runs while running it. That probably sounds confusing, so here’s an example:

Delay
Delay runs the coroutine after a delay.

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using MovementEffects;

public class ButtonExample : MonoBehaviour
{
    public Button button1;
    public Button button2;
    public Button button3;


    void Start ()
    {
        Timing.RunCoroutine(_MoveBackAndForth(button1).Delay(1f));
        Timing.RunCoroutine(_MoveBackAndForth(button2).Delay(2f));
        Timing.RunCoroutine(_MoveBackAndForth(button3).Delay(3f));
    }

    private IEnumerator<float> _MoveBackAndForth(Button myButton)
    {
        Vector3 myPos = myButton.transform.localPosition;
        float time = 0f;

        while(time < 2f)
        {
            myPos.x += Timing.DeltaTime;
            time += Timing.DeltaTime;
            transform.localPosition = myPos;
            yield return Timing.WaitForOneFrame;
        }

        while (time < 4f)
        {
            myPos.x -= Timing.DeltaTime;
            time += Timing.DeltaTime;
            transform.localPosition = myPos;
            yield return Timing.WaitForOneFrame;
        }
    }
}

The code above will simply move three buttons, first 2 units to the right over 2 seconds, and then 2 units to the left. The interesting part is that during the Timing.RunCoroutine calls we used the Delay function to add a different delay to each call, so button1 will start moving after one second, button2 will start moving after 2 seconds, and button3 after 3 seconds. This is far cleaner than passing in the delay and putting it at the top of the function.

CancelWith
This can take either a GameObject or a function that returns a bool. If you pass in a GameObject then the coroutine will automatically be terminated if the GameObject is destroyed or disabled. You might want to use this if your coroutine is moving some UI element and that UI element might occasionally end up being destroyed before the movement completes. CancelWith will ensure that the coroutine quits cleanly without throwing any exceptions. If you want to safely modify two or three game objects there are overloads to pass in two or three at a time. If you want to be safe from more than three game objects going out of scope then you can chain calls to CancelWith.

If you pass in a function then the coroutine will be terminated as soon as that function returns false.

int framesLeft = 100;
GameObject obj1;
GameObject obj2;
GameObject obj3;
GameObject obj4;

void Start()
{
    Timing.RunCoroutine(_Coroutine().CancelWith(obj1, obj2, obj3).CancelWith(obj4));
    Timing.RunCoroutine(_Coroutine().CancelWith(CancelFunction));
}

bool CancelFunction()
{
    if(framesLeft <= 0)
        return false;

    framesLeft--;
    return true;
}

 
Append and Prepend
These two functions can be used to chain two (or more) coroutines together. So this would turn right and then turn left:

Timing.RunCoroutine(_TurnRight().Append(_TurnLeft()));

They can also be used to append a delegate to the end of a coroutine. So this might be used to run a coroutine and then destroy the object once it was done:

Timing.RunCoroutine(_MoveToFinalPosition(obj1).Append(delegate { Destroy(obj1); }));

Of course in many cases you could have also just added the line to Destroy(obj1) to the end of the movement coroutine, but if you are using the same move function in several places and you don’t normally want to destroy the object at the end then this can be a clean way to add that functionality without fracturing your code base.

There are also many cases where you might want to call some event once a coroutine has finished. If that event is related more to how you are calling the coroutine than what the coroutine is doing then it is better to encapsulate the code for the event in the place that is calling it, and this pattern does that.

Superimpose
This quirky function superimposes two coroutines into a single handle. The combined coroutine won’t finish until both of its contributing coroutines are done. This can be combined with the WaitUntilDone function to make a coroutine wait until both functions finish before continuing. Here’s an example of that:

void Start()
{
    var handle = Timing.RunCoroutine(_NetworkStream1().Superimpose(_NetworkStream2()));
    Timing.RunCoroutine(_RunWhenDone(handle));
}

IEnumerator<float> _NetworkStream1()
{
    // Networking stuff happens here.
}

IEnumerator<float> _NetworkStream2()
{
    // Other networking stuff happens here.
}

IEnumerator<float> _RunWhenDone(IEnumerator<float> handle)
{
    yield return Timing.WaitUntilDone(handle);
    // This part gets run as soon as both networking streams are done.
}

 
Hijack
This fun little function alters the return value of a coroutine. This can be useful for cutscenes or replays where you want the same code to be executed but in slow motion.

float slowdown = 0.1f;

Timing.RunCoroutine(_MoveButton().Hijack(input =>
{
    if(input <= Timing.LocalTime)
        input = (float)Timing.LocalTime;
    return input + slowdown;
}));

All extension functions are very lightweight additions, so feel free to use them liberally or chain them together if you like.