学习目标
-
了解 飞机大战游戏的规则
-
理解 面向对象思想,会独立设计游戏的类与模块
-
掌握 pygame模块的使用
1.1 游戏介绍
飞机大战是一款由腾讯公司微信团队推出的软件内置的小游戏,这款游戏画面简洁有趣,规则简单易懂,操作简便易上手,在移动应用兴起之初曾风靡一时。
1.1.1 游戏介绍
飞机大战游戏主要以太空主题的画面为游戏背景,由玩家通过键盘控制英雄飞机向敌机总部发动进攻,在进攻的过程中既可以让英雄飞机发射子弹或炮弹击毁敌机以赢取分数,也可以拾取道具以增强自己的战斗力,一旦被敌机撞毁就结束游戏。
飞机大战游戏包含众多游戏元素,例如,大小各异的飞机、连发3颗的子弹、左上角的游戏分数等,其中主要的元素如图所示。
飞机大战游戏中的主要元素可以归纳为背景、英雄飞机、敌机、道具、分数和奖励、关卡设定这几大类,各类中游戏元素的具体说明如下。
1.背景 整个游戏窗口的背景是一张星空图像,该背景图像会缓缓地向下持续移动,使玩家产生一种英雄飞机向上飞行的错觉。
2.英雄飞机 英雄飞机是由玩家控制的飞机,是飞机大战游戏的主角,其相关说明具体如下。
(1)英雄飞机在游戏开始时显示在屏幕下方的中央位置。
(2) 英雄飞机在出场后的3秒内处于无敌状态,此时它不会被任何敌机撞毁,也不会撞毁任何敌机。
(3)玩家可以通过方向键(↑、↓、←、→)控制英雄飞机在屏幕范围内向上方、下方、左方和右方移动。 英雄飞机是由玩家控制的飞机,是飞机大战游戏的主角,其相关说明具体如下。
(4) 英雄飞机出场后每隔0.2秒会自动连续发射3颗子弹,关于子弹的特征和行为具体如下: 特征:子弹的速度为12,杀伤力为1。 行为:子弹由英雄飞机头部的正上方位置发射,沿屏幕垂直方向向上飞行;子弹被发射时需要播放发射子弹的音效;子弹在飞行途中若击中敌机,会对敌机造成伤害;子弹若已飞出屏幕且飞行途中未击中任何敌机,会被销毁。 英雄飞机是由玩家控制的飞机,是飞机大战游戏的主角,其相关说明具体如下。
(5) 英雄飞机出场后默认会携带3颗炸弹,玩家按下字母b时会引爆1枚炸弹,炸弹数量减1;炸弹数为0时,无法再使用炸弹。
(6) 英雄飞机带有多个动画和音效。当飞机飞行时,显示飞行动画;当飞机被敌机撞毁时,显示被撞毁动画,并播放被撞毁音效;当飞机升级时,播放升级音效。
英雄飞机是由玩家控制的飞机,是飞机大战游戏的主角,其相关说明具体如下。
(7) 英雄飞机具有多条生命。英雄飞机的初始生命为3;英雄飞机被敌机击中时,命数减1;英雄飞机得分每增加10万,命数加1;英雄飞机的生命为0时,游戏结束。英雄飞机的命数会实时地显示在游戏界面的右下方位置。
3.敌机 飞机大战游戏中敌机的机型小、中、大三种,各机型均有生命值、速度、分值、图片和音效等多个特征,且不同类型的敌机所具有的特征也不尽相同,后续在设计类时会有详细说明。 飞机大战中的敌机具有以下行为:
(1)敌机出现在游戏窗口顶部的随机位置。
(2)敌机按照各自不同的速度,沿垂直方向向游戏窗口的下方飞行。
(3)若敌机与英雄飞机相撞,则会击毁英雄飞机。
飞机大战中的敌机具有以下行为:
(4)若敌机被子弹击中,则敌机的生命值需要减去子弹的伤害度,此时根据敌机的生命值可以分如下两种情况进行处理: 如果敌机的生命值大于 0,那么显示敌机被击中图片(若有被击中图片),让敌机继续向屏幕下方飞行。 如果敌机的生命值等于0,那么播放敌机被撞毁动画与被撞毁音效。在被撞毁动画播放过程中,该敌机不会在屏幕上移动;在被撞毁动画播放完成后,该敌机被设为初始状态,跳转到第(1)步继续执行。
飞机大战中的敌机具有以下行为:
(5)若敌机飞出了游戏窗口且飞行途中没有被击毁,该敌机被设为初始状态。 值得一提的是,正在播放被撞毁动画的敌机是已经被摧毁的敌机,它既不能被子弹击中,也不能撞击英雄飞机。
4.道具 在游戏过程中,道具每隔30秒会从游戏窗口上方的随机位置向下飞出,一旦飞出的过程中碰撞英雄飞机,就会被英雄飞机拾取。飞机大战游戏有两种道具:炸弹补给和子弹增强,关于这两种道具的功能描述如表所示。
道具名称 | 功能描述 | 速度 | 播放音效 |
---|---|---|---|
炸弹补给 | 英雄飞机拾取后,炸弹数量加1 | 5 | 是 |
子弹增强 | 英雄飞机拾取后,发射的子弹由单排改为双排,且持续时长20秒 | 5 | 是 |
5.分数和奖励 当英雄飞机通过子弹或炸弹击毁敌机时,会获得与敌机分值相对应的分数,并将获得的分数实时地显示在游戏窗口的左上方。同一局游戏的分数会不断累加,并在下一局开始时自动清零。 系统会记录玩家历次游戏所得到的最高分,并在游戏暂停和结束时显示在游戏窗口上。
6.关卡设定 飞机大战游戏一共设立了3个关卡,依次是关卡1、关卡2和关卡3,其中关卡1为起始关卡。在英雄飞机的得分超过当前关卡的预设分值之后,游戏会自动进入下一个关卡。关卡越高难度越高,敌机数量和种类越多,速度也更快。3 个关卡的具体设定如表所示。
关卡名称 | 分值范围 | 小敌机数量(速度) | 中敌机数量(速度) | 大敌机数量(速度) |
---|---|---|---|---|
关卡 1 | < 10000 | 16(1 ~ 3) | 0(1) | 0(1) |
关卡 2 | < 50000 | 24(1 ~ 5) | 2(1) | 0(1) |
关卡 3 | >= 50000 | 32(1 ~ 7) | 4(1 ~ 3) | 2(1) |
1.1.2 游戏典型场景
飞机大战游戏的典型事件组成了各个典型场景,除了游戏进行中的场景之外,还包括游戏开始、飞机碰撞、游戏暂停、游戏结束几个场景。
1.2 项目准备
明确了飞机大战游戏之后,进入开发工作之前,我们需要先完成分析与设计等准备工作,以明确项目的实现方式和内部结构等。
1.2.1 类设计
根据游戏介绍分析可知,飞机大战游戏有众多游戏元素,为方便统一管理游戏元素,我们按游戏元素的职责进行归类,各类的名称及说明如表所示。
分类 | 类名 | 说明 |
---|---|---|
游戏类 | Game | 负责整个游戏的流程 |
指示器面板类 | HUBPanel | 负责统一管理游戏状态、游戏分数、炸弹数量、生命值以及文本提示等与游戏数据或状态相关的内容 |
音乐播放类 | MusicPlayer | 负责背景音乐和音效的播放 |
游戏背景类 | Background | 负责显示游戏的背景图像 |
状态按钮类 | StatusButton | 负责显示游戏的状态按钮 |
飞机类 | Plane | 表示游戏中的飞机,包括英雄飞机和敌机 |
子弹类 | Bullet | 表示英雄飞机发射的子弹 |
道具类 | Supply | 负责管理炸弹补给和子弹增强道具 |
文本标签类 | Label | 负责显示游戏窗口上的文本 |
飞机大战游戏项目中提炼出的类及类之间的继承关系如图所示。
1.2.2 模块设计
在设计程序时,我们既要考虑程序的设计理念和设计思想,又要考虑程序的结构。结构设计的原则是代码模块化、容易扩展和维护。因此,可将飞机大战游戏项目划分为4个模块:game.py、game_items.py、game_hud.py和game_music.py,各模块的说明如表所示。
模块 | 说明 |
---|---|
game.py | 游戏主模块,封装Game类并负责启动游戏。 |
game_items.py | 游戏元素模块,封装英雄飞机、子弹、敌机、道具等游戏元素类,并定义全局变量。 |
game_hud.py | 游戏面板模块,封装指示器面板类。 |
game_music.py | 游戏音乐模块,封装音乐播放器类。 |
1.2.3 创建项目
打开PyCharm工具,新建一个名称为“飞机大战”的项目。 在飞机大战项目中依次建立game.py、game_items.py、game_hud.py和game_music.py四个文件,并且将资源文件夹“res”复制到飞机大战目录下。创建好的项目文件结构如图所示。
图中的res目录包含三个子目录:images、sound和font,子目录分别放置了游戏中使用的图片、声音和字体素材。
11.2.4 下载集成pygame模块
1.3 游戏框架搭建
准备工作完成之后,便可以进入项目的实现阶段。游戏框架搭建是实现项目的第一步,它会按照游戏的完整流程搭建整个框架,如此便可以直接向框架内填充游戏的内容。
1.3.1 游戏类的设计
游戏类(Game)负责整个游戏的流程,它需要包含游戏中的主要元素,设计后的类图如图所示。
关于游戏类的属性和方法的说明具体如下。 1.Game类的属性 Game类的属性按作用的不同可以分为游戏属性和精灵(表示显示图像的对象,游戏窗口中看到的每个单独图像或者一行文本都可以看作一个精灵,例如,英雄飞机、一颗子弹、分数标签等)组属性。
(1)游戏属性
属性 | 说明 |
---|---|
main_window | 游戏主窗口,初始大小为 (480, 700) |
is_game_over | 游戏结束标记,初始为 False |
is_pause | 游戏暂停标记,初始为 False |
hero | 英雄精灵,初始显示在游戏窗口中间靠下位置 |
hud_panel | 指示器面板,负责显示与游戏状态以及数据相关的内容,包括状态图像、游戏得分、炸弹数量、英雄命数,以及游戏暂停或结束时显示在游戏窗口中央位置的提示信息等 |
player | 音乐播放器,负责播放背景音乐和游戏音效 |
(2)精灵组属性 游戏类中定义的精灵组属性如表所示。
属性 | 说明 |
---|---|
all_group | 所有精灵组,存放所有要显示的精灵,用于屏幕绘制和更新位置 |
enemies_group | 敌机精灵组,存放所有敌机精灵对象,用于检测子弹击中敌机以及敌机撞击英雄 |
supplies_group | 道具精灵组,存放所有道具精灵对象,用于检测英雄飞机拾取道具 |
关于游戏类的属性和方法的说明具体如下。
2.Game类的方法 Game类封装了多个方法,分别用于创建游戏元素、管理游戏的流程、监听系统事件等。
Game类中定义的方法如表所示。
方法 | 说明 |
---|---|
reset_game() | 重置游戏。在开启新一轮游戏之前,将游戏属性恢复到初始值 |
create_enemies() | 创建敌机精灵。在新游戏开始或者关卡晋级后,根据当前游戏级别创建敌机精灵 |
create_supplies() | 创建道具。游戏开始后每隔 30 秒随机投放炸弹补给或子弹增强道具 |
start() | 开始游戏。创建时钟对象并且开启游戏循环,在游戏循环中监听事件、更新精灵位置、绘制精灵、更新显示、设置刷新帧率 |
event_handler() | 事件监听。监听并处理每一次游戏循环执行时发生的事件,避免游戏循环中的代码过长 |
check_collide() | 碰撞检测。监听并处理每一次游戏循环执行时是否发生精灵与精灵之间的碰撞,例如,子弹击中敌机、英雄拾取道具、敌机撞击英雄等 |
明确了游戏类的设计之后,便可以开始游戏框架的搭建工作。游戏框架的实现过程如下。 1.定义游戏窗口尺寸的全局变量 2.实现Game类的基础代码 3.在主程序中启动游戏 4.使用空格键切换游戏状态
1.3.2 游戏框架实现
明确了Game类的设计后,便可以开始游戏框架的搭建工作。游戏框架的实现过程如下。
1.声明游戏窗口尺寸的全局变量
在game_items模块中,声明一个表示游戏窗口尺寸的全局矩形对象SCREEN_RECT
import pygame
# 游戏窗口区域(矩形区域),起点坐标和终点坐标
SCREEN_RECT=pygame.Rect(0,0,480,700)
2.实现Game类的基础代码
在game模块中定义Game类,并实现构造方法(_ _ init _ _)和重置游戏方法(reset_game)
import pygame
from game_items import *
class Game(object):
"""游戏类的构造方法"""
def __init__(self):
# 设置游戏主窗口
self.main_window=pygame.display.set_mode(SCREEN_RECT.size)
# 设置标题
pygame.display.set_caption("飞机大战")
# 定义游戏状态属性
self.is_game_over=False #游戏结束标记
self.is_pause=False #游戏暂停标记
"""重置游戏方法"""
def reset_game(self):
self.is_game_over = False # 游戏结束标记
self.is_pause = False # 游戏暂停标记
在Game类中定义event_handler()方法,用于监听游戏循环中发生的事件,若监听到退出事件,则返回True,否则返回Flalse
"""事件监听
return:如果监听到退出事件,返回True,否则返回False"""
def event_handler(self):
for event in pygame.event.get():
# 游戏退出事件
if event.type == pygame.QUIT:
return True
# 键盘退出按钮被触发
elif event.type==pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
return True
return False
在Game类中定义start()方法。start()方法用于开始游戏,该方法中先定义时钟对象,再开启游戏循环
"""开始游戏"""
def start(self):
clock=pygame.time.Clock() # 游戏时钟
while True: # 开始游戏循环
if self.event_handler(): # 当事件返回为True,则表示退出游戏,返回为空
return
pygame.display.update() # 更新游戏显示
clock.tick(60) # 设置游戏刷新帧率
以上方法的循环体中首先调用了event_handler()方法监听事件,然后调用了pygame.display.update()方法更新显示,最后调用tick()方法设置刷新帧率,设置后再保证动画效果流畅的前提下,降低了CPU负荷。
3.在主程序中启动游戏
在game模块的末尾增加以下代码,实现创建游戏对象并且启动游戏的功能
if __name__ =="__main__":
# 初始化游戏
pygame.init()
# 创建游戏对象,开启游戏
Game().start()
# 设置按下esc键退出游戏
pygame.quit()
运行游戏,可以看到创建成功的游戏主窗口,此时按下“Esc”键或者单击关闭按钮都可以退出程序。
4.使用空格键切换游戏状态
调整event_handler()方法。在事件监听的循环体中,增加对空格键按键事件的监听,并在监听到空格键时切换游戏状态
# 键盘空格键被触发
elif event.type==pygame.KEYDOWN and event.key==pygame.K_SPACE:
if self.is_game_over: # 如果游戏已经结束
self.reset_game() # 则重新开始
else:
self.is_pause=not self.is_pause # 否则切换为暂停状态/恢复游戏
在游戏循环语句中增加判断游戏状态的代码,若游戏已经结束,则使用print()方法输出游戏结束状态的描述文字,若游戏暂停,则使用print()方法输出游戏暂停状态的描述文字,若游戏正在进行中,则使用print()方法输出游戏状态的描述文字。
"""开始游戏"""
def start(self):
clock=pygame.time.Clock() # 游戏时钟
while True: # 开始游戏循环
if self.event_handler(): # 当事件返回为True,则表示退出游戏,返回为空
return
#判断游戏状态
if self.is_game_over:
print("游戏已经结束,按空格键重新开始……")
elif self.is_pause:
print("游戏已暂停,按空格键继续……")
else:
print("游戏进行中……")
pygame.display.update() # 更新游戏显示
clock.tick(60) # 设置游戏刷新频率
运行游戏,不断按下空格键,可以看到控制台中有关游戏状态提示文字的显示及变化。
需要说明的是,若需要测试游戏结束后使用空格键重新开始游戏的功能,可以先将构造方法的is_game_over属性暂时设置为True,再进行测试。
1.4 游戏背景和英雄飞机
飞机大战项目需要管理众多游戏对象(例如敌机、英雄飞机、子弹等),并且实现众多动画(例如英雄飞机飞行动画,飞机碰撞动画等),使用精灵和精灵组来管理这些游戏对象的显示和动画效果是再合适不过了。 接下来,本节为大家介绍游戏精灵类,带领大家绘制游戏背景和英雄飞机,并实现游戏背景的滚动动画。
1.4.1 介绍精灵和精灵组
pygame专门提供了两个类:Sprite和Group,分别表示精灵和精灵组,其中精灵代表显示图像的游戏对象,精灵组代表保存和管理一组精灵的容器。Sprite和Group类中包含一些便于操作的属性或方法,具体介绍如下。
1.Sprite类 Sprite是一个表示游戏对象的基类,该类中提供的常用属性和方法如表所示。
类型 | 名称 | 说明 |
---|---|---|
属性 | image | 记录从磁盘加载的图片内容或者字体渲染的文本内容,注意:子类中必须指定 |
属性 | rect | 记录 image 要显示在游戏窗口的矩形区域,注意:子类中必须指定 |
方法 | update(*args) | 默认什么都不做,子类可以根据需求重写,以改变精灵的位置rect 或者显示内容image |
方法 | add(*groups) | 将精灵添加到指定的精灵组(多值) |
方法 | remove(*groups) | 将精灵从指定的精灵组(多值)移除 |
方法 | kill() | 将精灵从所有精灵组移除 |
值得一提的是,Sprite只是一个基类,实际开发中需要派生一个Sprite的子类,通过子类创建精灵对象。
2.Group类 Group是一个包含若干精灵的容器类,该类中提供了一些管理精灵的常用方法,关于这些方法的说明如表所示。
名称 | 说明 |
---|---|
update(*args) | 组内所有精灵调用update(*args) 方法,一次改变所有精灵的位置rect 或显示内容image |
draw(surface) | 将组内所有精灵的 image 绘在 surface 的 rect 矩形区域,实现在游戏窗口中一次绘制多个精灵的功能 |
sprites() | 返回精灵组中所有精灵的列表 |
add(*sprites) | 向精灵组中添加指定的精灵(多值) |
remove(*sprites) | 从精灵组中移除指定的精灵(多值) |
empty() | 清空精灵组中所有的精灵 |
1.4.2 派生游戏精灵子类
按照pygame官方文档的建议,我们在实际开发中不能直接使用Sprite类,而是要使用Sprite类派生的子类。 pygame官方文档规定了Sprite类子类的注意事项,具体如下。
-
子类可以重写update()方法。
-
子类必须为image和rect属性赋值。
-
子类的构造方法能够接收任意数量的精灵组对象,以将创建完的精灵对象添加到指定的精灵组中。
-
子类的构造方法中必须调用父类的构造方法,如此才能向精灵组中添加精灵。
在game_items模块中定义pygame.sprite.Sprite的子类GameSprite,GameSprite类的类图如图所示。
GameSprite类定义了飞机大战游戏项目中大部分精灵类的通用特征和功能,它将作为其他精灵类的基类来使用。
"""游戏精灵类"""
class GameSprite(pygame.sprite.Sprite):
res_path="./res/images/" # 图片资源路径
def __init__(self,image_name,speed,*groups):
"""构造方法
:param image_name:要加载的图片文件名
:param speed:移动速度,0表示静止
:param groups:要添加到的精灵组,不传值则不添加"""
super().__init__(*groups)
#加载图像
self.image=pygame.image.load(self.res_path+image_name)
# 获得图片尺寸大小
self.rect=self.image.get_rect() # 默认再左上角
self.speed=speed # 保存移动速度
def update(self,*args):
"""更新精灵位置,默认再垂直方向移动"""
self.rect.y +=self.speed
GameSprite类定义了飞机大战游戏项目中大部分精灵类的通用特征和功能,它将作为其他精灵类的基类来使用。
1.4.3 绘制游戏背景和英雄飞机
1.按照Game类的类图,我们需要在Game类的构造方法中: 创建 3 个精灵组 创建背景精灵 创建英雄精灵
# 精灵组属性
self.all_group = pygame.sprite.Group() # 所有精灵组
self.enemies_group = pygame.sprite.Group() # 敌机精灵组
self.supplies_group = pygame.sprite.Group() # 道具精灵组
#创建精灵
#创建背景精灵,向下方移动
GameSprite("background.png",1,self.all_group)
#英雄精灵,静止不动
hero=GameSprite("me1.png",0,self.all_group)
hero.rect.center=SCREEN_RECT.center #显示在屏幕中央
2.在判断游戏状态之后、更新显示之前需要增加负责绘制和更新所有精灵的代码。
"""开始游戏"""
def start(self):
clock=pygame.time.Clock() # 游戏时钟
while True: # 开始游戏循环
if self.event_handler(): # 当事件返回为True,则表示退出游戏,返回为空
return
#判断游戏状态
if self.is_game_over:
print("游戏已经结束,按空格键重新开始……")
elif self.is_pause:
print("游戏已暂停,按空格键继续……")
else:
#更新all_gourp中所有精灵内容
self.all_group.update()
# 绘制all_group中的所有精灵
self.all_group.draw(self.main_window)
pygame.display.update() # 更新游戏显示
clock.tick(60) # 设置游戏刷新频率
运行游戏,可以在游戏窗口中看到缓缓向下移动的星空背景和英雄飞机,如图所示。
<