Reputation: 817
I have a simple bouncing box window that was drawn with 'Pygame'. Everything seems to work properly, except for a little annoyance. It stutters constantly! I have no idea what could be causing the stutter. I thought it might be a delay, so I implemented a fixed time-step to allow the loop to catch up, but this had no effect.
#--- initialize pygame window ---#
import pygame
import time
pygame.init()
size = (1200,500)
screen = pygame.display.set_mode(size, pygame.RESIZABLE)
fps = 60
#--- define color palette ---#
black = (0,0,0)
white = (255,255,255)
#--- define the player ---#
class player:
def __init__(self,screen,surface, color):
self.speed = 3
self.direction_x = 1
self.direction_y = 1
self.screen = screen
self.surface = surface
self.rect = self.surface.get_rect()
self.color = color
def set_pos(self, x,y):
self.rect.x = x
self.rect.y = y
def advance_pos(self):
screen_width, screen_height = screen.get_size()
if self.rect.x + self.rect.width > screen_width or player1.rect.x < 0:
player1.direction_x *= -1
player1.speed = 3
elif player1.rect.y + player1.rect.height > screen_height or player1.rect.y < 0:
player1.direction_y *= -1
player1.speed = 3
else:
player1.speed -= 0.001
self.rect.x += self.speed * self.direction_x
self.rect.y += self.speed * self.direction_y
def draw(self):
pygame.draw.rect(self.surface, self.color, [0,0,self.rect.width,self.rect.height])
def blit(self):
screen.blit(self.surface, self.rect)
player1 = player(screen, pygame.Surface((50,50)), white)
player1.set_pos(50,50)
player1.draw()
#--- define game variables ---#
previous = time.time() * 1000
lag = 0.0
background = black
done = False
#--- game ---#
while not done:
#--- update time step ---#
current = time.time() * 1000
elapsed = current - previous
lag += elapsed
previous = current
#--- process events ---#
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
break
if event.type == pygame.VIDEORESIZE:
screen = pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE)
#--- update logic ---#
while True:
player1.advance_pos()
lag -= fps
if lag <= fps:
break
#--- draw to screen ---#
screen.fill(background)
player1.blit()
pygame.display.update()
pygame.time.Clock().tick(fps)
Upvotes: 1
Views: 3067
Reputation: 28
An easy fix is:
Pygame uses only integers to calculate the position of the Rect object, what I did down here, I added a new object variable that saves the position as float, and then we pass it to the Rect position to round it up.
self.x += self.speed * self.direction_x
self.y += self.speed * self.direction_y
self.rect.x = self.x
self.rect.y = self.y
There is still some small jittering, but this is the smoothest we can get from pure Pygame Rects. I also moved the pygame.time.Clock()
to a constant of CLOCK = pygame.time.Clock().tick(fps)
outside from the game loop. And then call it inside the loop at the end of it, CLOCK.tick(fps)
#--- initialize pygame window ---#
import pygame
import time
pygame.init()
size = (1200,500)
screen = pygame.display.set_mode(size, pygame.RESIZABLE)
fps = 60
#--- define color palette ---#
black = (0,0,0)
white = (255,255,255)
#--- define the player ---#
class player:
def __init__(self,screen,surface, color):
self.speed = 3
self.direction_x = 1
self.direction_y = 1
self.screen = screen
self.surface = surface
self.rect = self.surface.get_rect()
self.color = color
self.x = 0
self.y = 0
def set_pos(self, x,y):
self.rect.x = x
self.rect.y = y
def advance_pos(self):
screen_width, screen_height = screen.get_size()
if self.rect.x + self.rect.width > screen_width or player1.rect.x < 0:
player1.direction_x *= -1
player1.speed = 3
elif player1.rect.y + player1.rect.height > screen_height or player1.rect.y < 0:
player1.direction_y *= -1
player1.speed = 3
else:
player1.speed -= 0.001
self.x += self.speed * self.direction_x
self.y += self.speed * self.direction_y
self.rect.x = self.x
self.rect.y = self.y
def draw(self):
pygame.draw.rect(self.surface, self.color, [0,0,self.rect.width,self.rect.height])
def blit(self):
screen.blit(self.surface, self.rect)
player1 = player(screen, pygame.Surface((50,50)), white)
player1.set_pos(50,50)
player1.draw()
#--- define game variables ---#
previous = time.time() * 1000
lag = 0.0
background = black
done = False
CLOCK = pygame.time.Clock()
#--- game ---#
while not done:
#--- update time step ---#
current = time.time() * 1000
elapsed = current - previous
lag += elapsed
previous = current
#--- process events ---#
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
break
if event.type == pygame.VIDEORESIZE:
screen = pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE)
#--- update logic ---#
while True:
player1.advance_pos()
lag -= fps
if lag <= fps:
break
#--- draw to screen ---#
screen.fill(background)
player1.blit()
pygame.display.update()
CLOCK.tick(fps)
Upvotes: 0
Reputation: 1
I created a clock object befor entering the "while not done" loop and had no more lags
#--- initialize pygame window ---#
import pygame
import time
pygame.init()
size = (1200,500)
screen = pygame.display.set_mode(size, pygame.RESIZABLE)
fps = 60
#--- define color palette ---#
black = (0,0,0)
white = (255,255,255)
#--- define the player ---#
class player:
def __init__(self,screen,surface, color):
self.speed = 3
self.direction_x = 1
self.direction_y = 1
self.screen = screen
self.surface = surface
self.rect = self.surface.get_rect()
self.color = color
def set_pos(self, x,y):
self.rect.x = x
self.rect.y = y
def advance_pos(self):
screen_width, screen_height = screen.get_size()
if self.rect.x + self.rect.width > screen_width or player1.rect.x < 0:
player1.direction_x *= -1
player1.speed = 3
elif player1.rect.y + player1.rect.height > screen_height or player1.rect.y < 0:
player1.direction_y *= -1
player1.speed = 3
else:
player1.speed -= 0.001
self.rect.x += self.speed * self.direction_x
self.rect.y += self.speed * self.direction_y
def draw(self):
pygame.draw.rect(self.surface, self.color, [0,0,self.rect.width,self.rect.height])
def blit(self):
screen.blit(self.surface, self.rect)
player1 = player(screen, pygame.Surface((50,50)), white)
player1.set_pos(50,50)
player1.draw()
#--- define game variables ---#
previous = time.time() * 1000
lag = 0.0
background = black
done = False
clock=pygame.time.Clock()
#--- game ---#
while not done:
#--- update time step ---#
current = time.time() * 1000
elapsed = current - previous
lag += elapsed
previous = current
#--- process events ---#
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
break
if event.type == pygame.VIDEORESIZE:
screen = pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE)
#--- update logic ---#
while True:
player1.advance_pos()
lag -= fps
if lag <= fps:
break
#--- draw to screen ---#
screen.fill(background)
player1.blit()
pygame.display.update()
clock.tick(fps)
pygame.quit()
Upvotes: 0
Reputation: 2465
This is a rewrite of your code that uses opengl instead for the rendering. The major changes are as follows:
I fixed the way the timer is done. Rather than doing just clock.tick(fps), I manually keep track of the amount of time that it takes to do all of the processing to the frame and add the appropriate millisecond delay to reach 60 fps. You can try that modification with your existing pygame code before migrating to opengl as that might be sufficient to remove most of the stutter.
import pygame
import time
from OpenGL.GL import *
class Player:
def __init__(self, screen, width, height, color):
self.x = 0
self.y = 0
self.speed = 3
self.direction_x = 1
self.direction_y = 1
self.screen = screen
self.width = width
self.height = height
self.color = color
def set_pos(self, x, y):
self.x = x
self.y = y
def advance_pos(self):
screen_width, screen_height = screen.get_size()
if self.x + self.width > screen_width or self.x < 0:
self.direction_x *= -1
self.speed = 3
elif self.y + self.height > screen_height or self.y < 0:
self.direction_y *= -1
self.speed = 3
else:
self.speed -= 0.001
self.x += self.speed * self.direction_x
self.y += self.speed * self.direction_y
def draw(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glTranslate(self.x, self.y, 0)
glBegin(GL_QUADS)
glColor(*self.color)
glVertex(0, 0, 0)
glVertex(self.width, 0, 0)
glVertex(self.width, self.height, 0)
glVertex(0, self.height, 0)
glEnd()
if __name__ == "__main__":
pygame.init()
size = width, height = (550, 400)
screen = pygame.display.set_mode(size, pygame.RESIZABLE | pygame.DOUBLEBUF | pygame.OPENGL)
fps = 60
black = (0,0,0,255)
white = (255,255,255,255)
player1 = Player(screen, 50, 50, white)
player1.set_pos(50,50)
done = False
previous = time.time() * 1000
glClearColor(*black)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, width, height, 0, -1, 1)
clock = pygame.time.Clock()
while not done:
current = time.time() * 1000
elapsed = current - previous
previous = current
delay = 1000.0/fps - elapsed
delay = max(int(delay), 0)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
break
if event.type == pygame.VIDEORESIZE:
size = width, height = event.w, event.h
screen = pygame.display.set_mode(size, pygame.RESIZABLE | pygame.DOUBLEBUF | pygame.OPENGL)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, width, height, 0, -1, 1)
glViewport(0, 0, width, height)
#reset player movement and position to avoid glitches where player is trapped outside new window borders
player1.set_pos(50, 50)
player1.direction_x = 1
player1.direction_y = 1
player1.advance_pos()
glClear(GL_COLOR_BUFFER_BIT)
glClear(GL_DEPTH_BUFFER_BIT)
player1.draw()
pygame.display.flip()
pygame.time.delay(delay)
Upvotes: 2