Python基于Pygame開發(fā)的經(jīng)典打磚塊游戲
項目概述
Pygame 是 Python 中一個功能強(qiáng)大的 2D 游戲開發(fā)庫,它提供了處理圖形、聲音、輸入和事件循環(huán)的完整工具集。本文通過 Pygame 實現(xiàn)一個經(jīng)典打磚塊游戲(Breakout)。
在這個游戲中,玩家控制屏幕底部的擋板,彈射小球擊碎上方所有磚塊。每塊磚擁有獨(dú)立的生命值,顏色隨血量變化而加深,擊中即扣血,歸零后磚塊消失并觸發(fā)粒子爆炸效果,目標(biāo)是在不失去所有球命的情況下清空全部磚塊。其中:
- 移動擋板:左右方向鍵 / A、D 鍵水平移動擋板;擋板擊球時,根據(jù)球撞擊擋板的偏移位置改變出球角度,越靠近兩端反彈角度越大。
- 發(fā)球:按空格鍵發(fā)球,發(fā)球方向在豎直向上的基礎(chǔ)上加入隨機(jī)偏角(±0.5 弧度),每局可多次發(fā)球(失球不扣關(guān)卡)。
- 磚塊血量:不同行的磚塊初始血量不同(最高行血量最高),每次被球擊中扣 1 點血;血量 > 0 時磚塊上顯示剩余血量數(shù)字,血量歸零后磚塊消失。
- 關(guān)卡遞進(jìn):清空全部磚塊后自動進(jìn)入下一關(guān),球速隨關(guān)卡提升(上限 9.0);當(dāng)前關(guān)卡、得分和最高分實時顯示在 HUD 欄。
- 鍵盤操作:SPACE:發(fā)球;R:重新開始;P:暫停/繼續(xù)。
- 勝利/失敗條件:球掉落到屏幕底部扣一條命;命數(shù)歸零 = 游戲失敗;清空所有磚塊 = 過關(guān)(自動進(jìn)入下一關(guān),無上限)。
游戲?qū)崿F(xiàn)

