如何仅在 pygame 中使用 "colliderect" 删除矩形 "hit" x 次

How to remove a rect in pygame using "colliderect" only if the rect has been "hit" x times

我正在制作一个 breakout 版本,虽然基础游戏可以运行,但我想制作关卡等。所以我想做的是在球击中 x 次之前,砖块不会被移除。

我试过添加一个计数器:

 for brick in self.bricks:
        if self.ball.colliderect(brick):
            self.brick_counter += 1
            self.score += 3
            self.ball_vel[1] = -self.ball_vel[1]
            if self.brick_counter == 2:
            self.bricks.remove(brick)
            break

但结果是球刚好穿过了砖块。

我也考虑过创建两层(或更多)砖块,但我不确定我将如何实现它。

完整代码如下:

import sys
import pygame

# Global variables is usually in the top of the document
SCREEN_SIZE = 750, 550

#OBJECT ATTRIBUTES
#PADDLE ATTRIBUTES
PADDLE_WIDTH = 54
PADDLE_HEIGHT = 10

MAX_PADDLE_X = SCREEN_SIZE[0] - PADDLE_WIDTH
PADDLE_Y = SCREEN_SIZE[1] - PADDLE_HEIGHT - 10

#BRICK ATTRIBUTES
BRICK_WIDTH = 75
BRICK_HEIGHT = 15

#BALL ATTRIBUTES
BALL_DIAMETER = 16
BALL_RADIUS = BALL_DIAMETER // 2

MAX_BALL_X = SCREEN_SIZE[0] - BALL_DIAMETER
MAX_BALL_Y = SCREEN_SIZE[1] - BALL_DIAMETER

#COLORS
BLACK       = (0,   0,     0)
WHITE       = (255, 255, 255)
BLUE        = (0,   0,   255)
CYAN        = (0,   255, 255)
PINK        = (255, 102, 255)
WHITE       = (255, 255, 255)
NAVYBLUE    = ( 60,  60, 100)
RED         = (200,   0,   0)
DARKRED     = (100,   0,   0)
ORANGE      = (200, 100,   0)
DARKORANGE  = (100,  50,   0)
YELLOW      = (200, 200,   0)
DARKYELLOW  = (100, 100,   0)
GREEN       = (  0, 200,   0)
DARKGREEN   = (  0, 100,   0)
BLUE        = (  0,   0, 200)
DARKBLUE    = (  0,   0, 100)
PURPLE      = (200,   0, 200)
DARKPURPLE  = (100,   0, 100)

RAINBOW = [(RED, DARKRED),(ORANGE,DARKORANGE),(YELLOW,DARKYELLOW),
           (GREEN,DARKGREEN),(BLUE,DARKBLUE),(PURPLE,DARKPURPLE)]
BRICK_COLOR =(0,   255, 255)

# State constants
STATE_BALL_IN_PADDLE = 0
STATE_PLAYING = 1
STATE_GAME_WON = 2
STATE_GAME_OVER = 3
STATE_LEVEL_ONE = 4
STATE_LEVEL_TWO = 5
STATE_LEVEL_THREE = 6
STATE_LEVEL_ONE_WON = 7
STATE_LEVEL_TWO_WON = 8
STATE_LEVEL_THREE_WON = 9

