Hi there, I was debugging a bug in my game and found something interesting.
I start a Coroutine from OnTriggerStay and do a waiting while loop where I add Time.deltaTime.
Inside the while loop I ‘yield return null’ and add Time.deltaTime to the a timer.
So in the first iteration Time.deltaTime returns 0.1f because the Coroutine was started form a method which belongs to the Physics System.
The documentation says that Time.deltaTime returns Time.fixedDeltaTime when running inside the FixedUpdate. So far so good.
But after the first yield return null, Time.deltaTime returns the normal delta between frames again.
When the ‘isAttacking’ while loop begins again, the first Time.deltaTime is 0.1f (fixedDeltaTime ) again.
100% of the coroutine code up to the first yield always executes immediately.
In this case you’re starting it from a physics callback, which is not the same as when yield return null; resumes it.
Here is some timing diagram help:
Use caution too because your outer while(isAttacking) loop does NOT have a yield in it, so if you ever change the time-check logic the game will lock up rock solid and everything will die. If that happens, no other code will be able to clear isAttacking.
Unity will lock up 100% of the time EVERY millisecond your scripting code is running.
Nothing will render, no input will be processed, no Debug.Log() will come out, no GameObjects or transforms will appear to update.
Absolutely NOTHING will happen… until your code either:
returns from whatever function it is running
yields from whatever coroutine it is running
As long as your code is looping, Unity isn’t going to do even a single frame of change. Nothing.
No exceptions.
“Yield early, yield often, yield like your game depends on it… it does!” - Kurt Dekker
Here’s a handy tool that might help you track down unknown infinite loops:
NOTE: if you do Pokemon exception handling (“gotta try/catch them all!”) this won’t work, and then you are on your own. Good luck. That is why Pokemon exception handling is bad.
Starting a coroutine from OnTriggerStay doesn’t mean that the coroutine runs at the same rate as physics or that Time.deltaTime will return Time.fixedDeltaTime. yield return null waits for the next Update frame, not the next FixedUpdate frame. If you want to wait for the next FixedUpdate frame then use WaitForFixedUpdate.
That makes sense. As Kurt-Dekker explained above, any coroutine code before the first yield return null will run instantly when called. In your case, since you start the coroutine in fixed update, the first occurrence of Time.deltaTime returns the fixed delta time. All occurrences after yield return null though will run in the update loop, which is why they return the normal delta time.
Exactly, but also in my example code, when the while starts again, the first deltaTime is a fixedDeltaTime again somehow.
[Edit]
This is not true in a second test.
That’s funny, the guy in the other thread has the exact same but opposite issue
It looks like a coroutine can change it’s context where it runs and therefore what deltaTime returns, depended on what you do inside the routine.
I mean in the code you’ve posted, we don’t see where isAttacking would be set to false, so this loop would run indefinitely until the game object its on is disabled or destroyed.
So we can’t see what context that this coroutine would “start again”.
But if you want it to run in tune with the physics engine, then the answer as mentioned is to use WaitForFixedUpdate to keep the coroutine in tune with the physics step.
Otherwise the behaviour you’re getting is expected.
This is and remains a common pattern which gets spread for no good reason. Nothing about attacking requires you to use a coroutine, and in fact encapsulating game logic in coroutines just makes your code a lot harder to debug.
Coroutines have the following issues:
you can’t see/inspect which routines are currently running
to stop a specific coroutine you have to store its reference in a field
coroutines may switch the order in which they run, ie A-B-C becomes A-C-B when you stop and restart B
… plenty more, this is just a shortlist
A timer is pretty darn simple to set up and test during Update which gives you full control over everything.
float attackStartTime; // this is your "timer"
private bool IsAttacking => Time.time - attackStartTime >= 0.15f;
private void OnTriggerStay(Collider other)
{
if (other.tag.Equals("Unit"))
attackStartTime = Time.time;
}
void Update()
{
if (IsAttacking)
Debug.Log("attacking, punch, kick, BAM");
}
Oh yeah, coroutines also have you write more code more often than not!
Note that “isAttacking” becomes implicit - having both a timer and a state bool has a high risk of either going out of sync, causing bugs (often hard to track down). You either rely on a single bool (which may be behind a timer toggle), or you just wrap the time test like I did.
Thanks for explanation.
But you missed the point of the topic, I’m not looking for help or advice here.
My posted code is just an example to explain the finding with deltaTime.