Hello,各位同学,攀哥今天给大家分享一个用Python制作童年经典游戏的教程,详细教程已经码出来了,快收藏起来!答应我,别让它吃灰,可以吗?要做彼此的天使!
走过路过不要错过,点个关注再撤退,点不了吃亏,点不了上当,O(∩_∩)O哈哈~
游戏源码素材,请关注+评论区留言获取哈
一、游戏简介
1.1 游戏课程描述
打砖块游戏,也称为弹珠台游戏,是一种经典的街机和电子游戏,玩家通常通过控制一个小球发射器来击打从屏幕上方落下的各种形状的砖块。目标是消除所有的砖块,同时尽可能多地收集积分和特殊奖励。游戏的操作通常包括左右移动发射器、调整角度以及按压发射键。随着关卡的提升,难度会增加,比如速度加快、砖块变得更难以击中,甚至会出现移动的敌人和特殊的障碍物。玩家需要快速反应和策略规划才能在游戏中取得高分。这类游戏以其简单易学和高度娱乐性深受玩家喜爱。
该项目使用了 Python 语言和 Pygame 库进行实现,旨在帮助初学者理解游戏开发的基本概念和实践。目前该游戏除了常规打砖块功能以外,还设计了自定义关卡砖块的摆放。简单易学,界面美观。
1.2 游戏效果截图
1.2.1 欢迎页面
本项目的欢迎界面设计的并不复杂,通过简单的绘制图片以及文字实现。这里需要掌握的是如何绘制图片和文字。

1.2.2 游戏页面
不同的关卡设置的砖块的显示方式是不一样的,可以通过游戏的地图文件修改砖块的摆放



1.2.3 暂停和结束页面
游戏设计了暂停页面,游戏开始后按 p 键即可暂停游戏,然后按 R 键即可恢复游戏


二、游戏实现
2.1 创建项目
打开 pycharm 按照如图所示,创建项目并导入图片资源:

- font 中存放项目中所需要的字体文件
- image 中存放项目中所需的所有的图片资源
- pojo 中存放项目中涉及到的实体文件
- venv 是自动生成的目录可以不用理会
- main.py 是游戏的启动主程序文件
2.2 游戏窗体搭建
2.2.1 新建游戏常量文件
在 pojo 中新建一个 constant.py ,该文件用来定义一些游戏中的常量数据,方便后期通过调整该文件参数,来实现游戏难易程度以及显示状态的转变。
"""
墨轩(攀哥出品)
Author:Administrator
学习使人强大,懒惰易坠深渊
"""
# 封装游戏中的常量数据
# 游戏屏幕的宽度
SCREEN_WIDTH = 512
# 游戏屏幕的高度
SCREEN_HEIGHT = 768
# 游戏帧速率
FPS = 60
2.2.2 初始化窗体
在 main.py 中,初始化 pygame,并按照常量文件中定义的屏幕的宽度和高度,创建游戏窗体
"""
Author: 墨轩(攀哥出品)
游戏的主程序
"""
import pygame
import sys
from pojo.constant import *
# 初始化pygame模块
pygame.init()
# 根据定义的屏幕尺寸创建游戏屏幕
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
# 设置游戏标题
pygame.display.set_caption("欢乐打砖块~墨轩(攀哥出品,盗版必究)")
if __name__ == '__main__':
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
2.2.3 显示背景图片
在 main.py 中加载背景图片,由于背景图片像素是 755*1334,这个要比游戏窗体要大,因此我们需要将背景图先缩放到游戏窗体的同等大小,然后再画背景图。

绘制背景图

【参考代码】
"""
Author: 墨轩(攀哥出品)
游戏的主程序
"""
import pygame
import sys
from pojo.constant import *
# 初始化pygame模块
pygame.init()
# 根据定义的屏幕尺寸创建游戏屏幕
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
# 设置游戏标题
pygame.display.set_caption("欢乐打砖块~墨轩(攀哥出品,盗版必究)")
# 获取时钟对象,方便设置游戏的帧速率
clock = pygame.time.Clock()
# 加载背景图
background = pygame.image.load("image/BG.png")
# 设置背景图片的大小(缩放到游戏窗体大小)
background = pygame.transform.scale(background, (SCREEN_WIDTH, SCREEN_HEIGHT))
if __name__ == '__main__':
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 绘制背景图
screen.blit(background, (0, 0))
# 设置游戏FPS(界面刷新频率)
clock.tick(FPS)
# 更新显示效果
pygame.display.update()
运行效果:

