Just to give some motivation to you guys, in case you haven’t worked with Unity Coroutines yet. Here is a little example what they can do for you.
Code below are two versions of the same thing. A text is faded in when Player enters a trigger and faded out if Player leaves it. Both versions are C# and run in Unity, but Version 1 is more C++ like. It uses a state machine. Version 2 uses a Coroutine.
Version 1:
public class InfoText : MonoBehaviour
{
enum States
{
FadeIn,
FadeOut,
Inner,
Outer,
}
float fadeSpeed = 1.0f;
float alpha = 1f;
int frameCount;
void Awake ()
{
stateMachine = new StateMachine<States>(OnEnterState, OnUpdateState, null);
stateMachine.SetState(States.Outer);
}
void Update()
{
stateMachine.UpdateStateMachine();
}
public void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Player")) return;
stateMachine.SetState(States.FadeIn);
}
public void OnTriggerExit(Collider other)
{
if (!other.CompareTag("Player")) return;
stateMachine.SetState(States.FadeOut);
}
void OnUpdateState(States curState)
{
// fade:
if (curState == States.FadeIn)
{
UpdateAlpha(1f);
}
else if (curState == States.FadeOut)
{
UpdateAlpha(-1f);
}
}
void OnEnterState(States lastState, States nextstate)
{
//
if (nextstate == States.Inner)
{
meshRenderer.enabled = true;
SetAlpha(1f);
}
else if (nextstate == States.Outer)
{
meshRenderer.enabled = false;
SetAlpha(0f);
}
else if (nextstate == States.FadeIn)
{
meshRenderer.enabled = true;
}
}
void UpdateAlpha(float sign)
{
alpha += fadeSpeed*sign*Time.deltaTime;
if (alpha > 1f)
{
alpha = 1f;
stateMachine.SetState(States.Inner);
}
else if (alpha < 0f)
{
alpha = 0f;
stateMachine.SetState(States.Outer);
}
SetAlpha(alpha);
}
void SetAlpha(float v)
{
alpha = v;
Color color = textMesh.color;
color.a = alpha;
textMesh.color = color;
}
}
Version 2:
public class InfoTxt : MonoBehaviour
{
public float fadeSpeed = 0.5f;
TextMesh textMesh;
float alpha = 0f; // assume Player out of trigger at start
MeshRenderer meshRenderer;
IEnumerator fadeRoutine;
void Awake()
{
textMesh = GetComponent<TextMesh>();
meshRenderer = GetComponent<MeshRenderer>();
meshRenderer.enabled = false; // set invisible
}
public void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Player")) return;
FadeInOut(1f);
}
public void OnTriggerExit(Collider other)
{
if (!other.CompareTag("Player")) return;
FadeInOut(-1f);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
Utility
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FadeInOut(float sign)
{
//
if (fadeRoutine != null)
{
StopCoroutine(fadeRoutine);
}
fadeRoutine = CoFade(sign);
StartCoroutine(fadeRoutine);
}
IEnumerator CoFade(float sign)
{
//
meshRenderer.enabled = true;
while (!FadeFinished())
{
alpha += fadeSpeed * Time.deltaTime * sign;
SetAlpha(alpha);
yield return null;
}
// ensure no rendering if nothing visible
if (alpha < 0f) meshRenderer.enabled = false;
// ensure Alpha is exactly 0 or 1:
alpha = Mathf.Clamp01(alpha);
SetAlpha(alpha);
}
bool FadeFinished()
{
if (alpha > 1f) return true;
if (alpha < 0f) return true;
return false;
}
void SetAlpha(float v)
{
alpha = v;
Color color = textMesh.color;
color.a = alpha;
textMesh.color = color;
}
}
There are some differences:
Aside from the setup Version 2 only needs one function to handle all states: CoFade(). If fading is needed, this function is started and does not end until fading is finished or it is stopped from outside by calling StopCoroutine(). It is easily readable and debugable. If you want to add a flash effect on the Mesh after it faded in, just go ahead and add the code at the end of the function. No need for another state that must be wired. If you want to call a callback function when fading finishes, just give it as a parameter to CoFade() and call it like a normal function at the end of CoFade().
There is no update() necessary for Version 2. Again, fading begins when ordered and ends when CoFade() returns. So no overhead if nothing happens.
You could easily write a generalized CoFade() that takes a Mesh or whatever and Fades it. Just fire and forget. If you want more control, save the IEnumerator and call StopCoroutine() whenever you like from wherever you like (just like in the sample above), e.g when the faded mesh dies.
Don’t think of it in terms of graphical effects or artist stuff. Think of behavior. Think of AI. No behavior trees needed, no state machines, no mambo jambo. Think of what could be done when combining state machines for high level behavior and Coroutines for low level behavior. Perhaps you could even skip state machines at all, I don’t know.
As far as I know, this is done by some Enumerator Objects handled by the system that also track state. So it should be possible to do in C++, even if usage would be a bit more clunky.
Coroutines are a killer feature. Half a beer for the guy who develops it for Unreal!