初始化與基礎(chǔ)設(shè)置
游戲啟動時初始化 Pygame 并定義屏幕尺寸、HUD 高度、顏色常量和磚塊布局參數(shù)。
pygame.init()
WIDTH, HEIGHT = 480, 580
HUD_H = 56
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Breakout: Brick Blaster")
clock = pygame.time.Clock()
顏色定義
DARK_BG = (3, 8, 16) # 全局深色背景 BLUE1 = (56, 138, 221) # 擋板主色 / 球外圈 BLUE2 = (133,183, 235) # 擋板高光 / 球內(nèi)圈 WHITE = (220, 225, 235) # 文字 / 球高光 GRAY = (100, 115, 130) # 次要文字 RED = (226, 75, 74) # 生命指示圓 / 游戲結(jié)束文字 GOLD = (250, 199, 117) # 暫停文字 GREEN = (93, 202, 165) # 關(guān)卡文字
磚塊顏色與布局
BRICK_COLORS = [
(83, 74, 183), # 紫色 第 0 行(最高血量)
(24, 95, 165), # 藍(lán)色 第 1 行
(15, 110, 86), # 青色 第 2 行
(59, 109, 17), # 綠色 第 3 行
(186,117, 23), # 琥珀 第 4 行
(152, 60, 29), # 珊瑚 第 5 行(最低血量)
]
PAD_W, PAD_H = 80, 12 # 擋板寬高
BALL_R = 8 # 小球半徑
COLS, ROWS_BRICKS = 8, 6 # 磚塊列數(shù)、行數(shù)
B_GAP = 6 # 磚塊間距
BW = (WIDTH - 40 - (COLS - 1) * B_GAP) // COLS # 單塊寬度
BH = 18 # 單塊高度
``
### 字體加載
```python
CHINESE_FONT_PATH = r"C:/Windows/Fonts/simsun.ttc"
font_sm = pygame.font.Font(CHINESE_FONT_PATH, 20)
font_md = pygame.font.Font(CHINESE_FONT_PATH, 28)
font_big = pygame.font.Font(CHINESE_FONT_PATH, 52)
直接加載系統(tǒng)中文字體文件,保證中文 HUD 文字正常顯示??缙脚_時可替換為 pygame.font.SysFont(None, size)。
核心類設(shè)計
1. 擋板類(Paddle)
Paddle 類封裝擋板的位置、移動邏輯和繪制方法。
構(gòu)造函數(shù) __init__:
def __init__(self):
self.x = WIDTH // 2 # 水平中心位置(以擋板中心為基準(zhǔn))
self.y = HEIGHT - 36 # 固定在屏幕底部附近
self.speed = 7 # 每幀移動像素數(shù)
移動方法 move:
def move(self, dx):
self.x = max(PAD_W // 2, min(WIDTH - PAD_W // 2, self.x + dx))
通過 max/min 將擋板限制在屏幕邊界內(nèi),防止超出屏幕。
矩形區(qū)域 rect:
def rect(self):
return pygame.Rect(self.x - PAD_W // 2, self.y, PAD_W, PAD_H)
以 self.x 為中心計算左邊界,便于后續(xù)碰撞檢測復(fù)用。
繪制方法 draw:
def draw(self, surf):
r = self.rect()
pygame.draw.rect(surf, BLUE1, r, border_radius=6) # 底層主色
pygame.draw.rect(surf, BLUE2, r.inflate(-20, -4), border_radius=4) # 高光條
用兩層矩形疊加實現(xiàn)簡潔的高光立體感:外層為深藍(lán)主色,內(nèi)層收縮后繪制淺藍(lán)高光條。
2. 小球類(Ball)
Ball 類管理小球的物理運(yùn)動、邊界反彈和出屏檢測。
構(gòu)造函數(shù) __init__:
def __init__(self, px, py):
self.x = float(px)
self.y = float(py - BALL_R - 2) # 初始貼在擋板上方
self.vx = 0.0
self.vy = 0.0
self.launched = False # 是否已發(fā)球
self.radius = BALL_R
未發(fā)球時小球跟隨擋板水平移動,按空格后才獲得速度。
發(fā)球方法 launch:
def launch(self, speed):
ang = -math.pi / 2 + random.uniform(-0.5, 0.5) # 在豎直向上基礎(chǔ)上隨機(jī)偏角
self.vx = speed * math.cos(ang)
self.vy = speed * math.sin(ang)
self.launched = True
使用極坐標(biāo)分解速度,random.uniform(-0.5, 0.5) 約 ±28.6°的隨機(jī)偏角讓每局發(fā)球方向各不相同,增加趣味性。
更新方法 update:
def update(self, pad_x):
if not self.launched:
self.x = float(pad_x) # 未發(fā)球時跟隨擋板
return
self.x += self.vx
self.y += self.vy
# 左右邊界反彈
if self.x - self.radius < 0:
self.x = float(self.radius); self.vx = abs(self.vx)
if self.x + self.radius > WIDTH:
self.x = float(WIDTH - self.radius); self.vx = -abs(self.vx)
# 頂部邊界反彈(HUD 下沿)
if self.y - self.radius < HUD_H:
self.y = float(HUD_H + self.radius); self.vy = abs(self.vy)
三面墻反彈均使用 abs() 強(qiáng)制方向,避免因浮點誤差導(dǎo)致球"穿墻"后速度方向錯誤的情況。
出屏檢測 off_screen:
def off_screen(self):
return self.y - self.radius > HEIGHT
繪制方法 draw:
def draw(self, surf):
ix, iy = int(self.x), int(self.y)
pygame.draw.circle(surf, BLUE1, (ix, iy), self.radius + 2) # 外發(fā)光圈
pygame.draw.circle(surf, BLUE2, (ix, iy), self.radius) # 主球體
pygame.draw.circle(surf, WHITE, (ix - 2, iy - 2), 3) # 高光點
三層圓形疊繪:外層比主球半徑大 2px 的深藍(lán)光暈,中層淺藍(lán)主體,右上角白色高光點,使球體具有立體感。
3. 磚塊類(Brick)
Brick 類封裝單塊磚的狀態(tài)、血量和帶漸變的繪制邏輯。
構(gòu)造函數(shù) __init__:
def __init__(self, col, row, hp):
self.col = col
self.row = row
self.hp = hp # 當(dāng)前血量
self.max_hp = hp # 初始最大血量(用于計算顏色漸變比例)
self.alive = True
self.x = 20 + col * (BW + B_GAP) # 像素坐標(biāo)(左上角)
self.y = HUD_H + 20 + row * (BH + B_GAP)
繪制方法 draw:
def draw(self, surf):
if not self.alive:
return
frac = self.hp / self.max_hp # 血量比例 0.0~1.0
base = BRICK_COLORS[self.row % len(BRICK_COLORS)]
color = tuple(int(c * (0.4 + 0.6 * frac)) for c in base) # 血量越少顏色越暗
r = self.rect()
pygame.draw.rect(surf, color, r, border_radius=4)
border_a = int(80 * frac) # 邊框透明度隨血量變化
pygame.draw.rect(surf, (*WHITE[:3], border_a), r, width=1, border_radius=4)
if self.max_hp > 1:
label = font_sm.render(str(self.hp), True,
tuple(min(255, c + 120) for c in color))
surf.blit(label, (r.centerx - label.get_width() // 2,
r.centery - label.get_height() // 2))
該方法實現(xiàn)了三層視覺反饋:
- 顏色變暗:
0.4 + 0.6 * frac將血量映射到亮度,滿血時最亮,瀕死時保留 40% 亮度(不完全變黑)。 - 邊框漸隱:白色邊框透明度隨血量同步減弱,強(qiáng)化"受損"的視覺感。
- 血量數(shù)字:血量 > 1 的磚塊在中心繪制剩余血量,文字色在磚塊底色基礎(chǔ)上提亮 120,保證可讀性。
4. 粒子類(Particle)
Particle 類實現(xiàn)擊磚時的粒子爆炸效果,模擬帶重力的碎片運(yùn)動。
構(gòu)造函數(shù) __init__:
def __init__(self, x, y, color):
self.x = float(x)
self.y = float(y)
self.vx = random.uniform(-3, 3) # 水平隨機(jī)速度
self.vy = random.uniform(-4, 0.5) # 初始向上(含少量向下)
self.life = 28 # 生命幀數(shù)
self.color = color
更新方法 update:
def update(self):
self.x += self.vx
self.y += self.vy
self.vy += 0.2 # 模擬重力加速度
self.life -= 1
每幀對 vy 施加 0.2 的向下加速度,模擬重力拋物線軌跡。
繪制方法 draw:
def draw(self, surf):
if self.life <= 0:
return
a = min(255, self.life * 9) # 透明度隨生命衰減
s = pygame.Surface((5, 5), pygame.SRCALPHA)
s.fill((*self.color[:3], a))
surf.blit(s, (int(self.x), int(self.y)))
使用 SRCALPHA 透明 Surface 繪制帶 Alpha 通道的小方塊,life * 9 使粒子在約 28 幀內(nèi)從不透明線性淡出至消失。
5. 游戲主類(Game)
Game 類統(tǒng)籌所有子系統(tǒng),負(fù)責(zé)關(guān)卡管理、碰撞檢測、狀態(tài)更新和畫面渲染。
構(gòu)造函數(shù) __init__:
def __init__(self):
self.best = 0
self.hp_values = [13, 11, 7, 5, 3, 2] # 各行磚塊初始血量(由上至下遞減)
self.reset()
hp_values 列表決定每行磚塊的初始生命值,第 0 行(最上方)最厚實,第 5 行(最下方)最薄,鼓勵玩家優(yōu)先擊打下方磚塊。
關(guān)卡初始化 _init_level:
def _init_level(self):
self.paddle = Paddle()
self.ball = Ball(self.paddle.x, self.paddle.y)
self.bricks = [
Brick(c, r, self.hp_values[r])
for r in range(ROWS_BRICKS)
for c in range(COLS)
]
使用列表推導(dǎo)式快速生成 6×8 共 48 塊磚,每行血量由 hp_values[r] 統(tǒng)一控制。
事件處理 handle_events:
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
self.reset()
if event.key == pygame.K_p:
self.paused = not self.paused
if event.key == pygame.K_SPACE and not self.ball.launched:
self.ball.launch(self.ball_speed)
核心更新 update:
update 方法集中處理三段碰撞檢測邏輯:
def update(self):
if self.paused or self.game_over or self.win:
return
# ① 擋板移動
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] or keys[pygame.K_a]: self.paddle.move(-self.paddle.speed)
if keys[pygame.K_RIGHT] or keys[pygame.K_d]: self.paddle.move(self.paddle.speed)
self.ball.update(self.paddle.x)
# ② 球出屏 → 扣命
if self.ball.off_screen():
self.lives -= 1
if self.lives <= 0:
self.game_over = True
if self.score > self.best: self.best = self.score
else:
self.ball = Ball(self.paddle.x, self.paddle.y)
return
# ③ 球 vs 擋板
pr = self.paddle.rect()
b = self.ball
if (b.vy > 0 and
pr.left - b.radius <= b.x <= pr.right + b.radius and
pr.top - b.radius <= b.y <= pr.bottom):
b.vy = -abs(b.vy)
offset = (b.x - self.paddle.x) / (PAD_W / 2) # -1.0 ~ +1.0
b.vx = offset * abs(b.vy) * 1.4 # 偏移越大,水平速度越強(qiáng)
spd = math.hypot(b.vx, b.vy)
cap = self.ball_speed * 1.5
if spd > cap:
b.vx *= cap / spd; b.vy *= cap / spd # 限速,防止球速失控
b.y = float(pr.top - b.radius - 1) # 防止球嵌入擋板
# ④ 球 vs 磚塊
for brick in self.bricks:
if not brick.alive: continue
br = brick.rect()
if not (b.x + b.radius > br.left and b.x - b.radius < br.right and
b.y + b.radius > br.top and b.y - b.radius < br.bottom):
continue
# 判斷從哪個方向擊中(水平穿透量 vs 垂直穿透量)
ol = abs(b.x + b.radius - br.left)
or_ = abs(br.right - (b.x - b.radius))
ot = abs(b.y + b.radius - br.top)
ob = abs(br.bottom - (b.y - b.radius))
minH = min(ol, or_)
minV = min(ot, ob)
if minH < minV: b.vx = -b.vx # 從側(cè)面擊中,反轉(zhuǎn)水平速度
else: b.vy = -b.vy # 從上/下?lián)糁?,反轉(zhuǎn)垂直速度
brick.hp -= 1
self.score += brick.max_hp * 10 * self.level
if self.score > self.best: self.best = self.score
for _ in range(12): # 生成粒子
self.particles.append(Particle(br.centerx, br.centery,
BRICK_COLORS[brick.row % len(BRICK_COLORS)]))
if brick.hp <= 0: brick.alive = False
break # 每幀只處理一塊磚,防止多重碰撞
# ⑤ 粒子更新
for p in self.particles[:]:
p.update()
if p.life <= 0: self.particles.remove(p)
# ⑥ 過關(guān)檢測
if all(not br.alive for br in self.bricks):
self.level += 1
self.ball_speed = min(4.5 + self.level * 0.4, 9.0)
self._init_level()
碰撞檢測的核心思路:通過比較球在四個方向上的穿透深度(overlap),取最小值判斷實際接觸面:水平穿透淺 → 從側(cè)面撞;垂直穿透淺 → 從上/下撞。這種方法比復(fù)雜的幾何求交更簡潔,適合 AABB 碰撞場景。
繪制方法 draw:
def draw(self):
screen.fill(DARK_BG)
# 星空背景(固定點陣,無需每幀隨機(jī))
for i in range(80):
sx = (i * 137) % WIDTH
sy = (i * 251) % HEIGHT
b = 30 + (i % 4) * 15
pygame.draw.circle(screen, (b, b, b + 20), (sx, sy), 1)
# HUD 信息欄
pygame.draw.rect(screen, (8, 14, 26), (0, 0, WIDTH, HUD_H))
pygame.draw.line(screen, BLUE1, (0, HUD_H), (WIDTH, HUD_H), 1)
screen.blit(font_sm.render(f"分?jǐn)?shù) {self.score}", True, WHITE), (12, 10))
screen.blit(font_sm.render(f"最高 {self.best}", True, GRAY), (12, 32))
screen.blit(font_sm.render(f"LV {self.level}", True, GREEN), (WIDTH - 100, 10))
for li in range(self.lives): # 生命圓點
pygame.draw.circle(screen, RED, (WIDTH - 22 - li * 20, 34), 6)
# 游戲?qū)ο?
for br in self.bricks: br.draw(screen)
for p in self.particles: p.draw(screen)
self.paddle.draw(screen)
self.ball.draw(screen)
# 提示 / 暫停 / 游戲結(jié)束遮罩
if not self.ball.launched and not self.game_over and not self.win:
hint = font_sm.render("按 SPACE 發(fā)球", True, GRAY)
screen.blit(hint, (WIDTH // 2 - hint.get_width() // 2, HEIGHT - 70))
if self.paused:
overlay = pygame.Surface((WIDTH, HEIGHT - HUD_H), pygame.SRCALPHA)
overlay.fill((3, 8, 16, 160))
screen.blit(overlay, (0, HUD_H))
t = font_big.render("PAUSED", True, GOLD)
screen.blit(t, (WIDTH // 2 - t.get_width() // 2, HEIGHT // 2 - 30))
if self.game_over:
overlay = pygame.Surface((WIDTH, HEIGHT - HUD_H), pygame.SRCALPHA)
overlay.fill((3, 8, 16, 190))
screen.blit(overlay, (0, HUD_H))
t1 = font_big.render("GAME OVER", True, RED)
t2 = font_sm.render(f"得分 {self.score} 最高 {self.best}", True, WHITE)
t3 = font_sm.render("按 R 重新開始", True, GRAY)
screen.blit(t1, (WIDTH // 2 - t1.get_width() // 2, HEIGHT // 2 - 60))
screen.blit(t2, (WIDTH // 2 - t2.get_width() // 2, HEIGHT // 2 + 10))
screen.blit(t3, (WIDTH // 2 - t3.get_width() // 2, HEIGHT // 2 + 44))
pygame.display.flip()
繪制順序為:背景 → 星空 → HUD → 磚塊 → 粒子 → 擋板 → 球 → 疊加遮罩,嚴(yán)格保證層次正確。
主循環(huán) run:
def run(self):
while True:
self.handle_events()
self.update()
self.draw()
clock.tick(60)
固定 60 FPS,保證物理運(yùn)動和動畫在不同性能機(jī)器上表現(xiàn)一致。
全部代碼
import pygame
import sys
import math
import random
pygame.init()
WIDTH, HEIGHT = 480, 580
HUD_H = 56
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Breakout: Brick Blaster")
clock = pygame.time.Clock()
DARK_BG = (3, 8, 16)
BLUE1 = (56, 138, 221)
BLUE2 = (133, 183, 235)
WHITE = (220, 225, 235)
GRAY = (100, 115, 130)
RED = (226, 75, 74)
GOLD = (250, 199, 117)
GREEN = (93, 202, 165)
BRICK_COLORS = [
(83, 74, 183), # purple row 0
(24, 95, 165), # blue row 1
(15, 110, 86), # teal row 2
(59, 109, 17), # green row 3
(186,117, 23), # amber row 4
(152, 60, 29), # coral row 5
]
CHINESE_FONT_PATH = r"C:/Windows/Fonts/simsun.ttc"
font_sm = pygame.font.Font(CHINESE_FONT_PATH, 20)
font_md = pygame.font.Font(CHINESE_FONT_PATH, 28)
font_big = pygame.font.Font(CHINESE_FONT_PATH, 52)
PAD_W, PAD_H = 80, 12
BALL_R = 8
COLS, ROWS_BRICKS = 8, 6
B_GAP = 6
BW = (WIDTH - 40 - (COLS - 1) * B_GAP) // COLS
BH = 18
class Paddle:
def __init__(self):
self.x = WIDTH // 2
self.y = HEIGHT - 36
self.speed = 7
def move(self, dx):
self.x = max(PAD_W // 2, min(WIDTH - PAD_W // 2, self.x + dx))
def rect(self):
return pygame.Rect(self.x - PAD_W // 2, self.y, PAD_W, PAD_H)
def draw(self, surf):
r = self.rect()
pygame.draw.rect(surf, BLUE1, r, border_radius=6)
pygame.draw.rect(surf, BLUE2, r.inflate(-20, -4).move(0, 0), border_radius=4)
class Ball:
def __init__(self, px, py):
self.x = float(px)
self.y = float(py - BALL_R - 2)
self.vx = 0.0
self.vy = 0.0
self.launched = False
self.radius = BALL_R
def launch(self, speed):
ang = -math.pi / 2 + random.uniform(-0.5, 0.5)
self.vx = speed * math.cos(ang)
self.vy = speed * math.sin(ang)
self.launched = True
def update(self, pad_x):
if not self.launched:
self.x = float(pad_x)
return
self.x += self.vx
self.y += self.vy
if self.x - self.radius < 0:
self.x = float(self.radius); self.vx = abs(self.vx)
if self.x + self.radius > WIDTH:
self.x = float(WIDTH - self.radius); self.vx = -abs(self.vx)
if self.y - self.radius < HUD_H:
self.y = float(HUD_H + self.radius); self.vy = abs(self.vy)
def off_screen(self):
return self.y - self.radius > HEIGHT
def draw(self, surf):
ix, iy = int(self.x), int(self.y)
pygame.draw.circle(surf, BLUE1, (ix, iy), self.radius + 2)
pygame.draw.circle(surf, BLUE2, (ix, iy), self.radius)
pygame.draw.circle(surf, WHITE, (ix - 2, iy - 2), 3)
class Brick:
def __init__(self, col, row, hp):
self.col = col
self.row = row
self.hp = hp
self.max_hp = hp
self.alive = True
self.x = 20 + col * (BW + B_GAP)
self.y = HUD_H + 20 + row * (BH + B_GAP)
def rect(self):
return pygame.Rect(self.x, self.y, BW, BH)
def draw(self, surf):
if not self.alive:
return
frac = self.hp / self.max_hp
base = BRICK_COLORS[self.row % len(BRICK_COLORS)]
color = tuple(int(c * (0.4 + 0.6 * frac)) for c in base)
r = self.rect()
pygame.draw.rect(surf, color, r, border_radius=4)
border_a = int(80 * frac)
pygame.draw.rect(surf, (*WHITE[:3], border_a), r, width=1, border_radius=4)
if self.max_hp > 1:
label = font_sm.render(str(self.hp), True,
tuple(min(255, c + 120) for c in color))
surf.blit(label, (r.centerx - label.get_width() // 2,
r.centery - label.get_height() // 2))
class Particle:
def __init__(self, x, y, color):
self.x = float(x)
self.y = float(y)
self.vx = random.uniform(-3, 3)
self.vy = random.uniform(-4, 0.5)
self.life = 28
self.color = color
def update(self):
self.x += self.vx
self.y += self.vy
self.vy += 0.2
self.life -= 1
def draw(self, surf):
if self.life <= 0:
return
a = min(255, self.life * 9)
s = pygame.Surface((5, 5), pygame.SRCALPHA)
s.fill((*self.color[:3], a))
surf.blit(s, (int(self.x), int(self.y)))
class Game:
def __init__(self):
self.best = 0
self.hp_values = [13, 11, 7, 5, 3, 2]
self.reset()
def reset(self):
self.score = 0
self.lives = 3
self.level = 1
self.ball_speed = 4.5
self.paused = False
self.game_over = False
self.win = False
self.particles = []
self._init_level()
def _init_level(self):
self.paddle = Paddle()
self.ball = Ball(self.paddle.x, self.paddle.y)
self.bricks = [
Brick(c, r, self.hp_values[r])
# Brick(c, r, random.randint(1, 10))
for r in range(ROWS_BRICKS)
for c in range(COLS)
]
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
self.reset()
if event.key == pygame.K_p:
self.paused = not self.paused
if event.key == pygame.K_SPACE and not self.ball.launched:
self.ball.launch(self.ball_speed)
def update(self):
if self.paused or self.game_over or self.win:
return
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.paddle.move(-self.paddle.speed)
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.paddle.move(self.paddle.speed)
self.ball.update(self.paddle.x)
# Ball lost
if self.ball.off_screen():
self.lives -= 1
if self.lives <= 0:
self.game_over = True
if self.score > self.best:
self.best = self.score
else:
self.ball = Ball(self.paddle.x, self.paddle.y)
return
# Ball vs paddle
pr = self.paddle.rect()
b = self.ball
if (b.vy > 0 and
pr.left - b.radius <= b.x <= pr.right + b.radius and
pr.top - b.radius <= b.y <= pr.bottom):
b.vy = -abs(b.vy)
offset = (b.x - self.paddle.x) / (PAD_W / 2)
b.vx = offset * abs(b.vy) * 1.4
spd = math.hypot(b.vx, b.vy)
cap = self.ball_speed * 1.5
if spd > cap:
b.vx *= cap / spd; b.vy *= cap / spd
b.y = float(pr.top - b.radius - 1)
# Ball vs bricks
for brick in self.bricks:
if not brick.alive:
continue
br = brick.rect()
if not (b.x + b.radius > br.left and b.x - b.radius < br.right and
b.y + b.radius > br.top and b.y - b.radius < br.bottom):
continue
ol = abs(b.x + b.radius - br.left)
or_ = abs(br.right - (b.x - b.radius))
ot = abs(b.y + b.radius - br.top)
ob = abs(br.bottom - (b.y - b.radius))
minH = min(ol, or_)
minV = min(ot, ob)
if minH < minV:
b.vx = -b.vx
else:
b.vy = -b.vy
brick.hp -= 1
pts = brick.max_hp * 10 * self.level
self.score += pts
if self.score > self.best:
self.best = self.score
color = BRICK_COLORS[brick.row % len(BRICK_COLORS)]
for _ in range(12):
self.particles.append(Particle(br.centerx, br.centery, color))
if brick.hp <= 0:
brick.alive = False
break
# Update particles
for p in self.particles[:]:
p.update()
if p.life <= 0:
self.particles.remove(p)
# Level clear?
if all(not br.alive for br in self.bricks):
self.level += 1
self.ball_speed = min(4.5 + self.level * 0.4, 9.0)
self._init_level()
def draw(self):
screen.fill(DARK_BG)
# subtle star dots
for i in range(80):
sx = (i * 137) % WIDTH
sy = (i * 251) % HEIGHT
b = 30 + (i % 4) * 15
pygame.draw.circle(screen, (b, b, b + 20), (sx, sy), 1)
# HUD
pygame.draw.rect(screen, (8, 14, 26), (0, 0, WIDTH, HUD_H))
pygame.draw.line(screen, BLUE1, (0, HUD_H), (WIDTH, HUD_H), 1)
screen.blit(font_sm.render(f"分?jǐn)?shù) {self.score}", True, WHITE), (12, 10))
screen.blit(font_sm.render(f"最高 {self.best}", True, GRAY), (12, 32))
screen.blit(font_sm.render(f"LV {self.level}", True, GREEN), (WIDTH - 100, 10))
# lives
for li in range(self.lives):
pygame.draw.circle(screen, RED, (WIDTH - 22 - li * 20, 34), 6)
for br in self.bricks:
br.draw(screen)
for p in self.particles:
p.draw(screen)
self.paddle.draw(screen)
self.ball.draw(screen)
if not self.ball.launched and not self.game_over and not self.win:
hint = font_sm.render("按 SPACE 發(fā)球", True, GRAY)
screen.blit(hint, (WIDTH // 2 - hint.get_width() // 2, HEIGHT - 70))
if self.paused:
overlay = pygame.Surface((WIDTH, HEIGHT - HUD_H), pygame.SRCALPHA)
overlay.fill((3, 8, 16, 160))
screen.blit(overlay, (0, HUD_H))
t = font_big.render("PAUSED", True, GOLD)
screen.blit(t, (WIDTH // 2 - t.get_width() // 2, HEIGHT // 2 - 30))
if self.game_over:
overlay = pygame.Surface((WIDTH, HEIGHT - HUD_H), pygame.SRCALPHA)
overlay.fill((3, 8, 16, 190))
screen.blit(overlay, (0, HUD_H))
t1 = font_big.render("GAME OVER", True, RED)
t2 = font_sm.render(f"得分 {self.score} 最高 {self.best}", True, WHITE)
t3 = font_sm.render("按 R 重新開始", True, GRAY)
screen.blit(t1, (WIDTH // 2 - t1.get_width() // 2, HEIGHT // 2 - 60))
screen.blit(t2, (WIDTH // 2 - t2.get_width() // 2, HEIGHT // 2 + 10))
screen.blit(t3, (WIDTH // 2 - t3.get_width() // 2, HEIGHT // 2 + 44))
pygame.display.flip()
def run(self):
while True:
self.handle_events()
self.update()
self.draw()
clock.tick(60)
if __name__ == "__main__":
Game().run()
以上就是Python基于Pygame開發(fā)的經(jīng)典打磚塊游戲的詳細(xì)內(nèi)容,更多關(guān)于Python Pygame打磚塊游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python?OpenCV形態(tài)學(xué)運(yùn)算示例詳解
這篇文章主要為大家介紹了OpenCV中的幾個形態(tài)學(xué)運(yùn)算,例如:腐蝕&膨脹、開&閉運(yùn)算、梯度運(yùn)算、頂帽運(yùn)算黑帽運(yùn)算,感興趣的可以了解一下2022-04-04
python使用Scrapy庫進(jìn)行數(shù)據(jù)提取和處理的方法詳解
在我們的初級教程中,我們介紹了如何使用Scrapy創(chuàng)建和運(yùn)行一個簡單的爬蟲,在這篇文章中,我們將深入了解Scrapy的強(qiáng)大功能,學(xué)習(xí)如何使用Scrapy提取和處理數(shù)據(jù)2023-09-09
使用PyCharm在Github上保存代碼并在服務(wù)器上運(yùn)行方式
這篇文章主要介紹了使用PyCharm在Github上保存代碼并在服務(wù)器上運(yùn)行方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02

