I am creating a basic Pong-style game using Pygame, and I have gotten as far as moving the paddles. I have code that should prevent the paddles from moving beyond the edges of the screen, but if a player holds down a movement key, the paddle moves just slightly past the edge of the screen. Once the player releases the key, the paddle snaps back to where it ought to stop.
I am somewhat new to Python and coding in general, so my code may not be the tidiest or most efficient. While this problem may not affect gameplay at all, I would like to know why it behaves this way and how to change it to how I want it.
My code is:
import random
import pygame
from pygame.locals import *
from sys import exit
screen_size = 600, 600
w,h = screen_size
screen = pygame.display.set_mode(screen_size)
clock = pygame.time.Clock()
pic = pygame.image.load
class paddle(pygame.sprite.Sprite):
def __init__(self, pos):
pygame.sprite.Sprite.__init__(self)
self.pos = pos
self.posx, self.posy = self.pos
self.width = 10
self.height = 100
self.image = pygame.Surface((self.width, self.height))
self.image.fill((255,255,255))
self.rect = pygame.Rect((0,0), self.image.get_size())
self.speed = 150
def update(self, mov, tp):
self.posy += mov * self.speed * tp
self.rect.center = self.posx, self.posy
class box(pygame.sprite.Sprite):
def __init__(self, pos):
pygame.sprite.Sprite.__init__(self)
self.pos = pos
self.posx, self.posy = self.pos
self.width = 10
self.height = 10
self.image = pygame.Surface((self.width, self.height))
self.image.fill((255,255,255))
self.rect = pygame.Rect((0,0), self.image.get_size())
self.speed = 300
self.directionx = 0
self.directiony = 0
def update(self, mov, tp):
self.posx += mov[0] * self.speed * tp
self.posy += mov[1] * self.speed * tp
self.rect.center = self.posx, self.posy
reset = True
done = False
while done == False:
if reset == True:
p1 = paddle((0,0))
p1 = paddle((20,(h-p1.height)/2))
p2 = paddle((0,0))
p2 = paddle((w-20,(h-p2.height)/2))
paddle_group = pygame.sprite.Group()
paddle_group.add(p1)
paddle_group.add(p2)
ball = box(((w/2)-10,h/2))
ball_group = pygame.sprite.Group(ball)
reset = False
else:
pass
time_passed = clock.tick(60)
time_passed_seconds = time_passed/1000.0
for event in pygame.event.get():
if event.type == QUIT:
done = True
p1_movey = 0
p2_movey = 0
pressed_keys = pygame.key.get_pressed()
pressed_mb = pygame.mouse.get_pressed()
if pressed_keys[K_ESCAPE]:
done = True
if pressed_keys[K_w]:
p1_movey = -2
elif pressed_keys[K_s]:
p1_movey = +2
if pressed_keys[K_UP]:
p2_movey = -2
elif pressed_keys[K_DOWN]:
p2_movey = +2
p1.update(p1_movey, time_passed_seconds)
p2.update(p2_movey, time_passed_seconds)
# This is where the border check is
for PADDLE in paddle_group.sprites():
if PADDLE.posy > h - (PADDLE.height/2):
PADDLE.posy = h - (PADDLE.height/2)
elif PADDLE.posy < (PADDLE.height/2):
PADDLE.posy = (PADDLE.height/2)
screen.fill((0,0,0))
paddle_group.draw(screen)
pygame.display.update()
pygame.quit()
The boundary part is marked with a comment.
The problem is that you're correcting each paddle
's posy
without adjusting its rect
at the same time. posx
and posy
store the location of your sprite-- in this case, it's center-- but the position of what you see on-screen is determined by the rect
. Because you add p#_movey
, then update
(which adjusts the rect), then, finally, make the posy
correction for out-of-bounds values, rect
remains at it's invalid location. Because you have adjusted posy
, though, future p#_movey
changes effect the correct location, not the invalid one (which is why your sprite remains O.B. until the movement key is released).
In short, this is what's going on:
posy
and rect
via update
.PADDLE.posy
to keep it in-bounds for each PADDLE in paddle_group
, but do not update PADDLE.rect
to reflect the adjustment. (And rect
does need to be updated.)There are two solutions I can think of:
1) Modify your for PADDLE in paddle_group.sprites()
loop so that PADDLE.rect.center
is refreshed, that it looks something like this:
# on the main loop...
for PADDLE in paddle_group.sprites():
if PADDLE.posy > h - (PADDLE.height/2):
PADDLE.posy = h - (PADDLE.height/2)
elif PADDLE.posy < (PADDLE.height/2):
PADDLE.posy = (PADDLE.height/2)
PADDLE.rect.center = PADDLE.posx, PADDLE.posy
## ^ adding this line, which recenters the rect.
2) Alternatively, you could run the boundary check in paddle
's update
method and remove the for PADDLE in paddle_group.sprites()
loop from the main loop all together. paddle.update(..)
would then look something like this:
# in class paddle(..) ...
def update(self, mov, tp):
self.posy += mov * self.speed * tp
self.posy = max(self.height / 2, min(self.posy, h - (self.height / 2)))
## ^ Adding this line, which checks boundaries for posy.
## because you're doing it here, there's no need to do it again on the main loop.
self.rect.center = self.posx, self.posy
... as long as your boundaries are defined by h
(screen height; the bottom of your window) and 0
(the top of the window) and the height of the paddle.
Either solution should work, though there are almost certainly third ways, too. Hope that helps!