球不会在 PyGame 中弹跳屏幕边缘

Ball doesn't bounce of screen edges in PyGame

我正在 Python 和 PyGame 一起开发 Pong 游戏。出于某种原因,游戏中的球有时不会从墙壁/屏幕边缘反弹。它从墙上反弹一两次然后停下来。

我不知道为什么会这样。

有人可以检查我的代码,看看是否有任何错误吗?

import os
import pygame

WIDTH = 600
HEIGHT = 500

class Slider:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.height = 120
        self.width = 8

    def draw(self, win):
        pygame.draw.rect(win, (0, 255, 64), (self.x, self.y, self.width, self.height))

    def move(self, dir, dis):
        if dir == 'top' and self.y >= 0:
            self.y -= dis
        elif dir == 'bottom' and self.y <= 379:
            self.y += dis 
        else: 
            self.y = self.y

class Ball:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.radius = 15

        self.speedY = 0.2
        self.speedX = 0.2

        self.top = y
        self.bottom = y + 30
        self.left = x
        self.right = x + 30

    def draw(self, win):
        ball = pygame.draw.circle(win, (255, 255, 0), (self.x, self.y), self.radius)
        self.top = ball.top
        self.bottom = ball.bottom
        self.left = ball.left
        self.right = ball.right

    def move(self):

        if self.top <= 0:
            self.speedY *= -1
        else:
            self.y += self.speedY
            self.x += self.speedX

        if self.bottom >= HEIGHT:
            self.speedY *= -1
        else:
            self.y += self.speedY
            self.x += self.speedX
            
        if self.left <= 0:
            self.speedX *= -1
        else:
            self.y += self.speedY
            self.x += self.speedX

        if self.right >= WIDTH:
            self.speedX *= -1
        else:
            self.y += self.speedY
            self.x += self.speedX


def draw_window(win, *sprites):
    pygame.display.update()
    win.fill((0, 0, 0, 0))
    for sprite in sprites:
        sprite.draw(win)

def main():
    win = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption('Pong Game')

    slider1 = Slider(100, 250)
    slider2 = Slider(500, 250)
    ball = Ball(300, 250)

    run = True
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_DOWN:
                slider1.move('bottom', 0.3)
            elif event.key == pygame.K_UP:
                slider1.move('top', 0.3)
            else:
                print(pygame.key.name(event.key))

        ball.move()

        key = pygame.key.get_pressed()

        if key[pygame.K_s]:
            slider2.move('bottom', 0.3)
        elif key[pygame.K_w]:
            slider2.move('top', 0.3)

        draw_window(win, slider1, slider2, ball)


    pygame.quit()
    quit()

main()

改变方向后移动小球,无论如何:

class Ball:
   # [...]

   def move(self):

        if self.top <= 0 or self.bottom >= HEIGHT:
            self.speedY *= -1
        if self.left <= 0 or self.right >= WIDTH:
            self.speedX *= -1

        self.y += self.speedY
        self.x += self.speedX

请注意,您根本不需要 topbottomleftright 属性。使用 pygame.Rect 对象:

class Ball:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.radius = 15
        self.rect = pygame.Rect(0, 0, self.radius*2, self.radius*2)  
        self.speedY = 0.2
        self.speedX = 0.2

    def draw(self, win):
         pygame.draw.circle(win, (255, 255, 0), (self.x, self.y), self.radius)

    def move(self):
        self.y += self.speedY
        self.x += self.speedX
        self.rect.center = round(self.x), round(self.y)

        if self.rect.top <= 0 or self.rect.bottom >= HEIGHT:
            self.speedY *= -1
        if self.rect.left <= 0 or self.rect.right >= WIDTH:
            self.speedX *= -1

你需要稍微改变一下你的 move 方法,你需要移除 else 块,因为它们会扰乱球的运动,调用 move 时总是移动球一次.您还可以在一行中结合检查球是否在轴的边缘(使用 or):

def move(self):

    if self.top <= 0 or self.bottom >= HEIGHT:
        self.speedY *= -1

    if self.left <= 0 or self.right >= WIDTH:
        self.speedX *= -1

    self.y += self.speedY
    self.x += self.speedX

什么很聪明:

您可以使用组合(如下面的 self.boundary 组件)或从 Rect.

继承 Ball

然后您可以重用 边界 中的方法,例如 move_ip(x,y)。 或通过 screen.contains(boundary) 或一些碰撞测试屏幕内部。

非整数运动

即使球的位置 (x,y) 可能是非整数(由于速度,例如 0.2),屏幕上的位置也只能表示为并呈现为整数像素坐标。因此,我故意使用 roundpygame.Rect 作为屏幕位置。

pygame.Rect表示像素粒度的对象,因此其属性存储为整数:

The coordinates for Rect objects are all integers.

使用 pygame.Rect

的边界测试碰撞
class Ball:
    def __init__(self, x, y):
        # use center to get (x,y) equivalent to former (self.x,self.y)
        self.center = (x,y)
        self.radius = 15
        # boundary as Rect (actually square) around the circle of the ball
        self.boundary = pygame.Rect(round(x)-self.radius, round(y)-self.radius, self.radius*2, self.radius*2)  
        self.speedY = 0.2
        self.speedX = 0.2

    def draw(self, win):
         pygame.draw.circle(win, (255, 255, 0), self.center, self.radius)

    def move(self):
        # move center to new position
        self.center[0] += self.speedX
        self.center[1] += self.speedY 
        # move boundary rectangle (screen position by integers only)
        self.boundary.move_ip(round(self.center[0]), round(self.center[1]))
        
        # test if inside the box using integers
        # if not pygame.Rect(0,0, WIDTH,HEIGHT).contains(self.boundary):
        if self.boundary.top <= 0 or self.boundary.bottom >= HEIGHT:
            self.speedY *= -1
        if self.boundary.left <= 0 or self.boundary.right >= WIDTH:
            self.speedX *= -1

另见这个有趣的 post 解释了 move_ip(原地移动)和 move(不移动实例但 returns 移动)之间的区别: