Python基础项目:飞机大战详解(附源码和原理讲解)
声明
此项目是对python基础语法和高级语法的结合,学完python基础和高级可以拿此项目练手,说的比较细,涵盖了pygame一些知识点的讲解。
项目展示
安装准备
这里我们要用到pygame模块,pygame模块是python中针对电子游戏开发的模块,功能十分完善,安装pygame模块的方法(针对windows用户):直接在终端输入 pip install pygame,如果嫌慢可以用国内镜像源,这里我拿清华大学举例子,具体方法是:
pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple/
安装成功后会显示successfully,也可以输入pip list 进行查看
素材准备
在飞机大战开始前我们会需要一些飞机图片,背景图片等等作为素材导入到游戏中,来让游戏更加生动精彩,素材图片你们可以自己去收集,这里我提供一些素材图片,地址如下:
https://pan.baidu.com/s/1pMM0beb
开始准备
项目准备
1.新建一个飞机大战的文件夹;
2.在里面新建一个自定义名字的python文件;
3.将刚才的游戏素材图片导入。
游戏第一印象
1.把一些静止的图象绘制到游戏窗口中
2.根据用户的交互或其他情况,移动这些图象,产生动画效果
3.根据图象是否发生重叠,判断敌机是否被摧毁,以及自己的战机是否被摧毁等情况
游戏原理
绘制窗口
初始化和退出
这里我们需要使用到pygame提供的init方法进行初始化,quit来退出
pygame.init() 导入并初始化所有pygame模块,以便后面我们需要用到
pygame.quit() 卸载所有之前pygame加载的模块,在游戏结束之前调用,及时释放内存空间
两个方法之前就是我们需要游戏代码的地方。
游戏中的坐标系
这里我自己画一个图来让大家理解
在游戏里,所有可见的元素都是以矩形区域来描述位置的,要描述一个矩形区域需要四个变量:
(x,y)(width,height) ;(x,y)是定义矩形的左上角的坐标,(width,height)是矩形窗口的宽和高,既准确大小
pygame专门提供一个类pygame.Rect用来描述矩形区域(注意Rect大小写),pygame.Rect里面包含了x,y,width,height,size等属性,定义一个plane_rect来描述自己飞机的位置和大小,举例如下:
import pygame
# 不执行pygame.init()同样可以使用Rect方法
plane_rect = pygame.Rect(100, 50, 125, 150)
print(plane_rect.x)
print(plane_rect.y)
print(plane_rect.width)
print(plane_rect.height)
输出如下:
100
50
125
150
创建游戏主窗口
pygame专门提供了一个模块pygame.display用于创建,管理游戏窗口
import pygame
pygame.init()
screen = pygame.display.set_mode((480, 700))
# 防止游戏窗口自动关闭,窗口一直显示,除非终止运行
while True:
pass
pygame.quit()
1.创建游戏窗口里set_mode方法,取决于背景图片的大小,里面第一个参数是(width,height),是一个元组一定要用() 括起来,这里我设置宽480,高700。
2.必须要用一个变量来接受set_mode返回的结果,以为后续所有图象绘制都基于这个放回的结果
3.我为了方便展示让窗口一直不关闭就用了一个while True,后续还会改进
绘制图像
图像文件初始是保存在磁盘上的,如需使用,我们就要把图像加载到内存,然后将图像绘制到指定位置既绘制到游戏窗口
绘制图像的三个步骤:
1.使用pygame.image.load()加载图像的数据
2.使用游戏屏幕对象,调用blit方法将图像绘制到指定屏幕
3.调用pygame.display.update()方法更新整个屏幕的显示,可以在所有图像绘制完之后调用update
import pygame
pygame.init()
screen = pygame.display.set_mode((480, 700))
# 绘制背景图像
# 加载图像数据,括号里面是图片地址
background = pygame.image.load('./飞机大战/images/background.png')
# blit绘制图像,(0,0)是将背景图的左上角绘制到游戏窗口的左上角
screen.blit(background, (0, 0))
# update 更新屏幕显示
pygame.display.update()
# 防止游戏窗口自动关闭,窗口一直显示,除非终止运行
while True:
# 针对未响应
pygame.event.get()
pygame.quit()
注意:如果你打开游戏窗口后提示未响应,你就在while循环下加上pygame.event.get()就可以解决
绘制自己的战机
和绘制图像一样用绘制图像的三个步骤将自己的战机绘制到游戏窗口里
# 绘制英雄的飞机
hero = pygame.image.load("./飞机大战/images/me1.png")
screen.blit(hero, (200, 500))
pygame.display.update()
以上代码要放在绘制背景图的下面,while循环的上面
游戏动画效果
动画实现原理
游戏动画原理和电源的原理类似,就是将多张静止的电源胶片或图片连续,快速的播放,产生连贯的视觉效果,一般每秒绘制60次,就能够达到非常好的效果,每次绘制的结果就叫帧,而每次用pygame.display.update()更新一次屏幕就叫一帧,这里我们就要用到循环来达到这种效果
游戏循环
上面的游戏窗口已经把游戏窗口和背景都已经设置好,而游戏循环意味着游戏正式开始,即解决刷新帧率,这里我们可以设置每秒刷新60次,即1/60秒移动所有图像的位置;也可以检测用户交互如用户操控按键和鼠标来移动战机的位置。
设置时钟对象
时钟顾名思义就是在规定的时间内执行多少次,可以理解为每秒执行多少次,即刷新帧率,pygame专门提供一个类pygame.time.Clock()可以非常方便的设置屏幕绘制速度,即刷新帧率。在游戏初始化里设置一个时钟对象,在循环里面调用
# 设置时钟对象
clock = pygame.time.Clock()
# 防止游戏窗口自动关闭,窗口一直显示,除非终止运行
while True:
# 设置游戏频率,可以指定循环内部的代码执行的频率,60表示每秒执行60次
clock.tick(60)
# 针对未响应
pygame.event.get()
英雄战机的移动
英雄战机的移动大致可以理解为在每次循环内坐标发生移动,并且让每次循环的时间设置在1/60秒,就可以看到飞机在窗口上连续的移动。我们先定义
飞机的位置,然后在每次循环内对飞机的坐标进行更改,然后每次循环结束前在对屏幕进行更新
# 设置时钟对象
clock = pygame.time.Clock()
# 定义rect记录飞机的初始位置
hero_rect = pygame.Rect(200, 500, 102, 126)
while True:
# 设置游戏频率,可以指定循环内部的代码执行的频率,60表示每秒执行60次
clock.tick(60)
# 修改飞机的位置
hero_rect.y -= 1
# 调用blit方法绘制图像,每次循环完用背景图覆盖一次,不然会造成每次循环的飞机重叠
screen.blit(background, (0, 0))
screen.blit(hero, hero_rect)
# 更新屏幕显示
pygame.display.update()
# 针对未响应
pygame.event.get()
需要注意的是,在每次循环内我们需要覆盖掉上一次战机,即用背景图进行覆盖,不然会造成每次循环的战机都留在屏幕上,所以我们在每次循环绘制战机之前,先对上一次循环的战机进行覆盖。下图就是不覆盖的结果:
战机移动的扩展
这里我们可以对战机移动进行一个扩展,就是当战机移动到顶部的时候,会从底部钻出来,这样会使战机更连贯,话不多说,我们来实现这个效果。
# 判断飞机的位置,让飞机从上面钻出,下面钻入
if hero_rect.y <= -126:
hero_rect.y = 700
其实这个效果的实现特别简单,就是飞机头部即y坐标处于-126即飞机的height的时候,令y等于窗口的底部即y=700即可
游戏循环中的监听事件
监听事件是什么?其实可以这样理解,监听就是游戏循环中判断用户的具体操作,只有捕获用户的具体操作,才能由针对的做出响应;事件就是游戏启动后用户针对游戏进行的操作,例如点击鼠标,按键盘,点击关闭按钮。
综上所述:就是判断和识别用户的所做的操作,来进行相应的响应。
pygame中通过pygame.event.get()可以获得用户当前所作动作的事件列表
识别退出游戏
退出游戏就是点击游戏窗口的×来退出游戏,这里的判断语句是针对pygame.event.get()方法,鼠标,键盘的每次移动都会实时显示在输出终端内,然后根据点击x按钮时输出的结果进行分析就可以得到以下判断语句,下面的判读退出系统几乎是确定的,所有pygame开发的游戏在退出游戏几乎都是下面的这段代码。
# 事件监听
for event in pygame.event.get():
if event.type == pygame.QUIT:
print("退出游戏....")
# quit卸载所有模块
pygame.quit()
# 退出
exit()
注意:有人可能会问为什么不用break退出,因为break只能退出当前循环即退出for循环,但是for循环上面还有一个while循环,所有要用到exit()来退出程序
精灵和精灵组
上面我们大致解决英雄战机的移动和绘制,但是针对敌机我们还不能解决,难道敌机是一个一个用上面的三个步骤进行绘制和移动吗,当然不是,那样太过于麻烦。这里我们引入一个十分重要的概念:精灵和精灵组。精灵是什么?其实精灵可以看成一个对象(这里看不懂可以去复习一下面向对象编程),是一个能够记录图像数据imag和位置rect的对象,它还有一个更新精灵位置的方法。说到这就简单了,这个精灵可以派生出很多子类,我们就可以把敌机看成这些子类,然后对每个子类重写不一样的属性和方法,然后直接调用就行,是不是简单了许多,而精灵组就是所有精灵的组合,对精灵组的所有操作都可以在精灵上实现。
敌机的实现
首先我们要知道pygame内部自带了精灵类pygame.sprites.Sprites和精灵组pygame.sprites.Group我们到时候直接用就可以了,精灵类即pygame.sprites.Sprites自带了两个个属性即图像数据image,位置属性rect,和两个方法一个是update()更新精灵位置和kill()从所有组中删除,而精灵组pygame.sprites.Group有add()方法向精灵组添加精灵,update()方法让所有精灵调用各自的update方法,以及draw(Surface)将所有精灵绘制到Surface的rect位置。
现在我们新建一个plane_sprites.py来创建敌机类
import pygame
class GameSprite(pygame.sprite.Sprite):
"""飞机大战游戏精灵"""
def __init__(self, image_name, speed=1):
# 调用父类的初始化方法,如果不调用就会对父类的init方法进行重写,就不能用父类的方法了
super().__init__()
# 定义对象的属性
# 加载图片
self.image = pygame.image.load(image_name)
# 返回图像大小
self.rect = self.image.get_rect()
# 速度
self.speed = speed
def update(self):
# 在屏幕的垂直方向移动
self.rect.y += self.speed
我们创建GameSprites类的时候继承了父类pygame.sprite.Sprite类的方法,如果不继承,就用不到父类的方法;另外用init初始化方法的时候,我们要用父类的init初始化方法,如果我们不用super()调用父类的初始化方法,就相当于重写了父类的初始化方法,也就用不了父类的属性了。image和rect都是精灵自带的,我们直接用就可以了,速度speed我们可以自定也可以直接设置默认值,后面可以改,update()方法就是更新敌机的位置。
别忘了导入精灵文件
下面我们来在主文件内创建敌机:
# 创建敌机的精灵
enemy = plane_sprites.GameSprite("./飞机大战/images/enemy1.png")
enemy1 = plane_sprites.GameSprite("./飞机大战/images/enemy1.png", speed=2)
# 创建敌机的精灵组
enemy_group = pygame.sprite.Group(enemy, enemy1)
这里我暂时创建两个敌机,速度不同,然后用精灵组收录两个敌机,上述代码是在在主程序的初始化里面实现的,然后再在循环里对精灵组进行设置,就可以对每个精灵进行操作了
下面的代码是在循环里执行的
# 让精灵组调用两个方法
# update--让精灵组中的所有精灵都更新位置
enemy_group.update()
# draw--在screen上绘制所有的精灵
enemy_group.draw(screen)
update和draw方法都是精灵组自带的,所以可以直接使用,由于我们没有设置敌机的位置,所以默认是从(0,0)即左上角向下移动
框架搭建
上面我们理清了所有游戏内容板块的实现,现在我们正式开始搭建游戏框架,游戏框架会把上面所以的版块通过封装调用使代码更好看更容易理解
针对游戏初始化我们有三个功能要实现:
1.设置游戏窗口
2.创建游戏时钟
3.创建精灵,精灵组
针对游戏循环我们有五个功能要实现:
1.设置刷新频率
2.事件监听
3.碰撞检测
4.更新绘制精灵组
5.更新屏幕显示
我们新建一个plane_main.py来进行框架的构架
下面的代码就是我们大致的框架:
import pygame
import plane_sprites
class PlaneGame:
"""飞机大战主游戏"""
def __init__(self):
print("游戏初始化")
# 1.创建游戏窗口,size是一个元组包含了宽度和高度
self.screen = pygame.display.set_mode(plane_sprites.SCREEN_RECT.size)
# 2.创建游戏时钟
self.clock = pygame.time.Clock()
# 3.调用私有方法,创建精灵和精灵组
self.__create_sprites()
def __create_sprites(self):
pass
def start_game(self):
print("游戏开始")
while True:
# 1.设置刷新帧率
self.clock.tick(plane_sprites.FRAME_PER_SEC)
# 2.事件监听
self.__event_handler()
# 3.碰撞检测
self.__check_collide()
# 4.更新绘制精灵组
self.__update_sprites()
# 5.更新显示
pygame.display.update()
pygame.event.get()
# 事件监听
def __event_handler(self):
for event in pygame.event.get():
# 判断是否退出游戏
if event.type == pygame.QUIT:
self.__game_over()
# 碰撞检测
def __check_collide(self):
pass
# 更新绘制精灵组
def __update_sprites(self):
pass
# 退出游戏
def __game_over(self):
print("游戏结束...")
pygame.quit()
exit()
if __name__ == '__main__':
# 创建游戏对象
game = PlaneGame()
# 启动游戏
game.start_game()
这里主窗口的大小和刷新的频率我们不用常量替代,方便我们以后可以调整大小,两个常量我们在plane_sprites.py文件下定义;
循环里的四个方法我用了私有方法形式表现
背景图像的滑动
在游戏中我们想让飞机在移动的时候能够实现背景图不断变换,就像坐火车一样,人在火车上,以人为参照物,我们人不动,窗外的景象不断向后移动,就造成了人在不断往前行进的样子,我们试想一下,如果战机不动,背景图不断向下滑动,是不是就能造成飞机在不断往前的效果呢,下面我们就来实现这种效果,我们用两张背景图拼成一张长图,然后每当第一张划过屏幕,滑到底之后,又重新拼接在第二张的上面,然后不断划过和重新拼接,就形成了飞机不断往前的假象。
首先我们在plane_sprites.py文件里新增一个背景精灵类
class Background(GameSprite):
"""游戏背景精灵"""
def update(self):
# 调用父类的方法实现精灵向下移动
super().update()
# 判断是否移出屏幕,如果移出屏幕,将图像设置到屏幕的上方
if self.rect.y >= SCREEN_RECT.height:
self.rect.y = -self.rect.height
这里背景精灵继承了敌机精灵类的update()方法,就是更新位置让敌机和背景图不断向下移动,一个判断语句来判读是否超过屏幕,如果移动到屏幕的最底部就会重新在上面移动下来,和上面的英雄战机移动到最上面从最小面钻出来类似,可以参考一下。背景精灵类解决后,现在去主程序plane_main.py对背景精灵进行调用,更新和绘制
# 创建精灵
def __create_sprites(self):
# 创建背景精灵
background1 = plane_sprites.Background("./飞机大战/images/background.png")
background2 = plane_sprites.Background("./飞机大战/images/background.png")
# 让背景2处于第一张背景的上面
background2.rect.y = -background2.rect.height
self.back_group = pygame.sprite.Group(background1, background2)
我们直接在主程序里的创建精灵方法创建两个背景精灵,然后设置第二个背景精灵的y值在第一个背景精灵的上面,然后不断滑动就能实现了,不要忘了要在循环里的更新绘制精灵里对精灵进行更新和绘制
# 更新绘制精灵组
def __update_sprites(self):
self.back_group.update()
self.back_group.draw(self.screen)
敌机
我们先来自己设置敌机出现的规律:
1.每隔一秒出现一架敌机
2.每架敌机向屏幕下方飞行,飞行速度各不相同
3.每架敌机出现的水平位置也不相同
4.当敌机从下方飞出后,不会再飞到屏幕中
我们针对上面的规律逐一实现功能
定时器
针对游戏中每隔一秒出现敌机,我们可以使用pygame自带的定时器,使用
pygame.time.set_timer()来添加定时器,所谓定时器就是每隔一段时间去执行一段操作。下面是定时器操作的步骤,套路非常固定
1.定义定时器常量–eventid
2.在初始化方法中,调用set_timer()方法设置定时器事件
3.在游戏循环中,监听定时器事件
1.下面这段代码在plane_sprites.py内定义,定义一个eventid
# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
2.在主程序初始化方法中,设置定时器事件
# 设置定时器事件——创建敌机
pygame.time.set_timer(plane_sprites.CREATE_ENEMY_EVENT, 1000)
3.在监听事件里判断是否满足,满足就输出敌机
if event.type == pygame.QUIT:
self.__game_over()
elif event.type == plane_sprites.CREATE_ENEMY_EVENT:
print("敌机出场")
创建敌机
1.在__create_sprites中添加敌机精灵模组,敌机是定时被创建的,因此在初始化方法中,不需要创建敌机
2.在__event_handler创建敌机,并且添加到精灵组中,调用精灵组的add方法可以向精灵组添加精灵
3.在__update_sprites里,让敌机精灵组调用update和draw方法进行更新和绘制
1.在plane_sprites.py文件里创建敌机精灵类
class Enemy(GameSprite):
"""敌机精灵"""
def __init__(self):
# 1.调用父类方法,创建敌机精灵,同时指定敌机图片
super().__init__('./飞机大战/images/enemy1.png')
# 2.指定敌机的初始随机速度
# 3.指定敌机的初始随机位置
def update(self):
# 1.调用父类方法,保持垂直方向飞行
super().update()
# 2.判断是否飞出屏幕,如果是,需要从精灵组删除
if self.rect.y >= SCREEN_RECT.height:
print("飞出屏幕")
在__create_sprites中添加敌机精灵组
# 创建敌机的精灵组
self.enemy_group = pygame.sprite.Group()
2.在监听事件中创建敌机精灵以及添加到精灵组中
if event.type == pygame.QUIT:
self.__game_over()
elif event.type == plane_sprites.CREATE_ENEMY_EVENT:
print("敌机出场")
# 创建敌机精灵
enemy = plane_sprites.Enemy()
# 将敌机精灵添加到敌机精灵组
self.enemy_group.add(enemy)
3.在__update_sprites里,让敌机精灵组调用update和draw方法进行更新和绘制
self.enemy_group.update()
self.enemy_group.draw(self.screen)
随机敌机位置和速度
听到随机我们就应该要想到random模块,使用random来让敌机出现的位置随机,我们先在plane_sprites.py文件中导入random模块,然后在敌机精灵类中的初始化方法中进行操作
def __init__(self):
# 1.调用父类方法,创建敌机精灵,同时指定敌机图片
super().__init__('./飞机大战/images/enemy1.png')
# 2.指定敌机的初始随机速度
self.speed = random.randint(2, 4)
# 3.指定敌机的初始随机位置
self.rect.bottom = 0
max_x = SCREEN_RECT.width - self.rect.width
self.rect.x = random.randint(0, max_x)
我们要让敌机从上面逐渐飞出来可以用pygame中rect的bottom方法来使敌机飞出来的时候更真实,也可以用rect.y = -rect.height,都能实现。
敌机的横坐标位置,我们就用屏幕长度减去飞机的width就是横坐标的最大值
销毁敌机
当敌机移出屏幕后一定要对敌机进行销毁,否则会造成内存浪费
销毁敌机我们可以使用精灵类自带的kill()功能,判断飞出后直接使用kill()功能进行销毁
下面的代码在创建敌机精灵类的update方法中实现
# 2.判断是否飞出屏幕,如果是,需要从精灵组删除
if self.rect.y >= SCREEN_RECT.height:
print("飞出屏幕")
# kill方法可以将精灵从所以精灵组中移出,精灵就会被销毁
self.kill()
英雄飞机
英雄飞机需求如下:
1.英雄飞机启动后,出现在屏幕水平中间位置,距离底部120像素
2.英雄每隔0.5秒发射一次子弹,每次连发3枚子弹
3.英雄默认不会移动,需要通过方向键控制水平移动
英雄飞机类
首先我们先在 plane_sprites.py文件里创建一个英雄飞机的类,并设置它的位置和速度,由于飞机默认静止,所以设置它的速度为0
class Hero(GameSprite):
"""英雄精灵"""
def __init__(self):
# 1.调用父类方法,设置image和speed
super().__init__("./飞机大战/images/me1.png", speed=0)
# 2.设置英雄的初始位置
self.rect.centerx = SCREEN_RECT.centerx
self.rect.bottom = SCREEN_RECT.bottom - 120
然后在__create_sprites方法里创建英雄飞机的精灵和精灵组,这里需要单独把英雄设置成属性,为后面碰撞检测做准备,以及到时候直接调用发射子弹的方法
# 创建英雄飞机的精灵和精灵组
self.hero = plane_sprites.Hero()
self.hero_group = pygame.sprite.Group(self.hero)
最后在__update_sprites方法里更新和绘制英雄飞机
self.hero_group.update()
self.hero_group.draw(self.screen)
现在就能游戏窗口就能显示敌机和英雄飞机了
移动英雄飞机
移动飞机我们采用pygame自带的使用键盘提供的方法获取键盘按键,获取到按键元组,然后用元组索引进行判断是否满足左右键,然后进行相应的移动操作
我们先在英雄精灵类里重写update方法,因为父类update方法是上下移动,英雄飞机是左右移动
def update(self):
# 英雄在水平反向移动,所以不能调用父类的update方法
self.rect.x += self.speed
然后再在主程序事件监听__event_handler方法增加键盘识别方法,这里我设置为每次按左右键都移动
2个像素,可以根据自己喜好设置,然后其他键都不移动,所以设置为0
# 使用键盘提供的方法获取键盘按键 - 按键元组
key_pressed = pygame.key.get_pressed()
# 判断元组中对应的按键索引值 1
if key_pressed[pygame.K_RIGHT]:
self.hero.speed = 2
elif key_pressed[pygame.K_LEFT]:
self.hero.speed = -2
else:
self.hero.speed = 0
这里我们还要设置一个移动范围,不能让英雄飞机移动到外面,这里我们在英雄飞机类的update方法实行,一移出我们就令它的x等于边界值,这样就不能移出了
def update(self):
# 英雄在水平反向移动,所以不能调用父类的update方法
self.rect.x += self.speed
# 控制英雄不能离开屏幕
if self.rect.x < 0:
self.rect.x = 0
elif self.rect.right > SCREEN_RECT.right:
self.rect.right = SCREEN_RECT.right
发射子弹
我们让子弹没0.5秒发射一次,这里我们就要用定时器来实现这个功能,这里可以参考创建敌机的方法,我就不演示了,我们直接创建一个子弹类,来对子弹进行创建更新和绘制
class bullet(GameSprite):
"""子弹精灵"""
def __init__(self):
# 调用父类方法,设置子弹图片,设置初始速度
super().__init__("./飞机大战/images/bullet1.png", speed=-2)
def update(self):
# 调用父类方法,让子弹垂直向下飞行
super().update()
# 判断子弹是否飞出屏幕
if self.rect.bottom < 0:
self.kill()
def __del__(self):
print("子弹被销毁")
之后再和敌机一样进行更新绘制到屏幕上,位置可以设置到英雄战机的头部偏上一点
子弹摧毁敌机
子弹摧毁敌机比较简单,pygame为我们提供了pygame.sprites.groupcollide()方法,我们直接将两个发生碰撞的事物放入方法里就行了,注意传入的两个变量一定时精灵组。
# 碰撞检测
def __check_collide(self):
# 1.子弹摧毁敌机
pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)
第一个True是子弹碰撞后是否消失,True是消失,相反False是不消失,同理后面的True是敌机消失
敌机碰撞英雄飞机
这里我们要用到pygame提供的一种新方法和pygame.sprites.groupcollide()类型的pygame.sprite.spritecollide()方法,这个方法能传入一个精灵和精灵组,返回一个敌机列表,然后判断是否销毁,这个方法只能是精灵组销毁,精灵不能销毁,那么如何使精灵即英雄战机销毁呢,我们可以判断返回列表里是否有值,如果有的话,表示英雄飞机和敌机发生碰撞,那么直接用kill()销毁英雄战机即可
# 碰撞检测
def __check_collide(self):
# 1.子弹摧毁敌机
pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)
# 2.敌机摧毁英雄飞机
enemies = pygame.sprite.spritecollide(self.hero, self.enemy_group, True)
# 3.判断是否列表有内容
if len(enemies) > 0:
# 让英雄牺牲
self.hero.kill()
# 结束游戏
PlaneGame.__game_over()
源码展示
下面是plane_main.py文件下的代码:
import pygame
import plane_sprites
class PlaneGame:
"""飞机大战主游戏"""
def __init__(self):
print("游戏初始化")
# 1.创建游戏窗口,size是一个元组包含了宽度和高度
self.screen = pygame.display.set_mode(plane_sprites.SCREEN_RECT.size)
# 2.创建游戏时钟
self.clock = pygame.time.Clock()
# 3.调用私有方法,创建精灵和精灵组
self.__create_sprites()
# 4.设置定时器事件
pygame.time.set_timer(plane_sprites.CREATE_ENEMY_EVENT, 1000)
pygame.time.set_timer(plane_sprites.HERO_FIRE_EVENT, 150)
# 创建精灵
def __create_sprites(self):
# 创建背景精灵
background1 = plane_sprites.Background()
background2 = plane_sprites.Background(True)
self.back_group = pygame.sprite.Group(background1, background2)
# 创建敌机的精灵组
self.enemy_group = pygame.sprite.Group()
# 创建英雄飞机的精灵和精灵组
self.hero = plane_sprites.Hero()
self.hero_group = pygame.sprite.Group(self.hero)
def start_game(self):
print("游戏开始")
while True:
# 1.设置刷新帧率
self.clock.tick(plane_sprites.FRAME_PER_SEC)
# 2.事件监听
self.__event_handler()
# 3.碰撞检测
self.__check_collide()
# 4.更新绘制精灵组
self.__update_sprites()
# 5.更新显示
pygame.display.update()
pygame.event.get()
# 事件监听
def __event_handler(self):
for event in pygame.event.get():
# 判断是否退出游戏
if event.type == pygame.QUIT:
self.__game_over()
elif event.type == plane_sprites.CREATE_ENEMY_EVENT:
# print("敌机出场")
# 创建敌机精灵
enemy = plane_sprites.Enemy()
# 将敌机精灵添加到敌机精灵组
self.enemy_group.add(enemy)
elif event.type == plane_sprites.HERO_FIRE_EVENT:
self.hero.fire()
# 使用键盘提供的方法获取键盘按键 - 按键元组
key_pressed = pygame.key.get_pressed()
# 判断元组中对应的按键索引值 1
if key_pressed[pygame.K_RIGHT]:
self.hero.speed = 2
elif key_pressed[pygame.K_LEFT]:
self.hero.speed = -2
else:
self.hero.speed = 0
# 碰撞检测
def __check_collide(self):
# 1.子弹摧毁敌机
pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)
# 2.敌机摧毁英雄飞机
enemies = pygame.sprite.spritecollide(self.hero, self.enemy_group, True)
# 3.判断是否列表有内容
if len(enemies) > 0:
# 让英雄牺牲
self.hero.kill()
# 结束游戏
PlaneGame.__game_over()
# 更新绘制精灵组
def __update_sprites(self):
self.back_group.update()
self.back_group.draw(self.screen)
self.enemy_group.update()
self.enemy_group.draw(self.screen)
self.hero_group.update()
self.hero_group.draw(self.screen)
self.hero.bullets.update()
self.hero.bullets.draw(self.screen)
# 退出游戏
def __game_over(self):
print("游戏结束...")
pygame.quit()
exit()
if __name__ == '__main__':
# 创建游戏对象
game = PlaneGame()
# 启动游戏
game.start_game()
下面是plane_sprites.py文件里的代码:
import random
import pygame
# 定义一个主窗口大小的常量
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
# 刷新的帧率
FRAME_PER_SEC = 60
# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
# 创建英雄飞机法神子弹事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1
class GameSprite(pygame.sprite.Sprite):
"""飞机大战游戏精灵"""
def __init__(self, image_name, speed=1):
# 调用父类的初始化方法,如果不调用就会对父类的init方法进行重写,就不能用父类的方法了
super().__init__()
# 定义对象的属性
# 加载图片
self.image = pygame.image.load(image_name)
# 返回图像大小
self.rect = self.image.get_rect()
# 速度
self.speed = speed
def update(self):
# 在屏幕的垂直方向移动
self.rect.y += self.speed
class Background(GameSprite):
"""游戏背景精灵"""
def __init__(self, is_alt=False):
# 调用父类的方法进行精灵的创建
super().__init__("./飞机大战/images/background.png")
# 判断是否是交替图像
if is_alt:
self.rect.y = -self.rect.height
def update(self):
# 调用父类的方法实现精灵向下移动
super().update()
# 判断是否移出屏幕,如果移出屏幕,将图像设置到屏幕的上方
if self.rect.y >= SCREEN_RECT.height:
self.rect.y = -self.rect.height
class Enemy(GameSprite):
"""敌机精灵"""
def __init__(self):
# 1.调用父类方法,创建敌机精灵,同时指定敌机图片
super().__init__("./飞机大战/images/enemy1.png")
# 2.指定敌机的初始随机速度
self.speed = random.randint(2, 4)
# 3.指定敌机的初始随机位置
self.rect.bottom = 0
max_x = SCREEN_RECT.width - self.rect.width
self.rect.x = random.randint(0, max_x)
def update(self):
# 1.调用父类方法,保持垂直方向飞行
super().update()
# 2.判断是否飞出屏幕,如果是,需要从精灵组删除
if self.rect.y >= SCREEN_RECT.height:
# print("飞出屏幕")
# kill方法可以将精灵从所以精灵组中移出,精灵就会被销毁
self.kill()
def __del__(self):
# print('精灵被销毁')
pass
class Hero(GameSprite):
"""英雄精灵"""
def __init__(self):
# 1.调用父类方法,设置image和speed
super().__init__("./飞机大战/images/me1.png", speed=0)
# 2.设置英雄的初始位置
self.rect.centerx = SCREEN_RECT.centerx
self.rect.bottom = SCREEN_RECT.bottom - 120
# 3.创建子弹精灵组
self.bullets = pygame.sprite.Group()
def update(self):
# 英雄在水平反向移动,所以不能调用父类的update方法
self.rect.x += self.speed
# 控制英雄不能离开屏幕
if self.rect.x < 0:
self.rect.x = 0
elif self.rect.right > SCREEN_RECT.right:
self.rect.right = SCREEN_RECT.right
def fire(self):
# print("发射子弹...")
for i in (0, 1, 2):
# 1.创建子弹精灵
bullet = Bullet()
# 2.设置精灵的位置
bullet.rect.bottom = self.rect.y - i * 20
bullet.rect.centerx = self.rect.centerx
# 3.将精灵添加到精灵组
self.bullets.add(bullet)
class Bullet(GameSprite):
"""子弹精灵"""
def __init__(self):
# 调用父类方法,设置子弹图片,设置初始速度
super().__init__("./飞机大战/images/bullet1.png", speed=-4)
def update(self):
# 调用父类方法,让子弹垂直向上飞行
super().update()
# 判断子弹是否飞出屏幕
if self.rect.bottom < 0:
self.kill()
def __del__(self):
# print("子弹被销毁")
pass
总结
飞机大战项目还是有点难度,算是对python基础和高级语法的一次总结,如果感兴趣的可以对此项目进行升级改造,比如增加敌机被子弹击落后呈现爆炸的功能等等,上面的素材文件里面包含了很多素材,不止上述所用到的素材,感兴趣的可以去试试