Coroutines
A coroutine is a piece of code that runs across many frames instead of finishing in one — the tool for "wait, then do something," "wait, then do something else, then wait again," or "keep adjusting a value a little bit every frame until it reaches a target." A health bar draining smoothly instead of snapping, a delay before an enemy attacks, a door that waits for a fade to finish before it opens — all the same shape.
Starting one
using JoystickEngine;
public class Example : GameEntity
{
protected override void OnCreate()
{
RunCoroutine(SayHelloLater());
}
private System.Collections.IEnumerator SayHelloLater()
{
yield return 2.0f; // pause here for 2 seconds
Log.Info("Hello, two seconds later");
}
}A coroutine is an ordinary C# method that uses yield return — reads top to bottom like normal sequential code, even though its body actually runs across many separate frames. yield return is where it pauses and where it later resumes, picking up exactly where it left off.
RunCoroutine returns a handle you can hold onto if you might need to stop it early — see Stopping a coroutine below.
What you can yield return
Three things, and that's the whole vocabulary:
yield return null; // pause for exactly one frame
yield return 1.5f; // pause for 1.5 seconds
yield return SomeOther(); // run a nested coroutine to completion, then continue| You yield return | What happens |
|---|---|
null | Resumes on the very next frame. Use this inside a loop that needs to check or update something every frame while it runs. |
a float | Resumes once that many seconds have passed. Use this for a plain delay. |
another coroutine (an IEnumerator) | Runs that one to completion first, then resumes this one. Use this to break a long coroutine into smaller, reusable pieces. |
Example: animating a value over time
private System.Collections.IEnumerator FadeOut(UIImage image, float duration)
{
float elapsed = 0.0f;
Vector4 startColor = image.Color;
while (elapsed < duration)
{
elapsed += Time.DeltaTime;
float t = System.Math.Min(elapsed / duration, 1.0f);
image.Color = new Vector4(startColor.X, startColor.Y, startColor.Z, startColor.W * (1.0f - t));
yield return null;
}
}This is the general shape for smoothly moving any value toward a target: loop, step the value a little closer each frame, yield return null, repeat until you arrive. Time covers Time.DeltaTime and why UI code usually wants it over the alternative.
Stopping a coroutine
private CoroutineHandle? m_Handle;
protected override void OnCreate()
{
m_Handle = RunCoroutine(SomeLongRunningThing());
}
public void Cancel()
{
if (m_Handle != null)
StopCoroutine(m_Handle);
}Stopping one that's already finished, or stopping the same one twice, is safe and does nothing. StopAllCoroutines() stops every coroutine this entity is currently running.
If you also override OnUpdate
Starting a coroutine works whether or not your script overrides OnUpdate — you don't need one just to use RunCoroutine. But if your script does override OnUpdate and also uses coroutines, add one line:
protected override void OnUpdate(float ts)
{
base.OnUpdate(ts); // keeps this entity's coroutines advancing
// ... your own per-frame logic ...
}Without it, your override replaces the coroutine-advancing behavior instead of adding to it — ordinary C# inheritance, not a coroutine-specific quirk — and this entity's coroutines will stop making progress. A script that never overrides OnUpdate at all doesn't need to think about this.
See also
- GameEntity —
RunCoroutine/StopCoroutine/StopAllCoroutinesare members of this base class, same asEnabledand everything else on that page. - UI Scripting — Moving and resizing elements is the same "step a value toward a target every frame" shape, applied to a Rect Transform instead of a color.