- Home /
Making a timer using WaitForSecondsRealtime without keyword 'new'?
Hi guys, I did a simple timer using WaitForSecondsRealtime within a Coroutine and it worked:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Timer : MonoBehaviour
{
Text txt;
int startTime = 10;
void Awake()
{
txt = GetComponent<Text>();
}
void Start()
{
StartCoroutine(CoroutineTimer());
}
IEnumerator CoroutineTimer()
{
while (startTime >= 0)
{
txt.text = startTime.ToString();
startTime -= 1;
yield return new WaitForSecondsRealtime(1);
}
}
}
However, if I want to reduce garbage by removing the keywords 'new', and change the IEnumerator to below, it magically doesn't work anymore:
IEnumerator CoroutineTimer()
{
WaitForSecondsRealtime countTime = new WaitForSecondsRealtime(1);
while (startTime >= 0)
{
txt.text = startTime.ToString();
startTime -= 1;
yield return countTime;
}
}
Answer by FlaSh-G · Nov 18, 2017 at 11:53 PM
It's not very magical. A WaitForSeconds object is a simple timer. The time measured does not reset without any specific reason to - beacause that would be pretty magical. As a result, the object will never wait again.
You can, however, use a custom WaitForSeconds object that has a reset function. I've written one for you:
using UnityEngine;
public class WaitTimer : CustomYieldInstruction
{
private float timeLeft;
private float lastTime;
public override bool keepWaiting
{
get
{
timeLeft -= Time.deltaTime;
return timeLeft > 0;
}
}
public WaitTimer(float time)
{
Reset(time);
}
public void Reset(float time = 0)
{
if(time == 0)
{
timeLeft = lastTime;
}
else
{
lastTime = timeLeft = time;
}
}
}
You need to reset the timer before using it again:
var timer = new WaitTimer(3);
yield return timer;
Debug.Log("3");
timer.Reset();
yield return timer;
Debug.Log("6");
timer.Reset(4);
yield return timer;
Debug.Log("10");
If you like inplace methods, you could add a function in addition to Reset that returns the timer:
public WaitTimer Again(float time = 0)
{
Reset(time);
return this;
}
and then use it like this:
var timer = new WaitTimer(3);
yield return timer;
Debug.Log("3");
yield return timer.Again();
Debug.Log("6");
Actually the question title is misleading. WaitForSeconds would work the way he uses it as it has "native" support by the coroutine scheduler. So a WaitForSeconds object contains no logic at all and just a single float value which is the desired wait time. So WaitForSeconds can be "reused" / cached without problem. Same is true for WaitForEndOfFrame and WaitForFixedUpdate.
However "WaitForSecondsRealtime" (which is not what has been written in the question title) is actually a CustomYieldInstruction and is defined like this:
public class WaitForSecondsRealtime : CustomYieldInstruction
{
private float waitTime;
public override bool keepWaiting
{
get
{
return Time.realtimeSinceStartup < this.waitTime;
}
}
public WaitForSecondsRealtime(float time)
{
this.waitTime = Time.realtimeSinceStartup + time;
}
}
So as you can see once created the internal wait time of course won't be updated when you "reuse" it
Just for completeness, the WaitForSeconds object looks like this:
[RequiredByNativeCode]
[StructLayout(Layout$$anonymous$$ind.Sequential)]
public sealed class WaitForSeconds : YieldInstruction
{
internal float m_Seconds;
public WaitForSeconds(float seconds)
{
this.m_Seconds = seconds;
}
}
The CustomYieldInstruction is pretty new. Along with that they shipped a few built-in implementations where "WaitForSecondsRealtime" is one of them. The other two are WaitWhile and WaitUntil which takes a predicate as parameter.
The CustomYieldInstruction class itself is just an IEnumerator implementation and looks like this:
public abstract class CustomYieldInstruction : IEnumerator
{
public abstract bool keepWaiting { get; }
public object Current { get { return null; } }
public bool $$anonymous$$oveNext()
{
return this.keepWaiting;
}
public void Reset()
{
}
}
Since this change they actually allow you to just yield an IEnumerator inside a coroutine and it will automatically start a new coroutine and yield on that coroutine ins$$anonymous$$d.
So when doing
yield return new WaitForSecondsRealtime(5);
you actually do
yield return StartCoroutine(new WaitForSecondsRealtime(5));