Search code examples
pythonpygamegame-physics

Gravity strength and jump height somehow dependant on framerate - Pygame


I'm currently in the early stages of making a classic 2D platformer using pygame for a school project, and I was experimenting with the jump mechanic, when I ran into this rather strange problem. Even though I have factored in the timedelta between updates, the jump length and height both get shorter when the amount of time between frames increases.

Here I've let 3 instances of the Player object jump across the screen, moving right with a constant speed of 700 pixels/second, and an initial upwards speed of 700 pixels/second. They each had an artificially increased minimal delta_time of 0.001s, 0.017s and 0.1s respectively.

This is how the new speed and position vectors are calculated inside of the Player object's update function, which is called once every frame (the delta_time is passed in from the main update loop):

self.speed.y += 1000.0 * delta_time
self.position += self.speed * delta_time

And this is how delta_time is calculated at the end of every cycle of the main update loop (it is initialized with a value of zero for the first frame):

delta_time = time.time() - pre_time
pre_time = time.time()
if delta_time < min_delta_time:
    time.sleep(min_delta_time - delta_time)
    delta_time += time.time() - pre_time
    pre_time = time.time()

The min_delta_time represents the minimal time between frame updates.

After many attempts at fixing this, I'm pretty certain that the flaw lies in the line that updates the speed, but even after double checking the math, I still can't figure out what the problem is. At first I thought that it could be the delta_time being imprecise, but if that was the case the horizontal speed would also be affected, and the trails would still line up.

So can i change anything to make the jump/gravity more consistent?


Solution

  • This isn't an error in coding so much as a logical error in the model: by modeling an acceleration as taking place instantaneously at update points rather than continuously across the full time span, inaccuracies are introduced which will get larger the sparser the updates.

    To illustrate this, consider an object accelerating at 1m/s^2 from stop. If we model this as above with a 1-second interval, after six seconds our model will put the object at 21m. Repeating with a 2-second interval, we'll put it at 24m, and a 3-second interval will put it at 27m. However, the real position should be 18m.

    The solution is to consider average velocity over the simulated time span rather than the instantaneous velocity at the sample points. In other words, rather than counting the full span's worth of acceleration, then using the result as the velocity for the whole span, add half of the span's acceleration, use that velocity to calculate distance traveled in that span, then add the remaining half of the acceleration.

    So your update logic would become something like:

    self.speed.y += 500.0 * delta_time
    self.position += self.speed * delta_time
    self.speed.y += 500.0 * delta_time