- Home /
Jump not framerate independent
Hey,
I'm using a custom 2D controller, it has a Move() method that uses transform.Translate(velocity) to move the character.
In my player controller script I apply gravity by doing this every frame right before I call Move() :
velocity.y += gravity * Time.deltaTime;
My Jump() method is called before the gravity, and sets the velocity.y to the jumpForce once.
At 60+ fps i'm able to jump over a specific amount of tiles, at 30 fps I miss the last tile by a small amount of height, the weird thing is that if I slow down the game with time scale, I reach the same height.
What is going on?
EDIT: Bunny83's solution worked perfectly, I'm going to keep Eno-Khaon's answer because it clearly explains the issue, thanks guys much appreciated
Answer by Eno-Khaon · Jul 13, 2018 at 03:13 AM
Physics calculated on a per-frame basis will ALWAYS* be framerate-dependent. This is why Unity uses FixedUpdate() to alleviate that somewhat by providing a consistent rate of performance to offer reproducible results.
*Well, okay, not always, but the calculations required to make it framerate-independent are non-trivial to the point where it's simpler to wind up close enough instead.
If you calculate where something should be in reality (based on predictive physics calculations), you're assuming that the rate of acceleration will change an infinite number of times per second. In your case (based on iterative physics calculations), however, the acceleration changes at a rate based on the current framerate.
To give an example of this, here's a breakdown of behavior under the following conditions:
1) Gravity accelerates objects at a rate of 1 unit per second.
2) The object "jumps" with a starting upward force of 1 unit per second velocity
Column A: Current Y-axis speed
Column B: Pending height change, based on current frame velocity
Column C: New height after current frame
Example 1: 5 frames per second
deltaTime per frame: 0.2
|A |B |C
+-------+-------+-------
|1.0 |0.2 |0.2
|0.8 |0.16 |0.36
|0.6 |0.12 |0.48
|0.4 |0.08 |0.56
|0.2 |0.04 |0.6
Final Height: 0.6 at 1 second, 5 frames
-------------------------------
Example 2: 10 frames per second
deltaTime per frame: 0.1
|A |B |C
+-------+-------+-------
|1.0 |0.1 |0.1
|0.9 |0.09 |0.19
|0.8 |0.08 |0.27
|0.7 |0.07 |0.34
|0.6 |0.06 |0.4
|0.5 |0.05 |0.45
|0.4 |0.04 |0.49
|0.3 |0.03 |0.52
|0.2 |0.02 |0.54
|0.1 |0.01 |0.55
Final Height: 0.55 at 1 second, 10 frames
-------------------------------
A "correct" Final Height would have been 0.5
As you can see, differences in framerate WILL affect the outcome because the framerate directly influences exactly how long the object is moving at any given speed. While the changes in velocity based on acceleration take effect in a true-to-life manner, the changes in position are based on the current velocity for a given frame, scaled by the time that frame took to process.
I'm having a hard time understanding what you're saying exactly (I'm exhausted), but i've noticed that at a timescale of 0.5 (twice the amount of frames), the character makes it at 30 fps where it doesn't with a timescale of 1, is this what you mean, and how can this be solved, I want a 100% consistent jump height.
Essentially, the point I'm trying to make is that 100% accurate physics calculations, performed iteratively by the framerate of gameplay, are prohibitively costly to calculate. You should aim for a "close enough" kind of approach because true-to-life physics aren't easy to calculate in every situation.
The two main approaches to compensate for this would basically be to either:
A) Use some form of fixed framerate, whether it's utilizing FixedUpdate() for position information or using Time.captureFramerate to ensure that a precise number of frames will render per second of "game time"
or...
B) Calculate your height purely mathematically based on your initial height and elapsed time. Following in the footsteps of the example provided here, it would look something like:
float newY = startY + ((jumpVelocityY * elapsedTime) - (0.5f * gravityY * elapsedTime * elapsedTime));
Thank you so much, I'm still unable to understand why the final heights are different but my brain is on fire, I'll give it another try tomorrow, as for consistency I don't $$anonymous$$d 100% but there seems to be too much of an inconsistency in my case, where a player could jump an extra tile at high fps, something I've never noticed in any platformers, I don't want to cap the fps as input lag is present with Vsync, and stutter when manually capping, I'll keep trying to figure it out and take a look at the links you posted, thanks again
Answer by Bunny83 · Jul 13, 2018 at 03:59 AM
While Eno-Khaon is basically right, there is an easy fix for constant accelerations. The main problem is that deltaTime can only be used to make linear things framerate independent. Since a constant acceleration applies a linear change to the velocity, the velocity correctly changes over time. However the position change is not linear but quadratic and it can't be scaled linearly.
The trick is to apply the velocity not once but twice each frame. Once before you do the velocity change and once after the velocity change. You just apply half the velocity each time. This will not make it perfect as we deal with time discrete values while in reality physics happens continuously. Though it will make the errors even out and not drift over time.
pos += 0.5f * velocity * Time.deltaTime;
if (jump)
velocity.y = jumpForce;
velocity += gravity * Time.deltaTime;
pos += 0.5f * velocity * Time.deltaTime;
Note that this does only work for constant accelerations / linear velocity changes. So if something like drag is used it won't work since drag is non linear
I've explained that already more in detail in the comments below my answer over here
This worked perfectly, I'm going to keep my previously accepted answer but mention it in my OP, thank you so much!
This solution is not working for me... Help please! Here's a simplified version of my script: ("gravity" is a negative number, "movement" is the variable you named "velocity", I'm using Unity's Character Controller)
private void Update()
{
myController.$$anonymous$$ove(movement * 0.5f);
movement.x = movement.z = 0.0f; //I want my player to only accelerate on the Y axis
if (Input.Get$$anonymous$$ey(controls.forward$$anonymous$$ey))
{
walk = true;
}
if (myController.isGrounded)
{
movement.y = 0;
if (Input.Get$$anonymous$$eyDown(controls.jump$$anonymous$$ey))
{
movement.y = jumpForce; //jumpForce is 0.15
}
}
if (walk)
movement += transform.forward * moveSpeed * Time.deltaTime;
movement.y -= gravity * Time.deltaTime;
myController.$$anonymous$$ove(movement * 0.5f);
}
You need to set the movement before and after you call $$anonymous$$ove, for example in the case of gravity:
movement.y -= gravity * 0.5f * Time.deltaTime;
myController.$$anonymous$$ove(movement * Time.deltaTime);
movement.y -= gravity * 0.5f * Time.deltaTime;
You don't need to call $$anonymous$$ove() twice
Thanks for the reply, but it still doesn't work... $$anonymous$$y simplified script now:
private void Update()
{
movement.y -= gravity * 0.5f * Time.deltaTime;
movement.x = movement.z = 0.0f;
walk = false;
if (Input.Get$$anonymous$$ey(controls.forward$$anonymous$$ey))
{
walk = true;
}
if (myController.isGrounded)
{
movement.y = 0;
if (Input.Get$$anonymous$$eyDown(controls.jump$$anonymous$$ey))
{
movement.y = jumpForce;
}
}
if (walk)
movement += transform.forward * moveSpeed * Time.deltaTime;
myController.$$anonymous$$ove(movement);
movement.y -= gravity * 0.5f * Time.deltaTime;
}
Answer by KarlFa · Jul 12, 2018 at 11:51 PM
Try using Time.fixedDeltaTime
I'm not using FixedUpdate at all, my character doesn't move with Box2D's physics
Time.deltaTime & Time.fixedDeltaTime works respectivly in their function
If you call Time.fixedDeltaTime in the basic Update function it will dont work and call Time.deltaTime.
You should execute your $$anonymous$$ove function in the fixedUpdate ins$$anonymous$$d of the Update()
This is a raycast based controller, movement and collision detection needs to happen every frame, in Update()
Your answer
Follow this Question
Related Questions
Framerate Independent Firing Rate (delta time?) 2 Answers
How do I modify the current transform? 2 Answers
How to make a object translate alonge an other 2 Answers
Can't move instantiated prefab 2 Answers