2.3 绘制分数血量
接下来我们来绘制分数和血量,分数初始的时候为 0,而血量为了方便后续调整初始血量,我们首先在常量文件 constant.py 中定义游戏初始血量

然后在 main.py 中定义分数 score 变量和血量 life_counter 变量,代码如下:

然后封装一下绘制分数以及血量的函数,代码如下:
def show_scores():
"""
绘制分数和血量面板
:return:
"""
# 绘制分数
font = pygame.font.SysFont("宋体", 40)
text = font.render(f"score:{score}", True, (255, 255, 0))
screen.blit(text, (10, 10))
# 绘制暂停提示
font = pygame.font.SysFont("", 30)
text = font.render(f"[P] Pause", True, (255, 255, 0))
screen.blit(text, (350, 15))
# 根据血量统计绘制相应数量的爱心图片
for i in range(life_counter):
# 循环,让爱心横向排列
screen.blit(hart_img, (i * 40 + 10, SCREEN_HEIGHT-60))
最后在主程序的 while 循环中调用一下该函数,注意要在绘制背景图的下面调用函数,否则会被背景图覆盖

运行效果:

初始的血量是 3,所以画了 3 个爱心,可以修改 constant.py 中的初始血量,那么显示的效果就会不一样,如下图所示:

2.4 欢迎界面处理
2.4.1 绘制欢迎界面
在 main.py 中封装绘制欢迎界面文字内容的函数,代码如下:
def show_welcome_text():
"""
显示欢迎提示文字
:return:
"""
font = pygame.font.Font("font/胡晓波骚包体.otf", 80)
text = font.render("童年回忆", True, (255, 255, 255))
screen.blit(text, (85, 100))
text = font.render("快乐砖块", True, (255, 255, 0))
screen.blit(text, (85, 200))
font = pygame.font.Font("font/胡晓波骚包体.otf", 50)
text = font.render("点击屏幕开始游戏", True, (255, 0, 0))
screen.blit(text, (50, 300))
font = pygame.font.Font("font/胡晓波骚包体.otf", 50)
text = font.render("墨轩学习网", True, (255, 255, 0))
screen.blit(text, (120, 380))
然后在主程序 while 循环中调用该函数,如下图所示:

运行效果:

2.4.2 界面切换
当显示欢迎界面之后,点击屏幕就要开始游戏了,换句话说,就是只有当游戏没有开始的时候,欢迎界面上的文字才做显示,一旦游戏开始了,就不做显示。
首先定义一个开关,is_game_started,初始的时候设置为 False,表示游戏没有开始

然后修改一下show_welcome_text()函数的调用方式,设置为只有当游戏没启动的时候,才调用该函数显示欢迎界面

最后,需要监听鼠标的点击事件,如果游戏没有开始,此时点击屏幕,就开始游戏

2.5 显示方块
游戏开启之后,接下来就需要在页面上显示界面了。
2.5.1 地图设置
为了增加游戏的可玩性,可以设置一些关卡,每个关卡方块的排列顺序不一样。我们在项目的 map 目录中新建一个 game_map.py,内容如下:
case_1 = [
[1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0],
[3, 3, 3, 3, 3, 3, 3, 3],
[0, 0, 0, 0, 0, 0, 0, 0],
[5, 5, 5, 5, 5, 5, 5, 5],
[0, 0, 0, 0, 0, 0, 0, 0],
[7, 7, 7, 7, 7, 7, 7, 7]
]
case_2 = [
[1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 2, 2, 2],
[0, 0, 0, 0, 3, 3, 3, 0],
[0, 0, 0, 4, 4, 4, 0, 0],
[0, 0, 5, 5, 5, 0, 0, 0],
[0, 6, 6, 6, 0, 0, 0, 0],
[7, 7, 7, 7, 7, 7, 7, 7]
]
case_3 = [
[1, 1, 1, 1, 1, 1, 1, 1],
[2, 1, 2, 2, 2, 2, 2, 2],
[3, 3, 1, 3, 3, 3, 3, 3],
[4, 4, 4, 1, 4, 4, 4, 4],
[5, 5, 5, 5, 1, 5, 5, 5],
[6, 6, 6, 6, 6, 1, 6, 6],
[1, 1, 1, 1, 1, 1, 1, 7]
]
代码说明:
- case_1,case_2,case_3 对应的是 3 个关卡
- 以 case_3 为例,每一行列表,代表着一行方块,有多少行列表,就代表着有多少行方块。而每个列表有多少个元素,就代表着这一行有多少个方块。
- 列表中的数字代表着方块图片的后缀编号,例如:数字 1 表示此处显示 r1.png。其中 0 表示该位置不画方块。

