pygame - jerky movement in general

Started by
7 comments, last by matteusw0001 4 years, 2 months ago

I've got a huge issue with pygame. Movement is always a bit jerky, even if the code is as simple as can be. Try running this simple example which moves a rect by 1 pixel at 60 FPS:


import sys
import pygame
pygame.init()

display = pygame.display.set_mode((640, 480), pygame.FULLSCREEN)
clock = pygame.time.Clock()
FPS = 60

def motion_test():

    rect = pygame.rect.Rect((0, 240), (40, 40))

    while 1:

        # clear display
        display.fill((0, 0, 0))

        # check quit
        for e in pygame.event.get():
            if (e.type == pygame.QUIT or
                e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()

        # move the rect by 1 pixel
        rect.x += 1
        if rect.x >= 640:
            rect.x = 0

        # draw the rect and flip the display
        pygame.draw.rect(display, (255, 0, 0), rect)
        pygame.display.flip()

        # tick the clock
        clock.tick(FPS)


if __name__ == "__main__":
    motion_test()

No matter on what hardware I test this, the rect is moving jerky. Every like half a second or so, it "jumps" a bit. It's just not smooth motion.

Please, this makes me mad... does anybody know what might cause this? Is it pygame itself, or Python's garbage collection, or the OS, or just what? Any ideas? Do you see the same running the example?

(Win7 64bit, Pygame 1.9.1, Python 2.7.3)

Advertisement

You need to introduce 'speed' variable and move objects depending on deltaTime since last loop iteration. Each iteration takes different time to complete, but the movement is fixed (1 unit per iteration), that makes the movement jerky.

Edit:
Or if you want to use clock.tick() to have fixed amount of frames try to use 30fps instead of 60.
More details here:
"... it has to do with how tick() is implemented.

It uses SDL_Delay, which in turn uses various platform-specific sleep functions, which depending on the OS can be quite inaccurate at the millisecond level. What's probably going on is, a popular OS's sleep function has a granularity of 15ms, whereas your target FPS of 60 requires 16.66...ms sleeps. One of your frames is going to stick around for 30ms, maybe every second or so."

Thanks for the hint! I now move the rect according to delta time (which is usually 16 / 17 ms @ 60 FPS), but still it's the same result: A notable "lag" after every 0.5 seconds or so.

Here's the code again:


import sys
import pygame
pygame.init()

display = pygame.display.set_mode((640, 480), pygame.FULLSCREEN)
clock = pygame.time.Clock()
FPS = 60

def motion_test():
    
    dt = 0
    rect_pos = [0.0, 240.0]
    rect = pygame.rect.Rect(rect_pos, (40, 40))
    rect_speed = 60 # pixels per sec

    while 1:

        # clear display
        display.fill((0, 0, 0))

        # check quit
        for e in pygame.event.get():
            if (e.type == pygame.QUIT or
                e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()

        # move the rect
        dt = clock.get_time()
        rect_pos[0] += rect_speed * dt / 1000.0
        if rect_pos[0] >= 640:
            rect_pos[0] = 0.0
        rect.topleft = rect_pos

        # draw the rect and flip the display
        pygame.draw.rect(display, (255, 0, 0), rect)
        pygame.display.flip()

        # tick the clock
        clock.tick(FPS)
        

if __name__ == "__main__":
    motion_test()

I will try again using 30 FPS, just a moment...

Just tried again using 30 FPS. The result is quite similar: The rect "hickups" every like 1.0 seconds. I'm desperate...

Can you try and confirm or provide a hint?


import sys
import pygame
pygame.init()

display = pygame.display.set_mode((640, 480), pygame.FULLSCREEN)
clock = pygame.time.Clock()
FPS = 30

def motion_test():
    
    dt = 0
    rect_pos = [0.0, 240.0]
    rect = pygame.rect.Rect(rect_pos, (40, 40))
    rect_speed = 60 # pixels per sec

    while 1:

        # clear display
        display.fill((0, 0, 0))

        # check quit
        for e in pygame.event.get():
            if (e.type == pygame.QUIT or
                e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()

        # move the rect
        dt = clock.get_time()
        rect_pos[0] += rect_speed * dt / 1000.0
        if rect_pos[0] >= 640:
            rect_pos[0] = 0.0
        rect.topleft = rect_pos

        # draw the rect and flip the display
        pygame.draw.rect(display, (255, 0, 0), rect)
        pygame.display.flip()

        # tick the clock
        clock.tick(FPS)
        

if __name__ == "__main__":
    motion_test()

Is there any way to get an accurate fixed time step on Win7 somehow? (Preferably @ 60 FPS)

I don't really know Python too good but what kind of numbers is this returning?
dt = clock.get_time()

I just Googled and I got this:
http://www.pygame.org/docs/ref/time.html

get_time()

time used in the previous tick
get_time() -> milliseconds

Returns the parameter passed to the last call to Clock.tick(). It is the number of milliseconds passed between the previous two calls to Pygame.tick().


I don't really get this as I haven't really used pygame but I would recommend you check what kind of values you are getting from get_time(). The description seems to imply that it should be returning FPS each time (since that was the parameter of the last call to clock.tick()) but that doesn't really make sense.

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

clock.get_time() returns the number of milliseconds that have passed since the previous call to clock.tick(). So at 60 FPS, this returns a value like 16 or 17 (on my machine).

The pygame docs are sometimes really nuts... alas.

/EDIT:

I have just tried the pyglet clock and limited it to 60 FPS; all the rest is still pygame. And see, it is much better now! Still not a 100% smooth but fine enough to code an NES-like game that runs at 60 FPS with a fixed time step.

Here's the code:


import sys
import pygame
import pyglet
pygame.init()

display = pygame.display.set_mode((640, 480), pygame.FULLSCREEN)
clock = pyglet.clock.Clock()
clock.set_fps_limit(60)

def motion_test():
    
    rect = pygame.rect.Rect((0, 240), (40, 40))

    while 1:

        # clear display
        display.fill((0, 0, 0))

        # check quit
        for e in pygame.event.get():
            if (e.type == pygame.QUIT or
                e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()

        # move the rect
        rect.x += 1
        if rect.x >= 640:
            rect.x = 0

        # draw the rect and flip the display
        pygame.draw.rect(display, (255, 0, 0), rect)
        pygame.display.flip()

        # tick the clock
        clock.tick()
        

if __name__ == "__main__":
    motion_test()


There is a function Clock.tick_busy_loop() which is more accurate then Clock.tick() you could try that.

My CVMy money management app: ELFSHMy game about shooting triangles: Lazer of Death

Thanks for the hint mousetail but I've tried that already and it's not really better.

Is there a clock object in python language that is VERY, VERY accurate on windows/mac/unix? What's the best way to program a NES-like game in python 2.x regarding the time step?

Anyone can help?

This topic is closed to new replies.

Advertisement