The pong paddle moves so fast that the ball winds up inside the paddle before the collision is detected. The problem is the user input moves the paddle by a single pixel so I don't know how to slow it down. What is the fix? Here is the code:
import pygame, sys, os
from pygame import*
from pygame.locals import*
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLUE = (0, 0, 128)
RED = (255,0,0)
os.environ["SDL_VIDEO_CENTERED"]="1"
displaysize=600
DISPLAYSURF = pygame.display.set_mode((displaysize,displaysize))
rectwidth = 50
rectheight= 50
rectposx =0
rectposy =0
class Player(object):
def __init__(self):
self.rect = pygame.rect.Rect((rectposx, rectposy, rectwidth, rectheight))
def handle_keys(self):
key = pygame.key.get_pressed()
dist = 1
if key[pygame.K_LEFT] and (player.rect.x>0):
self.rect.move_ip(-1, 0)
if key[pygame.K_RIGHT] and (player.rect.x<600-rectwidth):
self.rect.move_ip(1, 0)
if key[pygame.K_UP] and (player.rect.y>0):
self.rect.move_ip(0, -1)
if key[pygame.K_DOWN] and (player.rect.y<600-rectheight):
self.rect.move_ip(0, 1)
def draw(self, DISPLAYSURF):
pygame.draw.rect(DISPLAYSURF, BLUE, self.rect)
def postext(self):
pygame.image.load(self.rect).convert_alpha()
pygame.init()
player =Player()
pygame.display.set_caption('Hello World!')
clock=pygame.time.Clock()
fontObj = pygame.font.Font(None,32)
textSurfaceObj = fontObj.render('Hello World!', True, GREEN, BLUE)
#textPosition =
dt=0.1
v = pygame.math.Vector2(5,5)
ballposx=200
ballposy=200
ballrad=10
#DISPLAYSURF.fill(WHITE)
#x=10
#y=10
#dx=5
#rectpos = pygame.Rect(x,y,50,50)
#rect = pygame.draw.rect(DISPLAYSURF, BLUE, rectpos)
pygame.display.update()
running = True
n=0
while running:
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
if event.type==QUIT:
pygame.quit()
sys.exit
player.handle_keys()
ballposx=ballposx+v[0]*dt
ballposy=ballposy+v[1]*dt
DISPLAYSURF.fill(WHITE)
DISPLAYSURF.blit(textSurfaceObj,(0,0))
player.draw(DISPLAYSURF)
ball=pygame.draw.circle(DISPLAYSURF, GREEN, (int(ballposx),int(ballposy)), ballrad)
rectposx1=player.rect.x
rectposy1=player.rect.y
rectvelx=-(rectposx-rectposx1)/dt
rectvely=-(rectposy-rectposy1)/dt
if ballposx-ballrad<0:
v[0]=-v[0]
if ballposy-ballrad<0:
v[1]=-v[1]
if ballposx+ballrad>600:
v[0]=-v[0]
if ballposy+ballrad>600:
v[1]=-v[1]
if player.rect.colliderect(ball):
pygame.math.Vector2.reflect_ip(v,-v+5*pygame.math.Vector2(rectvelx,rectvely))
#print (player.rect.x, rectposy, ball.x, ball.y)
ballmass=1
rectmass=5
rectposx=rectposx1
rectposy=rectposy1
print (v)
#raise SystemExit("You win!")
pygame.display.update()
clock.tick(120)
One option would be to reduce the fps with something like: clock.tick(30)
. I doubt you need 120 FPS for pong
BUT if you do you will need to account for inter-pixel movement (and even if you drop the framerate you should do this anyway). That will involve some change, here is what I noticed will need changing:
The lines:
ball=pygame.draw.circle(DISPLAYSURF, GREEN, (int(ballposx),int(ballposy)), ballrad)
rectposx1=player.rect.x
rectposy1=player.rect.y
Ensures that you cast your position [ballposx,ballposy]
to integers for the drawing AND keep them as integers for the calculation. This will give less control than the following:
ball=pygame.draw.circle(DISPLAYSURF, GREEN, (int(ballposx),int(ballposy)), ballrad)
rectposx1=ballposx
rectposy1=ballposy
Here we still cast to integers to draw the rect BUT for the calculation we keep a more precise value of the ball's position. This way, if your speed is 1/2 pixel per frame you will move once every 2 frames, instead of either not at all or 1 pixel per frame.
Indeed, your collision detection expects the ball to touch the paddle, but your ball moves 5 pixels a frame, so it can jump through the edge of the paddle in a single move.
That's how I would do collision detection:
There are 2 strategies to a void that.
Move the ball in the way, that it is touching the player but not intersecting the player once a collision is detected. eg:
dx = ballposx - player.rect.centerx dy = ballposy - player.rect.centery if abs(dx) > abs(dy): ballposx = player.rect.left-ballrad if dx < 0 else player.rect.right+ballrad else: ballposy = player.rect.top-ballrad if dy < 0 else player.rect.bottom+ballrad
Reflect the movement of the ball only if its movement vector points in a direction "against" the ball. eg:
if abs(dx) > abs(dy): if (dx < 0 and v[0] > 0) or (dx > 0 and v[0] < 0): v.reflect_ip(pygame.math.Vector2(1, 0)) else: if (dy < 0 and v[1] > 0) or (dy > 0 and v[1] < 0): v.reflect_ip(pygame.math.Vector2(0, 1))
See also Sometimes the ball doesn't bounce off the paddle in pong game
Applying these 2 fixes to your code the ball will reflect properly on the player. eg:
ball = pygame.Rect((0,0), (ballrad*2, ballrad*2))
ball.center = int(ballposx),int(ballposy)
if player.rect.colliderect(ball):
dx = ballposx - player.rect.centerx
dy = ballposy - player.rect.centery
if abs(dx) > abs(dy):
ballposx = player.rect.left-ballrad if dx < 0 else player.rect.right+ballrad
if (dx < 0 and v[0] > 0) or (dx > 0 and v[0] < 0):
v.reflect_ip(pygame.math.Vector2(1, 0))
else:
ballposy = player.rect.top-ballrad if dy < 0 else player.rect.bottom+ballrad
if (dy < 0 and v[1] > 0) or (dy > 0 and v[1] < 0):
v.reflect_ip(pygame.math.Vector2(0, 1))
If you want to avoid the player pushing the ball out of the window, you need to restrict the ball to the window area and reflect the ball off the edges of the window like a pool ball:
min_x, min_y, max_x, max_y = 0, 0, window.get_width(), window.get_height()
ballposx = ballposx + v[0]*dt
ballposy = ballposy + v[1]*dt
if ballposx-ballrad < min_x:
ballposx = ballrad+min_x
v[0]=-v[0]
if ballposy-ballrad < min_y:
ballposy = ballrad+min_y
v[1]=-v[1]
if ballposx + ballrad > max_x:
ballposx = max_x-ballrad
v[0]=-v[0]
if ballposy + ballrad > max_y:
ballposy = max_y-ballrad
v[1]=-v[1]
See also Use vector2 in pygame. Collide with the window frame and restrict the ball to the rectangular area respectively How to make ball bounce off wall with Pygame? .
When a collision is detected, the player's position must be restricted so that the ball can take place between the widow boundary and the player:
if abs(dx) > abs(dy):
if dx < 0:
ballposx = max(player.rect.left-ballrad, ballrad+min_x)
player.rect.left = int(ballposx)+ballrad
else:
ballposx = min(player.rect.right+ballrad, max_x-ballrad)
player.rect.right = int(ballposx)-ballrad
With these changes, the ball can even be "squeezed" between the edge of the window and the player:
Minimal example:
import pygame
class Player(object):
def __init__(self, x, y, w, h):
self.rect = pygame.rect.Rect(x, y, w, h)
def handle_keys(self):
key = pygame.key.get_pressed()
if key[pygame.K_LEFT]:
self.rect.left = max(20, self.rect.left - 1)
if key[pygame.K_RIGHT]:
self.rect.right = min(window.get_height() - 20, self.rect.right + 1)
if key[pygame.K_UP]:
self.rect.top = max(20, self.rect.top - 1)
if key[pygame.K_DOWN]:
self.rect.bottom = min(window.get_width() - 20, self.rect.bottom + 1)
def draw(self, surface):
pygame.draw.rect(surface, (0, 0, 128), self.rect)
pygame.init()
window = pygame.display.set_mode((240, 240))
clock=pygame.time.Clock()
player = Player(20, 20, 50, 50)
v, vel = pygame.math.Vector2(1, 1), 0.5
ballPosX, ballPosY, ballRadius = 120, 120, 10
run = True
while run:
clock.tick(120)
for event in pygame.event.get():
if event.type==pygame.QUIT:
run = False
player.handle_keys()
min_x, min_y, max_x, max_y = 20, 20, window.get_width()-20, window.get_height()-20
ballPosX += v[0] * vel
ballPosY += v[1] * vel
if ballPosX - ballRadius < min_x:
ballPosX = ballRadius + min_x
v[0] = -v[0]
if ballPosY - ballRadius < min_y:
ballPosY = ballRadius + min_y
v[1] = -v[1]
if ballPosX + ballRadius > max_x:
ballPosX = max_x - ballRadius
v[0] = -v[0]
if ballPosY + ballRadius > max_y:
ballPosY = max_y - ballRadius
v[1] = -v[1]
ball = pygame.Rect((0,0), (ballRadius*2, ballRadius*2))
ball.center = int(ballPosX),int(ballPosY)
if player.rect.colliderect(ball):
dx = ballPosX - player.rect.centerx
dy = ballPosY - player.rect.centery
if abs(dx) > abs(dy):
if dx < 0:
ballPosX = max(player.rect.left-ballRadius, ballRadius+min_x)
player.rect.left = int(ballPosX)+ballRadius
else:
ballPosX = min(player.rect.right+ballRadius, max_x-ballRadius)
player.rect.right = int(ballPosX)-ballRadius
if (dx < 0 and v[0] > 0) or (dx > 0 and v[0] < 0):
v.reflect_ip(pygame.math.Vector2(1, 0))
else:
if dy < 0:
ballPosY = max(player.rect.top-ballRadius, ballRadius+min_y)
player.rect.top = int(ballPosY)+ballRadius
else:
ballPosY = min(player.rect.bottom+ballRadius, max_y-ballRadius)
player.rect.bottom = int(ballPosY)-ballRadius
ballPosY = player.rect.top-ballRadius if dy < 0 else player.rect.bottom+ballRadius
if (dy < 0 and v[1] > 0) or (dy > 0 and v[1] < 0):
v.reflect_ip(pygame.math.Vector2(0, 1))
window.fill((255, 255, 255))
pygame.draw.rect(window, (255,0,0), (18, 18, 203, 203), 2)
player.draw(window)
pygame.draw.circle(window, (0, 255, 0), (round(ballPosX), round(ballPosY)), ballRadius)
pygame.display.update()
pygame.quit()
exit()
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.