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 MEC;

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.