进阶打砖块:关卡编辑器与道具系统
打砖块(Breakout)是经典街机游戏。在基础版本之上,本文将扩展实现关卡编辑器、道具系统、粒子爆炸效果和连击计分系统,打造一个功能丰富、趣味十足的进阶版打砖块游戏。
一、回顾基础打砖块
基础打砖块包含:球拍(挡板)、球、砖块三个核心元素。玩家移动球拍接住反弹的球,球撞击砖块时砖块消失。当所有砖块被消除即通关,球掉落则失败。
# 基础常量定义
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60
# 球拍
PADDLE_WIDTH = 100
PADDLE_HEIGHT = 15
PADDLE_SPEED = 8
PADDLE_Y = SCREEN_HEIGHT - 40
# 球
BALL_SIZE = 10
BALL_SPEED = 6
# 砖块
BRICK_WIDTH = 60
BRICK_HEIGHT = 20
BRICK_ROWS = 6
BRICK_COLS = 12
BRICK_TOP_OFFSET = 50
# 颜色
COLOR_BG = (20, 20, 30)
COLOR_PADDLE = (100, 200, 255)
COLOR_BALL = (255, 255, 100)
COLOR_TEXT = (255, 255, 255)
# 砖块颜色(按耐久度)
BRICK_COLORS = {
1: (100, 200, 100), # 绿色 - 1击
2: (255, 200, 100), # 橙色 - 2击
3: (255, 100, 100), # 红色 - 3击
4: (200, 100, 255), # 紫色 - 4击
}
二、关卡数据格式设计
好的关卡数据格式应该易于设计、存储和加载。我们使用二维数组表示砖块布局,每个数字代表砖块的耐久度(0表示无砖块)。
# 关卡数据格式:二维数组,数字代表砖块耐久度
# 0 = 空,1-4 = 砖块耐久度,5 = 特殊砖块(掉落道具)
LEVELS = [
# 关卡1:经典布局
{
"name": "经典开局",
"layout": [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
],
"ball_speed": 6,
},
# 关卡2:金字塔
{
"name": "金字塔",
"layout": [
[0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0],
[0, 0, 0, 2, 2, 5, 5, 2, 2, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
],
"ball_speed": 7,
},
# 关卡3:棋盘
{
"name": "棋盘格",
"layout": [
[3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0],
[0, 2, 0, 2, 0, 5, 0, 5, 0, 2, 0, 2],
[3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0],
[0, 2, 0, 2, 0, 5, 0, 5, 0, 2, 0, 2],
[3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0],
],
"ball_speed": 8,
},
]
import json
class LevelManager:
"""关卡管理器"""
def __init__(self):
self.levels = LEVELS
self.current_level = 0
self.total_score = 0
def get_current_level(self):
if 0 <= self.current_level < len(self.levels):
return self.levels[self.current_level]
return None
def next_level(self):
self.current_level += 1
return self.current_level < len(self.levels)
def reset(self):
self.current_level = 0
self.total_score = 0
def save_levels(self, filename="breakout_levels.json"):
"""保存自定义关卡"""
with open(filename, 'w', encoding='utf-8') as f:
json.dump(self.levels, f, ensure_ascii=False, indent=2)
def load_levels(self, filename="breakout_levels.json"):
"""加载关卡文件"""
import os
if os.path.exists(filename):
with open(filename, 'r', encoding='utf-8') as f:
self.levels = json.load(f)
return True
return False
三、关卡编辑器实现(鼠标编辑)
关卡编辑器允许玩家用鼠标设计自己的关卡。点击或拖动来放置/移除砖块,支持选择砖块类型。
import pygame
class LevelEditor:
"""关卡编辑器"""
def __init__(self):
self.rows = 8
self.cols = 12
self.grid = [[0] * self.cols for _ in range(self.rows)]
self.current_brick = 1 # 当前选中的砖块类型
self.brush_size = 1 # 笔刷大小
pygame.init()
self.screen = pygame.display.set_mode(
(SCREEN_WIDTH, SCREEN_HEIGHT)
)
pygame.display.set_caption("打砖块 - 关卡编辑器")
self.clock = pygame.time.Clock()
self.font = pygame.font.SysFont("simhei", 16)
# 编辑器区域
self.edit_area = pygame.Rect(
20, 50,
self.cols * BRICK_WIDTH,
self.rows * BRICK_HEIGHT
)
# 砖块类型选择栏
self.brick_types = [0, 1, 2, 3, 4, 5] # 0=橡皮擦, 1-5=砖块
self.palette_rects = []
def run(self):
"""编辑器主循环"""
running = True
mouse_pressed = False
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_s:
self.save_level()
elif event.key == pygame.K_c:
self.clear_grid()
# 数字键选择砖块类型
elif pygame.K_0 <= event.key <= pygame.K_5:
self.current_brick = event.key - pygame.K_0
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # 左键
mouse_pressed = True
self.handle_mouse(event.pos)
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
mouse_pressed = False
elif event.type == pygame.MOUSEMOTION:
if mouse_pressed:
self.handle_mouse(event.pos)
self.draw()
pygame.display.flip()
self.clock.tick(60)
pygame.quit()
def handle_mouse(self, pos):
"""处理鼠标点击"""
x, y = pos
# 检查是否点击了调色板
for i, rect in enumerate(self.palette_rects):
if rect.collidepoint(pos):
self.current_brick = self.brick_types[i]
return
# 编辑网格
if self.edit_area.collidepoint(pos):
col = (x - self.edit_area.x) // BRICK_WIDTH
row = (y - self.edit_area.y) // BRICK_HEIGHT
if 0 <= row < self.rows and 0 <= col < self.cols:
# 笔刷大小处理
for dr in range(self.brush_size):
for dc in range(self.brush_size):
r, c = row + dr, col + dc
if 0 <= r < self.rows and 0 <= c < self.cols:
self.grid[r][c] = self.current_brick
def draw(self):
"""绘制编辑器界面"""
self.screen.fill(COLOR_BG)
# 绘制标题
title = self.font.render(
"关卡编辑器 [0-5选择砖块] [S保存] [C清空] [ESC退出]",
True, COLOR_TEXT
)
self.screen.blit(title, (10, 10))
# 绘制网格背景
pygame.draw.rect(self.screen, (40, 40, 50), self.edit_area)
# 绘制砖块
for row in range(self.rows):
for col in range(self.cols):
val = self.grid[row][col]
if val > 0:
x = self.edit_area.x + col * BRICK_WIDTH
y = self.edit_area.y + row * BRICK_HEIGHT
color = BRICK_COLORS.get(val, (150, 150, 150))
if val == 5:
color = (255, 255, 0) # 特殊砖块用金色
rect = pygame.Rect(x + 1, y + 1,
BRICK_WIDTH - 2, BRICK_HEIGHT - 2)
pygame.draw.rect(self.screen, color, rect)
pygame.draw.rect(self.screen, (255, 255, 255), rect, 1)
# 绘制网格线
for col in range(self.cols + 1):
x = self.edit_area.x + col * BRICK_WIDTH
pygame.draw.line(self.screen, (60, 60, 70),
(x, self.edit_area.y),
(x, self.edit_area.bottom), 1)
for row in range(self.rows + 1):
y = self.edit_area.y + row * BRICK_HEIGHT
pygame.draw.line(self.screen, (60, 60, 70),
(self.edit_area.x, y),
(self.edit_area.right, y), 1)
# 绘制调色板
self.draw_palette()
# 显示当前选择
info = self.font.render(
f"当前砖块: {self.current_brick} (0=橡皮擦, 5=道具砖)",
True, COLOR_TEXT
)
self.screen.blit(info, (10, SCREEN_HEIGHT - 30))
def draw_palette(self):
"""绘制砖块选择调色板"""
palette_y = self.edit_area.bottom + 20
self.palette_rects = []
for i, brick_type in enumerate(self.brick_types):
x = 20 + i * (BRICK_WIDTH + 10)
rect = pygame.Rect(x, palette_y, BRICK_WIDTH, BRICK_HEIGHT)
# 砖块颜色
if brick_type == 0:
color = (80, 80, 80) # 橡皮擦
elif brick_type == 5:
color = (255, 255, 0) # 道具砖
else:
color = BRICK_COLORS[brick_type]
pygame.draw.rect(self.screen, color, rect)
pygame.draw.rect(self.screen, COLOR_TEXT, rect, 2)
# 高亮当前选择
if brick_type == self.current_brick:
pygame.draw.rect(self.screen, (255, 255, 0), rect.inflate(4, 4), 2)
# 标签
label = self.font.render(str(brick_type), True, COLOR_TEXT)
self.screen.blit(label, (x + BRICK_WIDTH // 2 - 5, palette_y + 2))
self.palette_rects.append(rect)
def clear_grid(self):
"""清空网格"""
self.grid = [[0] * self.cols for _ in range(self.rows)]
def save_level(self):
"""保存关卡"""
level = {
"name": f"自定义关卡",
"layout": [row[:] for row in self.grid],
"ball_speed": 6,
}
LEVELS.append(level)
print(f"关卡已保存!当前共 {len(LEVELS)} 个关卡")
四、道具系统设计
道具系统为游戏增加策略性和趣味性。特殊砖块(类型5)被击碎时会掉落道具,玩家用球拍接住道具即可获得相应效果。
import pygame
import random
# 道具类型
POWERUP_MULTI_BALL = 'multi_ball' # 多球
POWERUP_WIDE_PADDLE = 'wide_paddle' # 加宽球拍
POWERUP_LASER = 'laser' # 激光
POWERUP_SLOW = 'slow' # 减速
POWERUP_EXTRA_LIFE = 'extra_life' # 加命
POWERUP_SHRINK = 'shrink' # 缩小球拍(负面)
# 道具配置
POWERUP_CONFIG = {
POWERUP_MULTI_BALL: {
'color': (100, 255, 100),
'symbol': 'M',
'name': '多球',
'duration': 0, # 永久(直到球丢失)
},
POWERUP_WIDE_PADDLE: {
'color': (100, 200, 255),
'symbol': 'W',
'name': '加宽',
'duration': 600, # 10秒(60FPS)
},
POWERUP_LASER: {
'color': (255, 100, 100),
'symbol': 'L',
'name': '激光',
'duration': 600,
},
POWERUP_SLOW: {
'color': (200, 200, 255),
'symbol': 'S',
'name': '减速',
'duration': 480,
},
POWERUP_EXTRA_LIFE: {
'color': (255, 200, 100),
'symbol': '+',
'name': '加命',
'duration': 0,
},
POWERUP_SHRINK: {
'color': (150, 150, 150),
'symbol': 'X',
'name': '缩小',
'duration': 480,
},
}
# 道具掉落概率权重
POWERUP_WEIGHTS = [
(POWERUP_MULTI_BALL, 15),
(POWERUP_WIDE_PADDLE, 20),
(POWERUP_LASER, 15),
(POWERUP_SLOW, 15),
(POWERUP_EXTRA_LIFE, 5),
(POWERUP_SHRINK, 10), # 负面道具
]
class PowerUp:
"""道具类"""
FALL_SPEED = 3
SIZE = 20
def __init__(self, x, y, power_type):
self.x = x
self.y = y
self.type = power_type
self.config = POWERUP_CONFIG[power_type]
self.rect = pygame.Rect(x, y, self.SIZE, self.SIZE)
self.active = True
def update(self):
"""更新道具位置"""
self.y += self.FALL_SPEED
self.rect.y = self.y
if self.y > SCREEN_HEIGHT:
self.active = False
def draw(self, screen, font):
"""绘制道具"""
color = self.config['color']
# 旋转效果(简单的闪烁)
pygame.draw.rect(screen, color, self.rect)
pygame.draw.rect(screen, (255, 255, 255), self.rect, 2)
# 符号
symbol = font.render(self.config['symbol'], True, (0, 0, 0))
screen.blit(symbol, (self.x + 6, self.y + 2))
@staticmethod
def random_powerup():
"""随机选择一个道具类型"""
types = [t for t, _ in POWERUP_WEIGHTS]
weights = [w for _, w in POWERUP_WEIGHTS]
return random.choices(types, weights=weights, k=1)[0]
class PowerUpManager:
"""道具效果管理器"""
def __init__(self, game):
self.game = game
self.active_effects = {} # {type: remaining_frames}
def activate(self, power_type):
"""激活道具效果"""
config = POWERUP_CONFIG[power_type]
if power_type == POWERUP_MULTI_BALL:
self._activate_multi_ball()
elif power_type == POWERUP_WIDE_PADDLE:
self.game.paddle.width = PADDLE_WIDTH * 1.5
self.active_effects[power_type] = config['duration']
elif power_type == POWERUP_LASER:
self.game.paddle.has_laser = True
self.active_effects[power_type] = config['duration']
elif power_type == POWERUP_SLOW:
for ball in self.game.balls:
ball.speed_multiplier = 0.6
self.active_effects[power_type] = config['duration']
elif power_type == POWERUP_EXTRA_LIFE:
self.game.lives += 1
elif power_type == POWERUP_SHRINK:
self.game.paddle.width = PADDLE_WIDTH * 0.7
self.active_effects[power_type] = config['duration']
print(f"道具激活: {config['name']}")
def _activate_multi_ball(self):
"""多球效果:每个球分裂成2个"""
new_balls = []
for ball in self.game.balls:
new_ball = Ball(ball.x, ball.y)
new_ball.dx = -ball.dx
new_ball.dy = ball.dy
new_balls.append(new_ball)
self.game.balls.extend(new_balls)
def update(self):
"""更新所有活跃效果"""
expired = []
for power_type, frames in self.active_effects.items():
frames -= 1
if frames <= 0:
expired.append(power_type)
else:
self.active_effects[power_type] = frames
for power_type in expired:
self.deactivate(power_type)
def deactivate(self, power_type):
"""取消道具效果"""
if power_type == POWERUP_WIDE_PADDLE:
self.game.paddle.width = PADDLE_WIDTH
elif power_type == POWERUP_LASER:
self.game.paddle.has_laser = False
elif power_type == POWERUP_SLOW:
for ball in self.game.balls:
ball.speed_multiplier = 1.0
elif power_type == POWERUP_SHRINK:
self.game.paddle.width = PADDLE_WIDTH
del self.active_effects[power_type]
def get_active_names(self):
"""获取当前活跃的道具名称列表"""
return [POWERUP_CONFIG[t]['name'] for t in self.active_effects]
五、核心游戏对象实现
import pygame
import math
class Paddle:
"""球拍"""
def __init__(self):
self.width = PADDLE_WIDTH
self.height = PADDLE_HEIGHT
self.x = (SCREEN_WIDTH - self.width) // 2
self.y = PADDLE_Y
self.has_laser = False
self.laser_cooldown = 0
@property
def rect(self):
return pygame.Rect(self.x, self.y, int(self.width), self.height)
def move(self, dx):
self.x += dx
self.x = max(0, min(self.x, SCREEN_WIDTH - self.width))
def draw(self, screen):
color = (255, 100, 100) if self.has_laser else COLOR_PADDLE
pygame.draw.rect(screen, color, self.rect, border_radius=5)
# 激光发射器
if self.has_laser:
for i in range(3):
lx = self.x + (i + 1) * self.width / 4
pygame.draw.circle(screen, (255, 200, 0),
(int(lx), self.y - 3), 3)
class Ball:
"""球"""
def __init__(self, x, y):
self.x = x
self.y = y
self.size = BALL_SIZE
self.speed = BALL_SPEED
self.speed_multiplier = 1.0
angle = random.uniform(-math.pi / 3, math.pi / 3)
self.dx = self.speed * math.sin(angle)
self.dy = -self.speed * math.cos(angle)
self.trail = [] # 拖尾效果
@property
def rect(self):
return pygame.Rect(self.x - self.size, self.y - self.size,
self.size * 2, self.size * 2)
def update(self):
self.trail.append((self.x, self.y))
if len(self.trail) > 8:
self.trail.pop(0)
speed = self.speed * self.speed_multiplier
self.x += self.dx * self.speed_multiplier
self.y += self.dy * self.speed_multiplier
# 墙壁碰撞
if self.x - self.size < 0:
self.x = self.size
self.dx = abs(self.dx)
if self.x + self.size > SCREEN_WIDTH:
self.x = SCREEN_WIDTH - self.size
self.dx = -abs(self.dx)
if self.y - self.size < 0:
self.y = self.size
self.dy = abs(self.dy)
def draw(self, screen):
# 拖尾
for i, (tx, ty) in enumerate(self.trail):
alpha = (i + 1) / len(self.trail)
radius = int(self.size * alpha)
color_val = int(100 + 155 * alpha)
pygame.draw.circle(screen, (color_val, color_val, 50),
(int(tx), int(ty)), radius)
# 球
pygame.draw.circle(screen, COLOR_BALL,
(int(self.x), int(self.y)), self.size)
pygame.draw.circle(screen, (255, 255, 200),
(int(self.x), int(self.y)), self.size, 1)
class Brick:
"""砖块"""
def __init__(self, x, y, durability, has_powerup=False):
self.x = x
self.y = y
self.width = BRICK_WIDTH
self.height = BRICK_HEIGHT
self.durability = durability
self.max_durability = durability
self.has_powerup = has_powerup
self.alive = True
self.hit_flash = 0 # 被击中闪烁
@property
def rect(self):
return pygame.Rect(self.x, self.y, self.width, self.height)
def hit(self):
"""被击中,返回是否被破坏"""
self.durability -= 1
self.hit_flash = 5
if self.durability <= 0:
self.alive = False
return True
return False
def draw(self, screen):
if not self.alive:
return
# 颜色根据耐久度
ratio = self.durability / self.max_durability
base_color = BRICK_COLORS.get(self.max_durability, (150, 150, 150))
# 闪烁效果
if self.hit_flash > 0:
color = tuple(min(255, c + 100) for c in base_color)
self.hit_flash -= 1
else:
color = base_color
pygame.draw.rect(screen, color, self.rect)
pygame.draw.rect(screen, (255, 255, 255), self.rect, 1)
# 道具砖块标记
if self.has_powerup:
cx = self.x + self.width // 2
cy = self.y + self.height // 2
pygame.draw.circle(screen, (255, 255, 0),
(cx, cy), 5, 2)
class Laser:
"""激光(激光道具的弹丸)"""
def __init__(self, x, y):
self.x = x
self.y = y
self.speed = 10
self.active = True
@property
def rect(self):
return pygame.Rect(self.x - 2, self.y, 4, 15)
def update(self):
self.y -= self.speed
if self.y < 0:
self.active = False
def draw(self, screen):
pygame.draw.rect(screen, (255, 100, 100),
(self.x - 2, self.y, 4, 15))
六、粒子爆炸效果
粒子效果让砖块被击碎时产生碎片飞溅,增加视觉冲击力。
import random
import math
class Particle:
"""单个粒子"""
def __init__(self, x, y, color):
self.x = x
self.y = y
angle = random.uniform(0, math.pi * 2)
speed = random.uniform(2, 6)
self.dx = math.cos(angle) * speed
self.dy = math.sin(angle) * speed
self.color = color
self.size = random.randint(2, 5)
self.life = random.randint(20, 40)
self.max_life = self.life
self.gravity = 0.15
def update(self):
self.x += self.dx
self.y += self.dy
self.dy += self.gravity # 重力
self.dx *= 0.98 # 空气阻力
self.life -= 1
def draw(self, screen):
if self.life > 0:
alpha = self.life / self.max_life
size = max(1, int(self.size * alpha))
color = tuple(int(c * alpha) for c in self.color)
pygame.draw.circle(screen, color,
(int(self.x), int(self.y)), size)
class ParticleSystem:
"""粒子系统"""
def __init__(self):
self.particles = []
def emit(self, x, y, color, count=15):
"""在指定位置发射粒子"""
for _ in range(count):
self.particles.append(Particle(x, y, color))
def emit_explosion(self, x, y, color):
"""爆炸效果(更多粒子)"""
self.emit(x, y, color, count=25)
# 额外的亮色粒子
bright_color = tuple(min(255, c + 80) for c in color)
self.emit(x, y, bright_color, count=10)
def update(self):
"""更新所有粒子"""
for p in self.particles:
p.update()
self.particles = [p for p in self.particles if p.life > 0]
def draw(self, screen):
for p in self.particles:
p.draw(screen)
def clear(self):
self.particles.clear()
七、计分与连击系统
class ScoreSystem:
"""计分与连击系统"""
# 砖块分数
BRICK_SCORES = {
1: 10,
2: 20,
3: 30,
4: 50,
5: 40, # 道具砖
}
def __init__(self):
self.score = 0
self.combo = 0
self.combo_timer = 0
self.max_combo = 0
self.combo_threshold = 120 # 2秒内连续击中算连击
def add_brick_score(self, brick_durability):
"""添加砖块分数"""
base_score = self.BRICK_SCORES.get(brick_durability, 10)
combo_multiplier = 1 + self.combo * 0.1 # 连击加成
earned = int(base_score * combo_multiplier)
self.score += earned
self.combo += 1
self.combo_timer = self.combo_threshold
self.max_combo = max(self.max_combo, self.combo)
return earned
def update(self):
"""更新连击计时器"""
if self.combo_timer > 0:
self.combo_timer -= 1
if self.combo_timer <= 0:
self.combo = 0 # 连击中断
def get_combo_text(self):
"""获取连击显示文本"""
if self.combo >= 3:
return f"{self.combo} 连击!"
return ""
def reset(self):
self.score = 0
self.combo = 0
self.combo_timer = 0
self.max_combo = 0
八、完整游戏主类
将所有组件整合到游戏主类中:
import pygame
import sys
import random
class BreakoutGame:
"""进阶打砖块游戏主类"""
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("进阶打砖块 - 关卡编辑器与道具系统")
self.clock = pygame.time.Clock()
self.font = pygame.font.SysFont("simhei", 20)
self.big_font = pygame.font.SysFont("simhei", 48)
self.level_manager = LevelManager()
self.score_system = ScoreSystem()
self.particle_system = ParticleSystem()
self.powerup_manager = None
self.state = "menu" # menu, playing, paused, level_complete, game_over
self.lives = 3
self.powerups = [] # 掉落中的道具
self.lasers = [] # 激光弹丸
self.bricks = []
self.balls = []
self.paddle = None
self.load_level()
def load_level(self):
"""加载当前关卡"""
level = self.level_manager.get_current_level()
if not level:
self.state = "game_over"
return
self.bricks = []
layout = level["layout"]
for row, row_data in enumerate(layout):
for col, val in enumerate(row_data):
if val > 0:
x = 20 + col * BRICK_WIDTH
y = BRICK_TOP_OFFSET + row * BRICK_HEIGHT
has_powerup = (val == 5)
durability = 1 if val == 5 else val
self.bricks.append(Brick(x, y, durability, has_powerup))
self.paddle = Paddle()
self.balls = [Ball(SCREEN_WIDTH // 2, SCREEN_HEIGHT - 60)]
self.powerup_manager = PowerUpManager(self)
self.particle_system.clear()
self.powerups.clear()
self.lasers.clear()
def run(self):
"""主游戏循环"""
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
if self.state == "playing":
self.state = "paused"
elif self.state == "paused":
self.state = "playing"
else:
running = False
elif event.key == pygame.K_SPACE:
if self.state == "menu":
self.state = "playing"
elif self.state == "level_complete":
if self.level_manager.next_level():
self.load_level()
self.state = "playing"
else:
self.state = "game_over"
elif self.state == "game_over":
self.restart()
elif event.key == pygame.K_r:
self.restart()
elif event.key == pygame.K_f:
# 发射激光
if self.paddle and self.paddle.has_laser:
self.fire_laser()
if self.state == "playing":
self.update()
self.draw()
pygame.display.flip()
self.clock.tick(FPS)
pygame.quit()
sys.exit()
def restart(self):
"""重新开始游戏"""
self.level_manager.reset()
self.score_system.reset()
self.lives = 3
self.load_level()
self.state = "playing"
def fire_laser(self):
"""发射激光"""
if self.paddle.laser_cooldown <= 0:
self.lasers.append(Laser(self.paddle.x + 20, self.paddle.y))
self.lasers.append(Laser(self.paddle.x + self.paddle.width - 20,
self.paddle.y))
self.paddle.laser_cooldown = 15
def update(self):
"""更新游戏状态"""
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.paddle.move(-PADDLE_SPEED)
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.paddle.move(PADDLE_SPEED)
self.paddle.laser_cooldown = max(0, self.paddle.laser_cooldown - 1)
# 更新球
for ball in self.balls[:]:
ball.update()
self.check_ball_collisions(ball)
if ball.y - ball.size > SCREEN_HEIGHT:
self.balls.remove(ball)
# 球丢失
if not self.balls:
self.lives -= 1
if self.lives <= 0:
self.state = "game_over"
else:
self.balls = [Ball(SCREEN_WIDTH // 2, SCREEN_HEIGHT - 60)]
# 更新道具
for powerup in self.powerups[:]:
powerup.update()
if powerup.rect.colliderect(self.paddle.rect):
self.powerup_manager.activate(powerup.type)
self.powerups.remove(powerup)
elif not powerup.active:
self.powerups.remove(powerup)
# 更新激光
for laser in self.lasers[:]:
laser.update()
for brick in self.bricks:
if brick.alive and laser.rect.colliderect(brick.rect):
brick.hit()
laser.active = False
break
if not laser.active:
self.lasers.remove(laser)
# 更新粒子
self.particle_system.update()
# 更新道具效果
self.powerup_manager.update()
# 更新连击
self.score_system.update()
# 检查关卡完成
if not any(b.alive for b in self.bricks):
self.state = "level_complete"
self.level_manager.total_score = self.score_system.score
def check_ball_collisions(self, ball):
"""检查球的碰撞"""
# 球拍碰撞
if ball.rect.colliderect(self.paddle.rect) and ball.dy > 0:
ball.y = self.paddle.y - ball.size
# 根据击中位置改变角度
relative_x = (ball.x - self.paddle.x) / self.paddle.width
angle = (relative_x - 0.5) * math.pi * 0.7
speed = math.hypot(ball.dx, ball.dy)
ball.dx = speed * math.sin(angle)
ball.dy = -abs(speed * math.cos(angle))
# 砖块碰撞
for brick in self.bricks[:]:
if not brick.alive:
continue
if ball.rect.colliderect(brick.rect):
# 判断碰撞方向
dx = ball.x - (brick.x + brick.width / 2)
dy = ball.y - (brick.y + brick.height / 2)
if abs(dx / brick.width) > abs(dy / brick.height):
ball.dx = -ball.dx
else:
ball.dy = -ball.dy
destroyed = brick.hit()
if destroyed:
self.score_system.add_brick_score(brick.max_durability)
# 粒子效果
self.particle_system.emit_explosion(
brick.x + brick.width // 2,
brick.y + brick.height // 2,
BRICK_COLORS.get(brick.max_durability, (150, 150, 150))
)
# 道具掉落
if brick.has_powerup and random.random() < 0.8:
power_type = PowerUp.random_powerup()
self.powerups.append(PowerUp(
brick.x + brick.width // 2 - 10,
brick.y,
power_type
))
break
def draw(self):
"""绘制游戏画面"""
self.screen.fill(COLOR_BG)
if self.state == "menu":
self.draw_menu()
elif self.state in ("playing", "paused", "level_complete", "game_over"):
self.draw_game()
if self.state == "paused":
self.draw_overlay("暂停", "按 ESC 继续")
elif self.state == "level_complete":
self.draw_overlay("关卡完成!", "按 空格 进入下一关")
elif self.state == "game_over":
self.draw_overlay("游戏结束", f"总分: {self.score_system.score}")
def draw_game(self):
"""绘制游戏画面"""
# 砖块
for brick in self.bricks:
brick.draw(self.screen)
# 道具
for powerup in self.powerups:
powerup.draw(self.screen, self.font)
# 激光
for laser in self.lasers:
laser.draw(self.screen)
# 粒子
self.particle_system.draw(self.screen)
# 球拍
self.paddle.draw(self.screen)
# 球
for ball in self.balls:
ball.draw(self.screen)
# HUD
self.draw_hud()
def draw_hud(self):
"""绘制信息栏"""
info = (f"分数: {self.score_system.score} "
f"生命: {self.lives} "
f"关卡: {self.level_manager.current_level + 1}")
text = self.font.render(info, True, COLOR_TEXT)
self.screen.blit(text, (10, 10))
# 连击显示
combo_text = self.score_system.get_combo_text()
if combo_text:
combo = self.big_font.render(combo_text, True, (255, 200, 0))
self.screen.blit(combo, (SCREEN_WIDTH // 2 - 80, 15))
# 活跃道具
active = self.powerup_manager.get_active_names()
if active:
power_text = self.font.render(
f"道具: {', '.join(active)}", True, (150, 255, 150)
)
self.screen.blit(power_text, (SCREEN_WIDTH - 200, 10))
def draw_menu(self):
"""绘制主菜单"""
title = self.big_font.render("进阶打砖块", True, COLOR_TEXT)
self.screen.blit(title, (SCREEN_WIDTH // 2 - 120, 200))
subtitle = self.font.render(
"按 空格 开始游戏 | ESC 退出", True, COLOR_TEXT
)
self.screen.blit(subtitle, (SCREEN_WIDTH // 2 - 120, 280))
controls = [
"A/D 或 ←/键 移动球拍",
"F 发射激光(需激光道具)",
"ESC 暂停/继续",
"R 重新开始",
]
for i, line in enumerate(controls):
text = self.font.render(line, True, (180, 180, 180))
self.screen.blit(text, (SCREEN_WIDTH // 2 - 100, 340 + i * 30))
def draw_overlay(self, title, subtitle):
"""绘制覆盖层"""
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 150))
self.screen.blit(overlay, (0, 0))
title_surf = self.big_font.render(title, True, COLOR_TEXT)
self.screen.blit(title_surf,
(SCREEN_WIDTH // 2 - title_surf.get_width() // 2, 250))
sub_surf = self.font.render(subtitle, True, COLOR_TEXT)
self.screen.blit(sub_surf,
(SCREEN_WIDTH // 2 - sub_surf.get_width() // 2, 320))
if __name__ == "__main__":
game = BreakoutGame()
game.run()
九、扩展建议
- 更多道具:磁力球拍(吸住球)、穿透球(穿透砖块)、爆炸球(范围伤害)
- Boss关卡:每5关出现一个Boss,有血条和特殊攻击
- 商店系统:用分数购买永久升级
- 成就系统:完成特定挑战解锁成就
- 关卡分享:导出/导入自定义关卡代码
- 音效系统:添加背景音乐和音效
- 排行榜:本地或在线最高分排行
- 移动端支持:触摸控制适配
- 视觉增强:屏幕震动、闪光、慢动作效果
- 故事模式:添加剧情和角色
通过本教程,你学会了在基础打砖块上扩展实现关卡编辑器、道具系统、粒子效果和连击计分。这些技术不仅适用于打砖块,也可以应用到其他2D游戏中。关卡编辑器让游戏内容可扩展,道具系统增加策略深度,粒子效果提升视觉体验,连击系统激励玩家追求高分。继续扩展和完善,你可以打造出一款真正有趣的游戏作品。