前言
- 小时候最喜欢玩的小游戏就是超级玛丽了,有刺激有又技巧,通关真的很难,救下小公主还被抓走了,唉,心累,最后还是硬着头皮继续闯,终于要通关了,之后再玩还是没有那么容易,哈哈,不知道现在能不能通关,今天就来实现一下,制作一个简易版的超级玛丽游戏
如果你正在学习Python并且缺少项目练习可以试试我这100个Python实战源码练习点击 领取呀!
1.需求分析
具备功能
- 播放与停止背景音乐
- 随机生成管道与导弹障碍
- 显示积分
- 跳跃躲避障碍
- 碰撞障碍
2.游戏功能结构
玛丽冒险的功能结构主要分为三类,分别为音效、主窗体以及随机出现的障碍物。如下图:
3.游戏业务流程
根据该游戏的需求分析以及功能结构
4.游戏预览
5.开发必备
游戏开发运行环境
- python3.7 以上
- 开发工具 PyCharm
- Python 内置模块:itertools、random
- 第三方模块:pygame
文件夹组织结构
玛丽冒险游戏的文件夹组织结构主要分为: audio(保存音效文件)和 image (保存图片)
6.玛丽冒险游戏实现
6.1游戏窗体的实现
在实现游戏窗体时,首先需要定义窗体的宽度与高度,然后通过 ygame 模块中的 init0 方法,实现初始化功能,接下来需要创建循环,在循环中通过 pdate0 函数不断更新窗体,最后需要判断用户是否单击了关闭窗体的按钮,如果单击了“关闭”按钮,将关闭窗体,否则继续循环显示窗体
通过 pygame 模块实现玛丽主窗体具体步骤如下
1.创建文件夹,一个保存音频,一个图片,创建 marie.py 文件
2.导入 pygame 库与 pygame 中的常用库,然后定义窗体宽度与高度
import pygame
from pygame.locals import *
import sys
# 设置游戏窗口的宽度和高度
SCREENWIDTH = 822
SCREENHEIGHT = 199
# 设置游戏帧率
FPS = 60
创建 mainGame0方法,在该方法中首先进行 pygame 的初始化工作,然后创建时间对象用于更新窗体中的画面,再创建窗体实例并设置窗体的标题文字,最后通过循环实现窗体的显示与刷新。
def mainGame():
score = 0
over = False
global SCREEN, FPSCLOCK
# 初始化pygame库
pygame.init()
# 初始化时钟对象
FPSCLOCK = pygame.time.Clock()
# 创建窗口对象
SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
# 设置窗口标题
pygame.display.set_caption("玛丽冒险")
while True:
# 处理游戏事件
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# 更新窗口内容
pygame.display.update()
# 控制帧率
FPSCLOCK.tick(FPS)
if __name__ == "__main__":
mainGame()
6.2地图加载
创建一个名称为 MyMap 的滚动地图类,然后在该类的初始化方法中加载背景图片和定义 X 与 Y 的坐标
class MyMap():
def __init__(self, x, y):
self.bg = pygame.image.load("image/bg.png").convert_alpha()
self.x = x
self.y = y
在 MyMap 类中创建 map _rolling0 方法,在该方法中根据地图背景图片的 X 坐标判断是否移出窗体,如果移出就给图片设置一个新的坐标点,否则按照每次 5 个像素的跨度向左移动
def map_rolling(self):
if self.x < -790:
self.x = 800
else:
self.x -= 5
在 MyMap 类中创建 map update0 方法,在该方法中实现地图无限滚动的效果
def map_update(self):
SCREEN.blit(self.bg,(self.x,self.y))
在 mainGame0 方法中,设置标题文字代码的下面创建两个背景图片的对象
bg1 = MyMap(0,0)
bg2 = MyMap(800,0)
在 mainGame0 方法的循环中,实现无限循环滚动的地图
if over == False:
bg1.map_update()
bg1.map_rolling()
bg2.map_update()
bg2.map_rolling()
6.3玛丽的跳跃功能
在实现玛丽的跳跃功能时,首先需要指定玛丽的固定坐标,也就是默认显示在地图上的固定位置,然后判断是否按下了键盘中的(空格)键,如果按下了就开启玛丽的跳跃开关,让玛丽以 5 个像素的距离向上移动。当玛丽到达窗体顶部的边缘时,再让玛丽以 5 个像素的距离向下移动,回到地面后关闭跳跃的开关。玛丽跳跃功能的业务流程如图
导入选代工具,创建一个名称为 Marie 的玛丽类,然后在该类的初始化方法中,首先定义玛丽跳跃时所需要的变量,然后加载玛丽跑动的三张图片,最后加载玛丽跳跃时的音效并设置玛丽默认显示的坐标位置
from itertools import cycle
# 玛丽类
class Marie():
def __init__(self):
# 初始化角色矩形对象
self.rect = pygame.Rect(0, 0, 0, 0)
# 角色跳跃状态,默认为False
self.jumpState = False
# 角色跳跃高度
self.jumpHeight = 130
# 角色最低y坐标
self.lowest_y = 140
# 角色已经跳跃的高度
self.jumpValue = 0
# 角色动作图片索引
self.marieIndex = 0
# 产生循环的角色动作图片索引值生成器
self.marieIndexGen = cycle([0, 1, 2])
# 角色动作图片元组
self.adventure_img = (
pygame.image.load("image/adventure1.png").convert_alpha(),
pygame.image.load("image/adventure2.png").convert_alpha(),
pygame.image.load("image/adventure3.png").convert_alpha(),
)
# 角色跳跃音频
self.jump_audio = pygame.mixer.Sound("audio/jump.wav")
# 设置角色矩形对象的大小为第一张动作图片的大小
self.rect.size = self.adventure_img[0].get_size()
# 角色初始位置
self.x = 50
self.y = self.lowest_y
self.rect.topleft = (self.x, self.y)
在 Marie 类中创建 move 方法,在该方法中判断如果玛丽的跳跃开关开启时,再判断玛丽是否在地面上,如果满足这两个条件玛丽就以 5 个像素的距离向上移动。当玛丽到达窗体顶部时以 5 个像素的距离向下移动,当玛丽回到地面后关闭跳跃开关
# 玛丽移动
def move(self):
if self.jumpState:
# 如果角色的y坐标大于等于最低y坐标,设置跳跃值为-5
if self.rect.y >= self.lowest_y:
self.jumpValue = -5
# 如果角色的y坐标小于等于最低y坐标减去跳跃高度,设置跳跃值为5
if self.rect.y <= self.lowest_y - self.jumpHeight:
self.jumpValue = 5
# 修改角色的y坐标
self.rect.y += self.jumpValue
# 如果角色的y坐标大于等于最低y坐标,设置跳跃状态为False
if self.rect.y >= self.lowest_y:
self.jumpState = False
在 Marie 类中创建 draw marie0 方法,在该方法中首先匹配玛丽跑步的动图,然后进行玛丽的绘
#绘制玛丽
def draw_marie(self):
marieIndex = next(self.marieIndexGen)
SCREEN.blit(self.adventure_img[marieIndex],(self.x,self.rect.y))
在 mainGame()方法中,创建地图对象的代码下面创建玛丽对象
marie = Marie()
在 mainGame0 方法的 while 循环中,判断关闭窗体的下面判断是否按下了键盘中的 space>(空格)键,如果按下了就开启玛丽跳跃开关并播放跳跃音效
if event.type == KEYDOWN and event.key == K_SPACE:
# 如果玛丽的y坐标大于等于最低y坐标,播放跳跃音频,调用跳跃方法
if marie.rect.y >= marie.lowest_y:
marie.jump_audio.play()
marie.jump()
在 mainGame0 方法中,绘制地图的代码下面实现玛丽的移动与绘制功能
marie.move()
marie.draw_marie()
6.4随机出现障碍
在实现障碍物的出现时,首先需要考虑到障碍物的大小以及障碍物不能相同,如果每次出现的障碍物都是相同的那么该游戏将失去了游戏的乐趣。所以需要加载两个大小不同的障碍物图片,然后随机抽选并显示,还需要通过计算来设置出现一个障碍并将障碍物显示在窗体当中的时间间隔
导入随机数,创建一个名称为 Obstacle 的障碍物类,在该类中定义一个分数,然后在初始化方法中加载障碍物图片、分数图片以及加分音效。创建 0 至 1 的随机数字,根据该数字抽选障碍物是管道还是飞行的导弹,最后根据图片的宽、高创建障碍物矩形的大小并设置障碍物的绘制坐标
class Obstacle():
score = 1 # 初始化分数变量为1
move = 5 # 设置移动速度为5
obstacle_y = 150 # 设置障碍物的y坐标为150
def __init__(self):
self.rect = pygame.Rect(0,0,0,0) # 创建用于碰撞检测的矩形对象
self.missile = pygame.image.load("image/missile.png").convert_alpha() # 加载导弹图片
self.pipe = pygame.image.load("image/pipe.png").convert_alpha() # 加载管道图片
self.numbers = ( # 加载数字图片
pygame.image.load("image/0.png").convert_alpha(),
pygame.image.load("image/1.png").convert_alpha(),
pygame.image.load("image/2.png").convert_alpha(),
pygame.image.load("image/3.png").convert_alpha(),
pygame.image.load("image/4.png").convert_alpha(),
pygame.image.load("image/5.png").convert_alpha(),
pygame.image.load("image/6.png").convert_alpha(),
pygame.image.load("image/7.png").convert_alpha(),
pygame.image.load("image/8.png").convert_alpha(),
pygame.image.load("image/9.png").convert_alpha(),
)
self.score_audio = pygame.mixer.Sound("audio/score.wav") # 加载得分音效
r = random.randint(0,1) # 生成随机数0或1
if r == 0:
# 如果随机数为0,设置障碍物为导弹,移动速度为15,y坐标为100
self.image = self.missile
self.move = 15
self.obstacle_y = 100
else:
# 如果随机数为1,设置障碍物为管道
self.image = self.pipe
# 设置障碍物的大小和位置
self.rect.size = self.image.get_size()
self.width, self.height = self.rect.size
self.x = 800 # 设置障碍物的初始位置为屏幕右侧
self.y = self.obstacle_y
self.rect.center = (self.x ,self.y)
在 Obstacle 类中首先创建 obstacle move0 方法,用于实现障碍物的移动,然后创建 drawobstacle()方法用于实现绘制障碍物
#障碍物移动
def obstacle_move(self):
self.rect.x -= self.move
#绘制障碍物
def draw_obstacle(self):
SCREEN.blit(self.image,(self.rect.x, self.rect.y))
在 mainGame()方法中,创建定义添加障碍物的时间与障碍物对象列表(玛丽对象的代码下面)
addObstackeTimer = 0
list = []
在 mainGame()方法中绘制计算障碍物出现的间隔时间(玛丽对象的代码下面)
if addObstackeTimer >= 100: # 如果生成障碍物的计时器达到100
r = random.randint(0, 50) # 生成一个0到50之间的随机数
if r > 15: # 如果随机数大于15
obstacle = Obstacle() # 创建一个障碍物对象
list.append(obstacle) # 将障碍物对象添加到列表中
addObstackeTimer = 0 # 重置生成障碍物的计时器为0
在 mainGame0 方法中计算循环遍历障碍物并进行障碍物的绘制(障碍物间隔时间代码的下面)
for i in range(len(list)):
list[i].obstacle_move()
list[i].draw_obstacle()
在 mainGame0 方法中更新整个窗体代码的上面,增加障碍物时间
addObstackeTimer += 10
6.5背景音乐的播放与停止
创建 Music Button 类,在该类中首先初始化背景音乐的音效文件与按钮图片,然后创建 isselect0 方法用于判断鼠标是否在按钮范围内
# 背景音乐按钮
class Music_Button():
is_open = True # 背景音乐是否开启的状态标志
def __init__(self):
# 加载开启和关闭按钮的图像资源
self.open_img = pygame.image.load("image/btn_open.png").convert_alpha()
self.close_img = pygame.image.load("image/btn_close.png").convert_alpha()
# 加载背景音乐的音频文件
self.bg_music = pygame.mixer.Sound("audio/bg_music.wav")
def is_select(self):
# 获取鼠标的位置
point_x, point_y = pygame.mouse.get_pos()
# 获取开启按钮图像的宽度和高度
w, h = self.open_img.get_size()
# 判断鼠标是否在按钮范围内
in_x = point_x > 20 and point_x < 20 + w
in_y = point_y > 20 and point_y < 20 + h
return in_x and in_y
mainGame 方法中障碍物对象列表代码的下面,创建背景音乐按钮对象,然后设置按钮默认图片,最后循环播放背景音乐。
muscic_button = Music_Button()
btu_img = muscic_button.open_img
muscic_button.bg_music.play(-1)
在 mainGame0 方法的 while 循环中,获取单击事件代码的下面实现单击按钮控制背景音乐的播放与停止功能
if event.type == pygame.MOUSEBUTTONUP:
# 判断是否为鼠标按键抬起事件
if muscic_button.is_select():
# 判断鼠标是否在背景音乐按钮范围内
if muscic_button.is_open:
# 如果背景音乐当前是开启状态
btu_img = muscic_button.close_img
muscic_button.is_open = False
muscic_button.bg_music.stop()
else:
# 如果背景音乐当前是关闭状态
btu_img = muscic_button.open_img
muscic_button.is_open = True
muscic_button.bg_music.play(-1)
在 mainGame()方法中添加障碍物时间代码的下面,绘制背景音乐按钮
SCREEN.blit(btu_img, (20, 20))
6.6碰撞与积分功能实现
在实现碰撞与积分时,首先需要判断玛丽与障碍物的两个矩形图片是否发生了碰撞,如果发生了碰撞就证明该游戏已经结束,否则判断玛丽是否跃过了障碍物,确认越过后进行加分操作并将分数显示在窗体顶部右侧的位置。
在 Obstacle 类中,draw obstacle0 方法的下面创建 getScore0 方法用于获取分数并播放加分音效,然后创建 showScore() 方法用于在窗体顶部右侧的位置显示分数
def getSocre(self):
"""
获取分数并重置分数为0
"""
# 获取当前的分数
tmp = self.score
# 如果分数为1,播放分数音效
if tmp == 1:
self.score_audio.play()
# 将分数重置为0
self.score = 0
# 返回原来的分数
return tmp
# 显示分数
def showScore(self, score):
"""
在游戏界面上显示分数
"""
# 将分数转换为一个数字列表
self.scoreDigits = [int(x) for x in list(str(score))]
# 计算所有数字图像的总宽度
totalWidth = 0
for digit in self.scoreDigits:
totalWidth += self.numbers[digit].get_width()
# 计算数字图像的起始横坐标
Xoffset = (SCREENWIDTH - (totalWidth + 30))
# 遍历分数的每个数字,将对应的数字图像绘制到屏幕上
for digit in self.scoreDigits:
# 绘制数字图像到屏幕上,并更新Xoffset的值
SCREEN.blit(self.numbers[digit], (Xoffset, SCREENHEIGHT * 0.1))
Xoffset += self.numbers[digit].get_width()
在 mainGame()方法的上面最外层创建 game over()方法,在该方法中首先需要加载与播放撞击的音效,然后获取窗体的宽度与高度,最后加载游戏结束的图片并将该图片显示在窗体的中间位置
def game_over():
# 播放碰撞音效
bump_audio = pygame.mixer.Sound("audio/bump.wav")
bump_audio.play()
# 获取屏幕的宽度和高度
screen_w = pygame.display.Info().current_w
screen_h = pygame.display.Info().current_h
# 加载游戏结束图片
over_img = pygame.image.load("image/gameover.png").convert_alpha()
# 在屏幕上绘制游戏结束图片,位置居中显示在屏幕中央
SCREEN.blit(over_img, ((screen_w - over_img.get_width()) / 2, (screen_h - over_img.get_height()) / 2))
在 mainGame()方法中,绘制障碍物代码的下面判断玛丽与障碍物是否发生碰撞,如果发生了碰撞则开启游戏结束的开关,并调用游戏结束的方法显示游戏结束的图片,否则判断玛丽是否跃过了障碍物,越过就进行分数的增加并显示当前得分
if pygame.sprite.collide_rect(marie, list[i]):
# 玛丽与物品发生碰撞
over = True # 游戏结束标志设为True
game_over() # 显示游戏结束画面
music_button.bg_music.stop() # 停止播放背景音乐
else:
if (list[i].rect.x + list[i].rect.width) < marie.rect.x:
# 物品已完全移出玛丽的右侧,计入分数
score += list[i].getSocre()
list[i].showScore(score)
为了实现游戏结束后再次按下键盘上的(空格)键时,重新启动游戏。所以需要在 mainGame0 方法中开启玛丽跳的状态代码的下面判断游戏结束的开关是否开启,如果开启将重新调用 mainGame0)方法重新启动游戏
if over == True:
mainGame()
总结
- 最后希望你编程学习上不急不躁,按照计划有条不紊推进,把任何一件事做到极致,都是不容易的,加油,努力!相信自己!
文末福利
- 最后这里免费分享给大家一份Python全套学习资料,希望能帮到那些不满现状,想提升自己却又没有方向的朋友,也可以和我一起来学习交流呀。
包含编程资料、学习路线图、源代码、软件安装包等!【[点击这里]】领取!
- ① Python所有方向的学习路线图,清楚各个方向要学什么东西
- ② 100多节Python课程视频,涵盖必备基础、爬虫和数据分析
- ③ 100多个Python实战案例,学习不再是只会理论
- ④ 华为出品独家Python漫画教程,手机也能学习