Question:
How to make parabolic frame movement independent in Python?

Problem:

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()


Solution:

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


Suggested blogs:

>How to fix mouseover event glitch in JavaScript?

>How to use querySelectorAll()" with multiple conditions in JavaScript?

>Fix ASP.NET Core Model Binding Not Working issue.

>How to create a zip file and return it to client for download?

>How to Group large number of data based on CreatedDate(DateTime)?

>Issues converting ASPX Web app from .net 3.5 to 4.8.1


Ritu Singh

Ritu Singh

Submit
0 Answers