My code has a box that gets pushed with an initial velocity. I'm trying to make it travel the same distance with the same time taken regardless of the fps.
When I run the code with an FPS of 60, this is my debug information
Mid time: 1.8163 s
Time for vel=0: 2.5681 s
End position: (651.94, 262.0)
When I run it with an FPS of 120, this is my debug information
Mid time: 1.3987 s
Time for vel=0: 5.0331 s
End position: (1224.91, 400.35)
I'm expecting both the debug information to be the same. I did some math and concluded that I should multiply velocity by dt and multiply friction by dt square, but clearly that doesn't work. So what's wrong with the code then?
import pygame
import sys
from pygame.locals import *
from time import time
class Entity:
def __init__(self, pos, vel, friction, rgb=(0, 255, 255), size=(50, 80)):
self.pos = pos
self.vel = vel
self.friction = friction
self.rgb = rgb
self.size = size
def update(self, dt):
friction = self.friction * dt**2
for i in range(2):
self.pos[i] += self.vel[i] * dt
# Adding/subtracting friction to velocity so that it approaches 0
if self.vel[i] > 0:
self.vel[i] -= friction
if self.vel[i] < 0:
self.vel[i] = 0
elif self.vel[i] < 0:
self.vel[i] += friction
if self.vel[i] > 0:
self.vel[i] = 0
def render(self, surf):
pygame.draw.rect(surf, self.rgb, (self.pos[0], self.pos[1], self.size[0], self.size[1]))
pygame.init()
clock = pygame.time.Clock()
FPS = 120
screen_size = (1600, 900)
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption('Window')
start_1 = time()
printed_first_debug = False
printed_second_debug = False
# position, velocity, friction
player = Entity([20, 100], [8, 4], 0.05)
run = True
while run:
t1 = time()
try:
dt = 60*(t1-t0)
except NameError:
dt = 60/FPS
t0 = time()
for event in pygame.event.get():
if event.type == QUIT:
run = False
screen.fill((30, 30, 30))
player.update(dt)
player.render(screen)
if player.pos[0] >= 600 and not printed_first_debug:
end_time = time()
print(f'Mid time: {round(end_time - start_1, 4)} s')
printed_first_debug = True
elif player.vel == [0, 0] and not printed_second_debug:
end_time = time()
print(f'Time for vel=0: {round(end_time - start_1, 4)} s')
print(f'End position: ({round(player.pos[0], 2)}, {round(player.pos[1], 2)})')
printed_second_debug = True
pygame.display.update()
clock.tick(FPS)
pygame.quit()
sys.exit()
Don't square dt
in your velocity update.
You are using Euler integration to estimate the fractional changes to position and velocity for small dt, so in the case where acceleration is constant (like from force of friction) or you are using Euler to estimate for a very small delta time, you can just multiply by delta t
velocity ~ acceleration * dt
just as
position ~ velocity * dt