Krotox Engine › Docs › Scripting API › Systems & Utilities

Coroutines, Invoke & tweens

Running code over time without timers in every script: coroutines and all the yield instructions, Invoke and InvokeRepeating for delayed and repeating calls, and Tween helpers with easing curves for moving, scaling, rotating and fading.

Coroutines

A coroutine is a method that can pause with yield return and continue later. Use them for sequences that happen over time: cutscenes, spawning waves, a delayed explosion, a fade.

using System.Collections;
using KrotoxEngine;

public class WaveSpawner : Entity
{
    public Prefab EnemyPrefab;

    void OnCreate()
    {
        StartCoroutine(Waves());
    }

    IEnumerator Waves()
    {
        for (int wave = 1; wave <= 5; wave++)
        {
            for (int i = 0; i < wave * 3; i++)
            {
                Instantiate(EnemyPrefab, WorldPosition);
                yield return new WaitForSeconds(0.5f);
            }
            // Wait until every enemy is gone before the next wave
            yield return new WaitUntil(() => Entity.FindAllWithTag("Enemy").Length == 0);
            yield return new WaitForSeconds(2.0f);
        }
    }
}
YieldResumes
yield return nullNext frame.
new WaitForSeconds(s)After s seconds of scaled time (stops while Time.TimeScale is 0).
new WaitForSecondsRealtime(s)After s real seconds, even while paused.
new WaitForFrames(n)After n frames.
new WaitUntil(() => condition)Once the condition is true.
new WaitWhile(() => condition)Once the condition is false.
StartCoroutine(Other())When that coroutine finishes.
new WaitForEndOfFrame(), new WaitForFixedUpdate()Next frame. They exist for ported code and are not synchronized to the end of rendering or the physics step.
Coroutine handle = StartCoroutine(Waves());
StopCoroutine(handle);
StopAllCoroutines();
bool done = handle.IsDone;

Invoke

For "do this once after a delay" or "every N seconds", you don't need an iterator:

Invoke(nameof(Respawn), 3.0f);                    // once, 3 s from now (scaled time)
InvokeRepeating(nameof(SpawnPickup), 1.0f, 5.0f); // first after 1 s, then every 5 s
Invoke(() => Destroy(), 2.0f);                    // a delegate works too

CancelInvoke(nameof(SpawnPickup));                // cancel one method's pending calls
CancelInvoke();                                   // cancel everything on this script
bool waiting = IsInvoking(nameof(Respawn));

Tweens

Tween helpers are coroutines that animate a value with an easing curve. Start them with StartCoroutine, or yield return them inside another coroutine to wait for them.

// Move, scale and rotate this entity
StartCoroutine(Tween.MoveTo(this, new Vector3(10, 5, 0), 2.0f, Ease.BounceOut));
StartCoroutine(Tween.ScaleTo(this, Vector3.One * 1.5f, 0.3f, Ease.BackOut));
StartCoroutine(Tween.RotateTo(this, new Vector3(0, 0, 90), 0.5f, Ease.CubicOut));   // Euler degrees

// Animate any value: float, Vector2, Vector3 or Vector4
var sprite = GetComponent<SpriteRendererComponent>();
StartCoroutine(Tween.To(0.0f, 1.0f, 0.4f, Ease.CubicOut, a => sprite.Color = new Vector4(1, 1, 1, a)));

// Keep animating while the game is paused (menus)
StartCoroutine(Tween.To(from, to, 1.0f, Ease.SineInOut, v => Translation = v, unscaled: true));

// Directions and rotations along an arc
StartCoroutine(Tween.Slerp(fromDir, toDir, 0.5f, Ease.QuadInOut, d => m_Aim = d));

// A raw 0 to 1 value, and evaluating a curve yourself
StartCoroutine(Tween.Curve(1.0f, Ease.ElasticOut, t => m_Pulse = t));
float eased = Tween.Evaluate(Ease.QuadOut, 0.5f);

Easing curves

FamilyValues
LinearLinear
QuadraticQuadIn, QuadOut, QuadInOut
CubicCubicIn, CubicOut, CubicInOut
SineSineIn, SineOut, SineInOut
ExponentialExpoIn, ExpoOut, ExpoInOut
OvershootBackIn, BackOut
Bounce and elasticBounceOut, ElasticOut

*In* starts slowly, *Out* ends slowly, *InOut* does both. BackOut overshoots and settles, which feels good for popping UI in.

Sequencing tweens

IEnumerator CollectAnimation()
{
    yield return Tween.ScaleTo(this, Vector3.One * 1.3f, 0.1f, Ease.QuadOut);     // pop
    yield return Tween.MoveTo(this, Translation + new Vector3(0, 1, 0), 0.4f, Ease.CubicOut);
    var sprite = GetComponent<SpriteRendererComponent>();
    yield return Tween.To(1.0f, 0.0f, 0.2f, Ease.Linear, a => sprite.Color = new Vector4(1, 1, 1, a));
    Destroy();
}

A button that pops

public class PopButton : Entity
{
    public override void OnAwake()
    {
        GetComponent<UIButtonComponent>().OnHoverEnter += () =>
            StartCoroutine(Tween.ScaleTo(this, Vector3.One * 1.08f, 0.15f, Ease.BackOut, unscaled: true));
        GetComponent<UIButtonComponent>().OnHoverExit += () =>
            StartCoroutine(Tween.ScaleTo(this, Vector3.One, 0.15f, Ease.QuadOut, unscaled: true));
    }
}