# Initialising pygame
class Breakout:
    def __init__(self):
        pygame.init()

        self.screen = pygame.display.set_mode(SCREEN_SIZE)
        pygame.display.set_caption("Breakout")

        self.clock = pygame.time.Clock()

        if pygame.font:
            self.font = pygame.font.SysFont("impact", 20)
        else:
            self.font = None

        self.init_game()

    def init_game(self):
        self.lives = 3
        self.score = 0
        self.state = STATE_BALL_IN_PADDLE
        self.level_state = STATE_LEVEL_ONE

        self.paddle   = pygame.Rect(300,PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT)
        self.ball     = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER)

        self.ball_vel = [5,-5]

        self.create_bricks()


    def draw_level_one(self):
        self.lives = 3
        self.score = 0
        self.level_state = STATE_LEVEL_ONE
        self.state = STATE_BALL_IN_PADDLE

        self.paddle = pygame.Rect(300, PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT)
        self.ball   = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER)

        self.ball_vel = [5,-5]

        self.create_bricks()

    def draw_level_two(self):
        self.lives = 3
        self.score = 0
        self.level_state = STATE_LEVEL_TWO
        self.state = STATE_BALL_IN_PADDLE

        self.paddle   = pygame.Rect(300,PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT)
        self.ball     = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER)

        self.ball_vel = [5,-5]

        self.create_bricks()

    def draw_level_three(self):
        self.lives = 3
        self.score = 0
        self.level_state = STATE_LEVEL_THREE
        self.state = STATE_BALL_IN_PADDLE

        self.paddle   = pygame.Rect(300,PADDLE_Y,PADDLE_WIDTH,PADDLE_HEIGHT)
        self.ball     = pygame.Rect(300,PADDLE_Y - BALL_DIAMETER,BALL_DIAMETER,BALL_DIAMETER)

        self.ball_vel = [5,-5]

        self.create_bricks()

    def create_bricks(self):
        y_ofs = 50
        self.bricks = []
        for i in range(7):
            x_ofs = 50
            for j in range(8):
                self.bricks.append(pygame.Rect(x_ofs,y_ofs,BRICK_WIDTH,BRICK_HEIGHT))
                x_ofs += BRICK_WIDTH + 10
            y_ofs += BRICK_HEIGHT + 5

    def draw_bricks(self):
        if self.level_state == STATE_LEVEL_ONE:
            for brick in self.bricks:
                pygame.draw.rect(self.screen, CYAN, brick)

        elif self.level_state == STATE_LEVEL_TWO:
            for brick in self.bricks:
                pygame.draw.rect(self.screen, GREEN, brick)

        elif self.level_state == STATE_LEVEL_THREE:
            for brick in self.bricks:
                pygame.draw.rect(self.screen, DARKRED, brick)

    def check_input(self):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_LEFT]:
            self.paddle.left -= 7
            if self.paddle.left < 0:
                self.paddle.left = 0

        if keys[pygame.K_RIGHT]:
            self.paddle.left += 7
            if self.paddle.left > MAX_PADDLE_X:
                self.paddle.left = MAX_PADDLE_X

        if keys[pygame.K_SPACE] and self.state == STATE_BALL_IN_PADDLE:
            self.ball_vel = [5,-5]
            self.state = STATE_PLAYING
        elif keys[pygame.K_RETURN] and (self.state == STATE_GAME_OVER
                                            or self.state == STATE_GAME_WON):
                                            self.draw_level_one()
        if keys[pygame.K_w]:
            self.state = STATE_GAME_WON
            self.ball.left = self.paddle.left + self.paddle.width / 2
            self.ball.top  = self.paddle.top - self.ball.height

        if keys[pygame.K_l]:
            self.state = STATE_GAME_OVER
            self.ball.left = self.paddle.left + self.paddle.width / 2
            self.ball.top  = self.paddle.top - self.ball.height

        if keys[pygame.K_1]:
            self.draw_level_one()
            self.ball.left = self.paddle.left + self.paddle.width / 2
            self.ball.top  = self.paddle.top - self.ball.height

        if keys[pygame.K_2]:
            self.draw_level_two()
            self.ball.left = self.paddle.left + self.paddle.width / 2
            self.ball.top  = self.paddle.top - self.ball.height

        if keys[pygame.K_3]:
            self.draw_level_three()
            self.ball.left = self.paddle.left + self.paddle.width / 2
            self.ball.top  = self.paddle.top - self.ball.height

        if self.level_state == STATE_LEVEL_ONE_WON:
            if keys[pygame.K_KP_ENTER]:
                self.draw_level_two()
                self.ball.left = self.paddle.left + self.paddle.width / 2
                self.ball.top  = self.paddle.top - self.ball.height
        if self.level_state == STATE_LEVEL_TWO_WON:
            if keys[pygame.K_KP_ENTER]:
                self.draw_level_three()
                self.ball.left = self.paddle.left + self.paddle.width / 2
                self.ball.top  = self.paddle.top - self.ball.height
        if self.level_state == STATE_LEVEL_THREE_WON:
            if keys[pygame.K_KP_ENTER]:
                self.draw_level_one()
                self.ball.left = self.paddle.left + self.paddle.width / 2
                self.ball.top  = self.paddle.top - self.ball.height

        if keys[pygame.K_ESCAPE]:
            sys.exit()

    def move_ball (self):
        self.ball.left += self.ball_vel[0]
        self.ball.top += self.ball_vel[1]

        #bounds check
        if self.ball.left <= 0:
            self.ball.left = 0
            self.ball_vel[0] = -self.ball_vel[0]
        elif self.ball.left >= MAX_BALL_X:
            self.ball.left = MAX_BALL_X
            self.ball_vel[0] = -self.ball_vel[0]

        if self.ball.top < 0:
            self.ball.top = 0
            self.ball_vel[1] = -self.ball_vel[1]
        elif self.ball.top >= MAX_BALL_Y:
            self.ball.top = MAX_BALL_Y
            self.ball_vel[1] = -self.ball_vel[1]


    def handle_collisions(self):

        self.brick_counter = 0

        for brick in self.bricks:
            if self.ball.colliderect(brick):
                #self.brick_counter += 1
                self.score += 3
                self.ball_vel[1] = -self.ball_vel[1]
                #if self.brick_counter == 2:
                self.bricks.remove(brick)
                break

        if len(self.bricks) == 0 or self.state == STATE_GAME_WON:
            if self.level_state == STATE_LEVEL_ONE:
                self.level_state == STATE_LEVEL_ONE_WON
            elif self.level_state == STATE_LEVEL_TWO:
                self.level_state == STATE_LEVEL_TWO_WON
            elif self.level_state == STATE_LEVEL_THREE:
                self.level_state == STATE_LEVEL_THREE_WON

        if self.ball.colliderect(self.paddle):
            self.ball.top = PADDLE_Y - BALL_DIAMETER
            self.ball_vel[1] = -self.ball_vel[1]
        elif self.ball.top > self.paddle.top:
            self.lives -= 1
            if self.lives > 0:
                self.state = STATE_BALL_IN_PADDLE
            else:
                self.state = STATE_GAME_OVER

    def show_stats(self):
        if self.font:
            font_surface = self.font.render("SCORE: " + str(self.score)
                                             + " LIVES: " + str(self.lives), False, WHITE)
            self.screen.blit(font_surface, (205, 5))

    def show_message(self, message):
        if self.font:
            size = self.font.size(message)
            font_surface = self.font.render(message, False, WHITE)
            x = (SCREEN_SIZE[0] - size[0]) /2
            y = (SCREEN_SIZE[1] - size[1]) /2
            self.screen.blit(font_surface, (x,y))

    def run(self):
        while 1:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()

            self.clock.tick(60)
            self.screen.fill(BLACK)
            self.check_input()
            self.handle_collisions()

            if self.state == STATE_PLAYING:
                self.move_ball()
                self.handle_collisions()
            elif self.state == STATE_BALL_IN_PADDLE:
                self.ball.left = self.paddle.left + self.paddle.width / 2
                self.ball.top = self.paddle.top - self.ball.height
                self.show_message("PRESS SPACE TO LAUNCH THE BALL")
            elif self.state == STATE_GAME_OVER:
                self.show_message("GAME OVER. PRESS ENTER TO PLAY AGAIN")
            elif self.state == STATE_GAME_WON:
                self.show_message("YOU WON! PRESS ENTER TO GO TO THE NEXT LEVEL")

            self.draw_bricks()

            #Draw paddle
            pygame.draw.rect(self.screen, PINK, self.paddle)

            #DraW ball
            pygame.draw.circle(self.screen, WHITE, (self.ball.left + BALL_RADIUS,
                               self.ball.top + BALL_RADIUS), BALL_RADIUS)

            self.show_stats()

            pygame.display.flip()

if __name__ == "__main__":
    Breakout().run()

看起来您正在使用 pygame.Rect 作为砖块对象,并使用单个 global 计数器来计算砖块被击中的次数。一旦任何一块砖被击中两次,那么计数器就不再能正常发挥作用。您真正应该做的是创建一个同时具有矩形和计数器的 Brick class,并更新该砖的计数器。

class Brick:
  def __init__(self, rect):
    self.rect = rect
    self.hit_counter = 2

然后 self.bricks 将是这些对象的列表。

此外,您似乎假设砖块将从底部或顶部碰撞并导致速度的 y 分量反转。如果它从侧面碰撞,反弹看起来会有点奇怪,并且可能会让人感觉球是 "just passing through"。在发生碰撞时,您必须查看球相对于砖块的位置,然后决定是翻转速度的 x 分量还是翻转 y 分量。

类似这样的事情(抱歉,我可能没有完全匹配您的字段,但您明白了):

dx = brick.centerx - ball.centerx
dy = brick.centery - ball.centery
if abs(dx) > abs(dy):
  self.ball_vel[0] *= -1
else:
  self.ball_vel[1] *= -1