2.5.2 方块实体类
接下来,为了方便后续的操作,我们来封装一下方块的实体类,代码如下:
"""
Author:墨轩(攀哥)出品
学习使人强大,懒惰易坠深渊
方块类
"""
import pygame
class Rect:
def __init__(self,index,x,y,row,col):
"""
初始化方块的函数
:param index: 显示第几张方块图片
:param x: 第一行第一列方块的横坐标
:param y: 第一行第一列方块的纵坐标
:param row: 方块显示在第几行
:param col: 方块显示在第几列
"""
super().__init__()
# 加载图片
image = pygame.image.load("image/r%s.png" % index)
# 调整图片大小
self.image = pygame.transform.scale(image, (54, 28))
# 获取图片的宽度和高度
self.w = self.image.get_rect().width
self.h = self.image.get_rect().height
# 根据第一个方块的坐标,结合行和列计算出当前方块的横纵坐标
self.x = x + col * (self.w + 2)
self.y = y + row * (self.h + 10)
# 将方块的坐标设置到方块的中心点
self.rect = self.image.get_rect(center=(self.x, self.y))
2.5.3 绘制方块
首先在 main.py 中列举关卡列表,并设置默认关卡,代码如下所示

定义存放所有方块的列表,并封装根据关卡列表元素初始化方块的函数
# 当前方块列表
rects = []
def init_rect():
"""
展示关卡方块
:return:
"""
r = 0 # 当前方块的行号
c = 0 # 当前方块的列号
for row in case: # 遍历出每一个二级列表
for col in row: # col 表示二级列表中的每一个元素,0~7
index = col # 记录当前元素
if index == 0: # 如果当前元素是0,则跳过
c = c + 1 # 列号向后,将元素为0的位置空出来
continue # 结束当前循环,不创建方块
# 创建方块
re = Rect(index, 35, 100, r, c)
# 将创建出来的方块加入到方块列表中
rects.append(re)
# 列号累加
c = c + 1
# 每一行遍历结束后,列号重置为0,重新向后逐个找位置
c = 0
# 行号累加
r = r + 1
开始游戏的时候,调用该函数,初始化所有的方块

在 while 循环中,遍历所有的方块,并绘制这些方块

【运行效果】



2.6 玩家功能
2.6.1 玩家实体类
"""
Author:墨轩(攀哥)出品
学习使人强大,懒惰易坠深渊
"""
import pygame
from pojo.constant import *
class Player:
def __init__(self):
# 加载图片
image = pygame.image.load("image/my.png")
# 图片缩放
self.image = pygame.transform.scale(image, (image.get_width(), image.get_height() / 3 * 2))
# 获取图片的宽度和高度
self.width = self.image.get_width()
self.height = self.image.get_height()
# 设置玩家初始位置
self.x = SCREEN_WIDTH / 2
self.y = SCREEN_HEIGHT - 50
# 设置中心点
self.rect = self.image.get_rect(center=(self.x, self.y))
def move_left(self):
"""
向左移动
:return:
"""
self.rect.move_ip(-PLAYER_SPEED, 0)
if self.rect.x < 0:
self.rect.x = 0
def move_right(self):
"""
向右移动
:return:
"""
self.rect.move_ip(PLAYER_SPEED, 0)
if self.rect.x + self.rect.width >= SCREEN_WIDTH:
self.rect.x = SCREEN_WIDTH - self.rect.width
2.6.2 绘制玩家
在 main.py 中创建玩家,并在主程序的 while 循环中绘制玩家


