Krotox Engine › Docs › Scripting API › Systems & Utilities
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.
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);
}
}
}
| Yield | Resumes |
|---|---|
yield return null | Next 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;
OnUpdate (see the frame order).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));
StopAllCoroutines() does not cancel invokes; use CancelInvoke.nameof(Method) over a string, so renaming the method cannot silently break it.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);
| Family | Values |
|---|---|
| Linear | Linear |
| Quadratic | QuadIn, QuadOut, QuadInOut |
| Cubic | CubicIn, CubicOut, CubicInOut |
| Sine | SineIn, SineOut, SineInOut |
| Exponential | ExpoIn, ExpoOut, ExpoInOut |
| Overshoot | BackIn, BackOut |
| Bounce and elastic | BounceOut, ElasticOut |
*In* starts slowly, *Out* ends slowly, *InOut* does both. BackOut overshoots and settles, which feels good for popping UI in.
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();
}
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));
}
}