TOPIC CLOSED
All coroutines are frame dependent. Even calls like yield return new WaitForSeconds(5);
are frame dependent. This call will begin waiting after then end of the frame it was called and will not continue evaluating until the frame after it has returned. You can see for yourself here.
Check out asynchronous programming here. It is frame independent.
Think long and hard before you go down this route. Async programming is an extremely advanced topic with tons of weird edge cases when used in Unity, since nearly 100% of the Unity scripting API can ONLY be called from the main thread.
This limitation means EVERYTHING you do in another thread asynchronously will need to be marshaled through delegates placed into some type of locking queue interface and processed on the main thread anyway.
There is almost NEVER a need for async programming this in Unity until you get to EXTREMELY advanced levels of custom engine or third party package integration, or perhaps heavy computation procedural generation.
Most of the time when people THINK they need async routines, they waste a few months and then realize it doesn’t get them much and now their codebase is wrecked.
Source: I’ve seen it happen again and again and again.
Instead, start with the basic timing of Unity:
https://docs.unity3d.com/Manual/ExecutionOrder.html
It’s incredibly powerful and simple at the same time.
You are right Kurt, I should have tossed some warning in there about it’s complexity.
I assumed they were looking to do something outside the Unity API if they were looking for something completely separate from the Unity loop.
yield return null; waits for 1 frame, which means some ways of using it are frame dependent. But it doesn’t have to be. For example, this is not frame dependent – it runs for 3 seconds, adding 12 to size, no matter the frame rate:
float endTime=Time.time+3.0f;
while(Time.time<endTime) {
size += Time.deltaTime*4;
...
yield return null;
}
It’s a tiny bit frame dependent since it runs to the first frame past 3 seconds. But “real” frame dependent runs proportional to the frame rate.
But say you had code that was "loop through an array { use A*;* yield return null; }. It handles array items per second. If the frame rate is cut in half, it will take twice as long to process the array. But it’s not so much return null’s fault. yield return WaitForSeconds(0.01f) has the same problem since the shortest Unity can wait is 1 frame.
Actually i just made a game with frame independent and Coroutines are so easy to use but frame dependent. For example: Crouch with lerp
IENumerator CrouchEvent() {
while(condition) {
"lerp operation"
yield return null;
}
}
void Update() {
if(Input.GetKey(KeyCode.C) && Time.time > crouchTimer) {
StartCoroutine(CrouchEvent());
crouchTimer = Time.time + crouchCoolDown;
}
}
Is there a way to do this operation with frame independent? When i try to lerping operations with loops without coroutines, project crashes.
It’s always helpful in threads like these to give a bit of context as to what you’re trying to accomplish, and this is the perfect example: async is technically a valid answer to the question you asked but it is MILES away from the correct answer for any variation on, “how do I make a character crouch?”
You can use coroutines for this just fine, and the way to make it framerate independent is the same as if you were in Update: multiply in Time.deltaTime with whatever you’re trying to do.
I’m not really clear on what it is you’re actually trying to do. What is it about crouching in this case that needs to be made framerate-independent? I have a sneaking suspicion that the part of the code that is giving you trouble is the part that you’ve obfuscated under “lerp operation”.
Lerp is often used in two ways. One of them is just funny and is depends on frame rate. But the “official” way to Lerp is just fine. You use Time.deltaTime to increase a value from 0 to 1 over however many seconds, using it as the Lerp percent:
while(num0to1IncreasedByTimeDeltaTime<1) {
value=Lerp(start, end, num0to1IncreasedByTimeDeltaTime);
num0to1IncreasedByTimeDeltaTime += Time.deltaTime * someConstant;
yield return null;
}
But the other weird way isn’t linear and the math is tricky. You lerp yourself 5% closer each frame:
while(value is not close to end) {
value=Lerp(value, end, 0.05f);
yield return null;
}
It obviously goes slower with a slower frame rate. You can sort of fix it by basing 0.05f on the frame rate, but the math is wrong. In theory this type of Lerp is only used for cutsie stuff where you don’t care about the exact time, so close-enough is good enough.
Thank you for answer. What about this?
private IEnumerator CrouchEvent() {
float timer = 0f;
float duration = 0.1f;
while (timer < duration) {
timer += Time.deltaTime;
_characterController.height = Mathf.Lerp(standHeight, crouchHeight, timer / duration);
yield return null;
}
}
Is this frame independent?
Yep, that looks like it should almost do the trick. It’s functionally the same as a common pattern that I like to use in situations like this, which looks like:
private IEnumerator CrouchEvent() {
float duration = 0.1f;
for (float t = 0f; t < duration; t += Time.deltaTime) {
_characterController.height = Mathf.Lerp(standHeight, crouchHeight, timer / duration);
yield return null;
}
_characterController.height = crouchHeight;
}
I feel the for loop is a bit more natural to read but a while loop is perfectly fine.
You do want to add a final line to set the height to the final value; otherwise, you’ll end up with very slightly different crouch heights at the end depending on where the frame time landed on the final iteration of the loop.
Ha! I’ll do that, often by tweaking the loop to hit the final value and run a final time. But I’ve been curious, tried it without a “must end exactly on final value” check, and never seen a difference. Esp. for something transient such as a crouch, with animation covering it up.
Here’s my pattern for dealing with this final iteration in Lerp intervals:
float fraction = 0.0f;
for (float t = 0; fraction < 1.0f; t += Time.deltaTime)
{
fraction = t / duration;
// TODO: use fraction here as the third argument for your Lerp
yield return null;
}
For loop doesn’t care what its three terms are. It’s just a wrapper.
And this will always leave you on the very final target.