2.6.3 玩家移动
当游戏开始了,也就是 is_game_started 之后,可以通过键盘控制玩家左右滑动,具体代码如下:
在绘制玩家之后,添加键盘监听事件
# 获取键盘按键
key_pressed = pygame.key.get_pressed()
# 如果按的是键盘左键
if key_pressed[pygame.K_LEFT]:
# 如果游戏启动了
if is_game_started:
# 玩家向左移动
player.move_left()
# 如果按的是右键
if key_pressed[pygame.K_RIGHT]:
# 如果游戏启动了
if is_game_started:
# 玩家向右移动
player.move_right()
2.7 足球功能
2.7.1 封装足球实体类
在 constant.py 中添加有关小球的游戏常量
# 小球移动的方向
LEFT_UP = 1 # 左上
LEFT_DOWN = 2 # 左下
RIGHT_UP = 3 # 右上
RIGHT_DOWN = 4 # 右下
# 小球移动的速度
ball_speed_x = 6
ball_speed_y = 6
新建 football.py 封装实体类 Football,初始化足球
"""
Author:墨轩(攀哥)出品
学习使人强大,懒惰易坠深渊
"""
import pygame
from pojo.constant import *
class Football:
def __init__(self, player):
super().__init__()
# 加载图片
self.image = pygame.image.load("image/足球.png")
self.width = self.image.get_width()
self.height = self.image.get_height()
# 根据玩家的坐标设置小球显示坐标
self.x = player.x
self.y = player.y - player.height / 2 - self.height / 2
self.rect = self.image.get_rect(center=(self.x, self.y))
# 设置小球旋转角度
self.div = 0
# 设置小球默认的移动方向
self.dir = LEFT_UP
# 设置小球初始速度
self.speed_x = ball_speed_x
self.speed_y = ball_speed_y
2.7.2 创建小球并绘制


2.7.3 小球旋转特效
在 football.py 中添加旋转函数
def rotate(self):
self.image = pygame.image.load("image/足球.png")
self.width = self.image.get_width()
self.height = self.image.get_height()
# 给定旋转角度旋转
self.image = pygame.transform.rotate(self.image, self.div)
# 每次旋转5度
self.div = self.div - 5
# 旋转一圈,恢复到0
if self.div == -360:
self.div = 0
在 main.py 的 while 循环中,绘制好小球后就调用该函数
football.rotate()
2.7.4 小球移动
当游戏开始后,小球默认向左上角移动,碰到边缘就进行反弹
def move(self, player):
rx = self.rect.x
ry = self.rect.y
rw = self.rect.width
rh = self.rect.height
px = player.rect.x
py = player.rect.y
pw = player.rect.width
ph = player.rect.height
# 默认左上角移动
if self.dir == LEFT_UP:
self.rect.move_ip(-self.speed_x, -self.speed_y)
# 如果先碰到左边缘
if rx < 0:
# 转变方向
self.dir = RIGHT_UP
if ry < 0:
self.dir = LEFT_DOWN
elif self.dir == LEFT_DOWN:
self.rect.move_ip(-self.speed_x, self.speed_y)
if rx < 0:
self.dir = RIGHT_DOWN
if ry >= py - ph and px < rx < px + pw - rw / 2:
self.dir = LEFT_UP
elif self.dir == RIGHT_UP:
self.rect.move_ip(self.speed_x, -self.speed_y)
if rx > SCREEN_WIDTH - self.width:
self.dir = LEFT_UP
if ry < 0:
self.dir = RIGHT_DOWN
elif self.dir == RIGHT_DOWN:
self.rect.move_ip(self.speed_x, self.speed_y)
if rx > SCREEN_WIDTH - self.width:
self.dir = LEFT_DOWN
if ry >= py - ph and px < rx < px + pw - rw / 2:
self.dir = RIGHT_UP
2.7.5 碰撞方块
先在 football 中封装检测是否碰撞到方块的函数
def hit(self, re):
# 如果碰撞到了,且小球在方块的下方
if self.rect.colliderect(re) and self.rect.y > re.rect.y:
if self.dir == RIGHT_UP:
self.dir = LEFT_DOWN
if self.dir == LEFT_UP:
self.dir = RIGHT_DOWN
return True
# 如果碰撞到了,且小球在方块的上方
if self.rect.colliderect(re) and self.rect.y < re.rect.y:
if self.dir == LEFT_DOWN:
self.dir = RIGHT_UP
if self.dir == RIGHT_DOWN:
self.dir = LEFT_UP
return True
# 如果碰撞到了,且小球在方块的左侧
if self.rect.colliderect(re) and self.rect.x+self.rect.width > re.rect.x:
if self.dir == RIGHT_UP:
self.dir = LEFT_UP
if self.dir == RIGHT_DOWN:
self.dir = LEFT_DOWN
return True
# 如果碰撞到了,且小球在方块的右侧
if self.rect.colliderect(re) and self.rect.x < re.rect.x+re.rect.width:
if self.dir == LEFT_UP:
self.dir = RIGHT_UP
if self.dir == LEFT_DOWN:
self.dir = RIGHT_DOWN
return True
return False
在 main.py 中新建如下变量:
# 游戏是否结束
is_game_over = False
# 暂停游戏
is_pause = False
# 消除图片
xc_img = pygame.image.load("image/消除特效.png")
# 暂停画面
pause_img = pygame.image.load("image/pause.png")
然后在 main.py 中封装开始游戏的函数
def play_game():
global is_game_over
global life_counter
global score
if not is_game_over and not is_pause:
football.move(player)
# 如果足球脱离木板
if football.rect.y - football.height / 2 > player.rect.y + player.height / 2:
# 血量减少
life_counter -= 1
if life_counter <= 0:
is_game_over = True
else:
# 让足球重新回到木板上
football.rect.y = player.rect.y - player.height / 2 - football.height
football.rect.x = player.rect.x+player.width/2 - football.width
football.dir = LEFT_UP
for re in rects:
if football.hit(re):
# 随机一个水平速度,增加游戏难度
football.speed_x = random.randint(1, 3)
football.speed_y = random.randint(3,6)
# 暂存方块的横纵坐标,方便在对应位置上显示消除图片
x = re.rect.x
y = re.rect.y
# 删除被碰撞到的方块
rects.remove(re)
# 显示爆炸图片
screen.blit(xc_img, (x, y))
score += 10
注意将先前主程序中 while 循环里面小球移动的代码注释掉

