飞机大战--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 = 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 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之后再和敌机一样进行更新绘制到屏幕上,位置可以设置到英雄战机的头部偏上一点
子弹摧毁敌机
子弹摧毁敌机比较简单,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 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总结
飞机大战项目还是有点难度,算是对python基础和高级语法的一次总结,如果感兴趣的可以对此项目进行升级改造,比如增加敌机被子弹击落后呈现爆炸的功能等等,上面的素材文件里面包含了很多素材,不止上述所用到的素材,感兴趣的可以去试试