Reputation: 1616
I wanted to implement something that works similar to yield return new WaitUntil(() => Check());
, but with one extra addition. After the Check()
condition is met it should wait x seconds checking every frame if the condition is still true.
This is my implementation:
private IEnumerator CheckCor(float waitTime)
{
bool checkFlag = true;
bool checkFlag2;
float whileTime;
while (checkFlag)
{
yield return new WaitUntil(() => Check());
checkFlag2 = true;
whileTime = waitTime;
while (whileTime > 0)
{
if (!Check())
{
checkFlag2 = false;
}
whileTime -= Time.deltaTime;
yield return null;
}
if (checkFlag2)
{
checkFlag = false;
}
}
}
where Check()
is
private bool Check();
My implementation is working perfectly fine, but it seems a bit long.
Is there any shorter way to achieve the same behavior?
(Also making it universal would be a plus e.g. yield return WaitUntilForSeconds(Check(), 3f);
, where Check() is the condition and 3f is time to check every frame for the condition. I'm guessing it could be done using CustomYieldInstruction
, but I'm not sure how it works.)
Upvotes: 2
Views: 3520
Reputation: 20249
It's not too bad to implement this as a CustomYieldInstruction
. Pass in the checker as a Func<bool>
, keep a flag to remember if you've started the timer yet or not, and reset that flag if the check function returned false at any point. You can even accept a Func<float>
to call when the timer is reset with how much time was remaining:
using UnityEngine;
public class WaitUntilForSeconds: CustomYieldInstruction
{
float pauseTime;
float timer;
bool waitingForFirst;
Func<bool> myChecker;
Action<float> onInterrupt;
bool alwaysTrue;
public WaitUntilForSeconds(Func<bool> myChecker, float pauseTime,
Action<float> onInterrupt = null)
{
this.myChecker = myChecker;
this.pauseTime = pauseTime;
this.onInterrupt = onInterrupt;
waitingForFirst = true;
}
public override bool keepWaiting
{
get
{
bool checkThisTurn = myChecker();
if (waitingForFirst)
{
if (checkThisTurn)
{
waitingForFirst = false;
timer = pauseTime;
alwaysTrue = true;
}
}
else
{
timer -= Time.deltaTime;
if (onInterrupt != null && !checkThisTurn && alwaysTrue)
{
onInterrupt(timer);
}
alwaysTrue &= checkThisTurn;
// Alternate version: Interrupt the timer on false,
// and restart the wait
// if (!alwaysTrue || timer <= 0)
if (timer <= 0)
{
if (alwaysTrue)
{
return false;
}
else
{
waitingForFirst = true;
}
}
}
return true;
}
}
}
Then, you can just use
yield return new WaitUntilForSeconds(Check, 3f);
// or
yield return new WaitUntilForSeconds(Check, 3f, (float t) => {Debug.Log($"Interrupted with {t:F3} seconds left!");});
And if you need a parameter for the checker, you can use a parameterless lambda:
yield return new WaitUntilForSeconds(() => Check(Vector3.up), 3f);
And, as usual for lambdas, be aware of any variable capture you do.
If you don't want an entire CustomYieldInstruction
, I would just use another WaitUntil
for the pause:
private IEnumerator CheckCor(float waitTime)
{
bool stillWaiting = true;
while (stillWaiting)
{
yield return new WaitUntil(() => Check());
float pauseTime = waitTime;
yield return new WaitUntil(() =>
{
pauseTime -= Time.deltaTime;
stillWaiting = !Check();
return stillWaiting || pauseTime <= 0;
});
if (stillWaiting)
{
// Stuff when pause is interrupted goes here
}
}
// Stuff after pause goes here
}
Upvotes: 5