2.8 其他功能
2.8.1 游戏的结束
上面我们已经可以实现小球脱离木板掉血,且血量小于 0 的时候将游戏结束的开关打开,现在要做的事情就是游戏结束之后进行提示,我们封装一个游戏结束的提示函数:
def show_gameover_text():
"""
显示游戏结束面板
:return:
"""
font = pygame.font.Font("font/胡晓波骚包体.otf", 100)
text = font.render("Game Over", True, (255, 0, 0))
screen.blit(text, (85, 200))
font = pygame.font.Font("font/胡晓波骚包体.otf", 50)
text = font.render("敲击空格重新游戏", True, (255, 0, 0))
screen.blit(text, (65, 300))
然后在主程序的 while 循环中,如果游戏结束就调用该函数
if is_game_over:
show_gameover_text()
最后,就是一些开关控制了,比如当游戏结束后,清除界面上的方块、不让小球移动、不让玩家移动等等。
- 清除界面上的方块,换句话就是如果游戏结束了,就不会只方块

- 不让小球移动,以及旋转

- 不让玩家移动

2.8.2 游戏的重新开始
当游戏结束后我们通过空格键控制游戏重新开始,因此我们需要在主程序的 while 循环中监听一下空格键的相关事件,然后敲击空格后,如果当前游戏处于结束状态,就将游戏数据恢复初始化,代码如下:
# 如果按的是空格键
if key_pressed[pygame.K_SPACE]:
if is_game_over:
is_game_over = False
is_game_started = False
life_counter = life
score = 0
rects = []
player = Player()
football = Football(player)
init_rect()
2.8.3 游戏的暂停
游戏通过 p 键控制暂停,r 键控制继续游戏,因此我们监听这两个按键
if key_pressed[pygame.K_p]:
if is_game_started and not is_game_over:
is_pause = True
if key_pressed[pygame.K_r]:
is_pause = False
游戏处于暂停阶段,就不让小球移动了,这个前面我们已经实现了

当游戏暂停时,我们显示暂停页面,首先封装一下显示暂停页面的函数
def show_pause():
"""游戏暂停"""
screen.blit(pause_img, (10, 200))
font = pygame.font.Font("font/胡晓波骚包体.otf", 60)
text = font.render("【R】键回到游戏", True, (255, 255, 255))
screen.blit(text, (20, 350))
在主程序的 while 循环内编写,当游戏处于暂停时,就调用上面的函数显示暂停页面
if is_pause:
show_pause()

被折叠的 条评论
为什么被折叠?



