# 第七章:遊戲結束與重置 ## 紀錄最高分數 * ### 程式碼 ```python= max_score = 0 while running: ... # 更新遊戲 ... if len(balls) == 0: max_score = max(max_score, score) ``` * ### 說明 當沒有球留在場中時,表示遊戲結束,紀錄當前的最高分數。 ## 遊戲結束 * ### 主程式 * #### 介紹 當遊戲結束時,呼叫 `show_game_over()` 顯示結束畫面。 跳過原先的更新遊戲流程,以及畫面顯示。 * #### 程式碼 ```python= game_over = False while running: ... if game_over: show_game_over() continue # 更新遊戲 ... if len(balls) == 0: ... game_over = True ``` * ### 遊戲結束畫面 * #### 介紹 1. 畫出結束時最後的畫面 2. 利用半透明黑色遮罩讓畫面變暗 3. 在畫面中央畫一個視窗 4. 在視窗上面寫「遊戲結束」,並寫上本次遊玩的分數以及最高分數 5. 在視窗下面畫一個按鈕,上面寫著「再玩一次」 * #### 程式碼 ```python= # 遊戲結束視窗 game_over_img = pygame.image.load(os.path.join('img', 'game_over.png')).convert() game_over_img = pygame.transform.scale(game_over_img, (GAME_OVER_LEN, GAME_OVER_LEN)) game_over_img.set_colorkey(BLACK) # 半透明黑色遮罩 black_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) black_surface.fill((0, 0, 0, 128)) # 再玩一次按鈕 again_button = pygame.Surface((BUTTON_WIDTH, BUTTON_HEIGHT)) again_button.fill(WHITE) draw_text(again_button, "再玩一次", 32, BUTTON_WIDTH // 2, BUTTON_HEIGHT // 2, BLACK) button_rect = again_button.get_rect() button_rect.center = (WIDTH // 2, HEIGHT // 2 + 130) def show_game_over(): screen.blit(background_img, (0, 0)) draw_text(screen, f"分數: {score}", 32, WIDTH // 2, 35) all_sprites.draw(screen) screen.blit(black_surface, (0, 0)) screen.blit(game_over_img, (WIDTH // 2 - GAME_OVER_LEN // 2, HEIGHT // 2 - GAME_OVER_LEN // 2)) draw_text(screen, "遊戲結束", 48, WIDTH // 2, HEIGHT // 2 - 130) draw_text(screen, "本次分數", 32, WIDTH // 2 - 90, HEIGHT // 2 - 25) draw_text(screen, str(score), 32, WIDTH // 2 - 90, HEIGHT // 2 + 25) draw_text(screen, "最高分數", 32, WIDTH // 2 + 90, HEIGHT // 2 - 25) draw_text(screen, str(max_score), 32, WIDTH // 2 + 90, HEIGHT // 2 + 25) screen.blit(again_button, button_rect) pygame.display.update() ``` ## 重新開始遊戲 * ### 按按鈕 * #### 介紹 判斷是否按下「再完一次」按鈕,若按下則呼叫 `start_game()` 重新開始遊戲。 * #### 程式碼 ```python= while running: ... if game_over: ... if event.type == pygame.MOUSEBUTTONDOWN: if button_rect.collidepoint(event.pos): start_game() continue ``` * ### `start_game()` 函式 * #### 介紹 1. 將原先的初始設定搬到 `start_game()` 函式中 1. 將全域變數設為 `global` 1. 將球速變回初始速度 * #### 程式碼 ```python= def start_game(): global all_sprites, board, balls, bricks, score, game_over score = 0 game_over = False Ball.speed = 7 # 初始宣告遊戲物件 all_sprites = pygame.sprite.Group() # 板子 board = Board((WIDTH // 2, HEIGHT - 40 * t)) all_sprites.add(board) # 球 balls = pygame.sprite.Group() ball = Ball(math.pi / 4, (WIDTH // 2, HEIGHT - 100 * t)) balls.add(ball) all_sprites.add(ball) # 磚塊 bricks = pygame.sprite.Group() for r in range(BRICK_ROWS): for c in range(BRICK_COLS): x = BRICK_MARGIN + c * (BRICK_WIDTH + BRICK_SPACING) + BRICK_SPACING / 2 y = BRICK_MARGIN + r * (BRICK_HEIGHT + BRICK_SPACING) brick = Brick(x, y) bricks.add(brick) all_sprites.add(bricks) ``` --- :::spoiler 完整程式碼 ```python= import pygame import math import random import os t = 1.5 FPS = 60 # 大小設定 WIDTH, HEIGHT = 800, 700 BRICK_ROWS = 5 BRICK_COLS = 8 BRICK_MARGIN = 70 BRICK_SPACING = 8 BRICK_WIDTH = (WIDTH - BRICK_MARGIN * 2) // BRICK_COLS - BRICK_SPACING BRICK_HEIGHT = 45 BOARD_SIZE = (150, 30) BALL_RADIUS = 15 GAME_OVER_LEN = 400 BUTTON_WIDTH = 160 BUTTON_HEIGHT = 60 # 顏色設定 WHITE = (255, 255, 255) RED = (255, 0, 0) BLUE = (0, 0, 255) GREEN = (0, 255, 0) BLACK = (0, 0, 0) # 遊戲初始化 pygame.init() pygame.mixer.init() screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("打磚塊遊戲") clock = pygame.time.Clock() # 載入圖片 # 背景圖片 background_img = pygame.image.load(os.path.join('img', 'background.jpg')).convert() background_img = pygame.transform.scale(background_img, (WIDTH, HEIGHT)) # 磚塊圖片 brick_img = pygame.image.load(os.path.join('img', 'chocolate.png')).convert() brick_img = pygame.transform.scale(brick_img, (BRICK_WIDTH, BRICK_HEIGHT)) brick_img.set_colorkey(BLACK) # 板子圖片 board_img = pygame.image.load(os.path.join('img', 'wood.png')).convert() board_img = pygame.transform.scale(board_img, BOARD_SIZE) board_img.set_colorkey(BLACK) # 球圖片 ball_img = pygame.image.load(os.path.join('img', 'ball.png')).convert() ball_img = pygame.transform.scale(ball_img, (BALL_RADIUS * 2, BALL_RADIUS * 2)) ball_img.set_colorkey(BLACK) # 視窗圖示 icon_img = pygame.image.load(os.path.join('img', 'icon.jpg')).convert() pygame.display.set_icon(icon_img) # 遊戲結束視窗 game_over_img = pygame.image.load(os.path.join('img', 'game_over.png')).convert() game_over_img = pygame.transform.scale(game_over_img, (GAME_OVER_LEN, GAME_OVER_LEN)) game_over_img.set_colorkey(BLACK) # 載入音效 hit_sound = pygame.mixer.Sound(os.path.join('sound', 'hit.wav')) font_path = 'font.ttf' def draw_text(surf, text, size, x, y, color=WHITE): font = pygame.font.Font(font_path, size) text_surface = font.render(text, True, color) text_rect = text_surface.get_rect() text_rect.center = (x, y) surf.blit(text_surface, text_rect) # 半透明黑色遮罩 black_surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) black_surface.fill((0, 0, 0, 128)) # 再玩一次按鈕 again_button = pygame.Surface((BUTTON_WIDTH, BUTTON_HEIGHT)) again_button.fill(WHITE) draw_text(again_button, "再玩一次", 32, BUTTON_WIDTH // 2, BUTTON_HEIGHT // 2, BLACK) button_rect = again_button.get_rect() button_rect.center = (WIDTH // 2, HEIGHT // 2 + 130) def circle_rect_collision(rectangle, circle): cx, cy = circle.rect.center r = circle.radius rx, ry = rectangle.rect.topleft w, h = rectangle.rect.size # 找出最近點 closest_x = max(rx, min(cx, rx + w)) closest_y = max(ry, min(cy, ry + h)) # 計算距離 distance = math.sqrt((closest_x - cx) ** 2 + (closest_y - cy) ** 2) # 判斷碰撞 return distance < r def show_game_over(): screen.blit(background_img, (0, 0)) draw_text(screen, f"分數: {score}", 32, WIDTH // 2, 35) all_sprites.draw(screen) screen.blit(black_surface, (0, 0)) screen.blit(game_over_img, (WIDTH // 2 - GAME_OVER_LEN // 2, HEIGHT // 2 - GAME_OVER_LEN // 2)) draw_text(screen, "遊戲結束", 48, WIDTH // 2, HEIGHT // 2 - 130) draw_text(screen, "本次分數", 32, WIDTH // 2 - 90, HEIGHT // 2 - 25) draw_text(screen, str(score), 32, WIDTH // 2 - 90, HEIGHT // 2 + 25) draw_text(screen, "最高分數", 32, WIDTH // 2 + 90, HEIGHT // 2 - 25) draw_text(screen, str(max_score), 32, WIDTH // 2 + 90, HEIGHT // 2 + 25) screen.blit(again_button, button_rect) pygame.display.update() def start_game(): global all_sprites, board, balls, bricks, score, game_over score = 0 game_over = False Ball.speed = 7 # 初始宣告遊戲物件 all_sprites = pygame.sprite.Group() # 板子 board = Board((WIDTH // 2, HEIGHT - 40 * t)) all_sprites.add(board) # 球 balls = pygame.sprite.Group() ball = Ball(math.pi / 4, (WIDTH // 2, HEIGHT - 100 * t)) balls.add(ball) all_sprites.add(ball) # 磚塊 bricks = pygame.sprite.Group() for r in range(BRICK_ROWS): for c in range(BRICK_COLS): x = BRICK_MARGIN + c * (BRICK_WIDTH + BRICK_SPACING) + BRICK_SPACING / 2 y = BRICK_MARGIN + r * (BRICK_HEIGHT + BRICK_SPACING) brick = Brick(x, y) bricks.add(brick) all_sprites.add(bricks) # 磚塊類別 class Brick(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image = brick_img self.rect = self.image.get_rect() self.rect.topleft = x, y # 板子類別 class Board(pygame.sprite.Sprite): def __init__(self, center): super().__init__() self.image = board_img self.rect = self.image.get_rect() self.rect.center = center self.speed = 10 * t def update(self): keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: self.rect.x -= self.speed if self.rect.left < 0: self.rect.left = 0 if keys[pygame.K_RIGHT]: self.rect.x += self.speed if self.rect.right > WIDTH: self.rect.right = WIDTH # 球類別 class Ball(pygame.sprite.Sprite): speed = 7 def __init__(self, angle, center): super().__init__() self.radius = BALL_RADIUS self.image = ball_img self.rect = self.image.get_rect() self.rect.center = center self.dx = Ball.speed * math.cos(angle) self.dy = -Ball.speed * math.sin(angle) self.speed_factor = 1.03 def update(self): self.rect.x += self.dx self.rect.y += self.dy # 邊界反彈 if self.rect.left < 0: self.rect.left = 0 self.dx = -self.dx elif self.rect.right > WIDTH: self.rect.right = WIDTH self.dx = -self.dx if self.rect.top <= 0: self.rect.top = 0 self.dy = -self.dy elif self.rect.top >= HEIGHT: self.kill() def hit_board(self, board_rect): # 計算角度 max_angle = math.pi / 3 offset = (self.rect.centerx - board_rect.centerx) / (board_rect.width / 2) angle = max_angle * offset # 計算 dx, dy self.dx = Ball.speed * math.sin(angle) self.dy = -Ball.speed * math.cos(angle) def hit_brick(self, brick_rect): hit_sound.play() # 加速 self.dx *= self.speed_factor self.dy *= self.speed_factor Ball.speed *= self.speed_factor # 計算四個方向的重疊量 overlap_left = abs(self.rect.right - brick_rect.left) overlap_right = abs(self.rect.left - brick_rect.right) overlap_top = abs(self.rect.bottom - brick_rect.top) overlap_bottom = abs(self.rect.top - brick_rect.bottom) min_overlap = min(overlap_left, overlap_right, overlap_top, overlap_bottom) # 判斷反彈方向 if min_overlap == overlap_left and self.dx > 0: self.dx = -self.dx elif min_overlap == overlap_right and self.dx < 0: self.dx = -self.dx else: self.dy = -self.dy # 隨機產生新球 if random.random() < 0.1: self.create_new_ball() def create_new_ball(self): new_ball = Ball(math.pi * 3 / 4, self.rect.center) balls.add(new_ball) all_sprites.add(new_ball) start_game() max_score = 0 # 主迴圈 running = True while running: clock.tick(FPS) # 輸入處理 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if game_over: show_game_over() if event.type == pygame.MOUSEBUTTONDOWN: if button_rect.collidepoint(event.pos): start_game() continue # 更新遊戲 all_sprites.update() if len(balls) == 0: max_score = max(max_score, score) game_over = True # 球碰板子 hits = pygame.sprite.spritecollide(board, balls, False, circle_rect_collision) for ball in hits: ball.hit_board(board.rect) # 球碰磚塊 hits = pygame.sprite.groupcollide(bricks, balls, True, False, circle_rect_collision) for brick, hit_balls in hits.items(): hit_balls[0].hit_brick(brick.rect) score += 1 # 畫面顯示 screen.blit(background_img, (0, 0)) draw_text(screen, f"分數: {score}", 32, WIDTH // 2, 35) all_sprites.draw(screen) pygame.display.update() pygame.quit() ``` :::