博主在哔哩哔哩上学习了黑马程序员的python教程,并且完成了老师讲的项目实战,为了巩固知识点通过这篇博客来记录一下。
目录
1. 创建python项目+pygame模块下载
1.1 创建项目
1.2 pygame下载
2.导入模块+创建游戏窗口
2.1导入模块
2.2 创建游戏窗口
3 添加游戏背景
3.1 创建精灵父类+背景精灵类
3.2 背景精灵类
3.3 展示背景
3.4背景图片动画
3.5设置时钟和帧率
4.敌机出现
4.1单个敌机出现
4.2多个敌机出现
4.2.1敌机定时器事件
4.2.2事件监听【按键退出+监听敌机定时器事件】
4.2.3随机速度和位置
5.飞机出现+键盘操控
5.1飞机出现
5.2 键盘控制飞机水平移动
5.3防止飞机溢出屏幕
6.飞机发射子弹
6.1 子弹位置逻辑
6.2子弹定时器事件监听
6.3 子弹飞出屏幕销毁
7.检查碰撞
7.1子弹敌机碰撞
7.2飞机敌机碰撞
8.资源分享
首先飞机大战中包含2个python文件,1个images的文件夹。
plane_main.py就是飞机大战的主程序,plane_sprites则是定义精灵类,方便plane_main.py主程序直接导入。
博主先画了一个思维导图来滤清思路每一个py文件的每一个类都需要定义哪些内容。
接下来就开始细化整个飞机大战的内容,开始编写代码吧!
打开pycharm创建一个新的python项目,我讲它命令为hm_plane,并将游戏中所需要的图片文件夹复制到项目中,并创建plane_main.py和plane_sprites.py这两个python文件。(图片和代码会在本文末尾给出)
在项目最下面点击Terminal然后进行pygame安装。(首先需要安装pip,pip安装方法我在另一个博客中有写Matplotlib不显示中文解决办法_不拘于时.的博客-CSDN博客)
pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple/
等待一会后显示成功安装。
想做一个游戏,首先就需要把pygame的模块导入,先给2个py文件进行模块导入。
import pygame
from plane_sprites import *
import pygame
先在plane_main中创建一个PlaneGame类,一般游戏的一些内容初始化的时候就应该有,如:游戏窗口、窗口背景等。
我们先把游戏的窗口展示出来,游戏的窗口使用的是pygame.display.set_mode(),括号里需要填写游戏窗口的大小即矩形元素。游戏窗口的大小是根据背景图的像素决定的,游戏中用到的背景图是480*700,由于这个大小是固定的那我们就在plane_sprites中定义一个静态常量去存储这个矩形元素来直接调用。
pygame.Rect(x,y,width,height),其中x和y是左上角的坐标,向右x增加,向下y增加,width和height就是窗口的宽和高。
SCREEN_RECT = pygame.Rect(0,0,480,700)
回到主程序中,完善窗口代码,set_mode()括号中需要知道长宽,SCREEN_RECT.size就代表矩形元素中的长宽。
初始化游戏窗口后还需要展示,那什么时候才会展示呢?就是开始游戏的时候,因此我们还要设置一个开始游戏的方法来更新显示。开始游戏这一方法需要一直执行,所以通过while True来判断。什么时候结束游戏?飞机撞毁、手动退出的时候,这两种情况后续再说,所以暂时开始游戏这一方法是一直运行的。
class PlaneGame(object):
def __init__(self):
print("游戏初始化")
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
def start_game(self):
while True:
pygame.display.update()
这时,我们还需要在最底部创建几行代码来创建一个属于PlaneGame的对象,要不然又怎么运行start_game呢?
if __name__ == '__main__':
# 创建游戏对象
game = PlaneGame()
# 启动游戏
game.start_game()
右键运行plane_main就会出现一个黑色的游戏窗口,因为我们此时还没有设置游戏窗口。
在游戏开发中,在游戏中显示的图片,皆可称为精灵。通常,精灵表示游戏中所有运动的部分。
绘制图片有三要素,分别是:图片加载到内存、确定位置、更新图片。
我们在plane_sprites中,先创建一个GameSprite类。由于飞机大战中的图片都是运动的还需要定义一个update方法来修改各个图片的y坐标位置,y+=speed就说明图片会垂直向下运动。
class GameSprite(pygame.sprite.Sprite):
def __init__(self,image_name,speed =1):
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
之前创建的GameSprite是一个父类,我们创建背景精灵类,只需要继承GameSprite然后写上对应的图片路径和速度即可。
class Background(GameSprite):
def __init__(self):
super().__init__("./images/background.png")
def update(self):
super().update()
接下来的问题是,背景精灵类创建完成,我们怎么创建一个背景精灵类的对象又要怎么将背景展示?
要想创建对象就需要在plane_main中的PlaneGame中建立一个私有方法__create_prites来创建背景对象,同时需要把对象添加进背景精灵组。
def __create_sprite(self):
bg = Background()
self.back_group = pygame.sprite.Group(bg)
背景精灵对象创建完成,接下来就是更新,那么我们需要在plane_main中的PlaneGame中建立一个私有方法__update_prites来更新精灵组,并将其画在屏幕上。
def __update_sprites(self):
self.back_group.update()
self.back_group.draw(self.screen)
上述两个私有方法__create_sprites __update_sprites已经创建,但是我们并没有调用,因此需要在PlaneGame初始化方法中调用__create_sprite方法,在__start_game中调用__update_sprites
self.__create_sprite()
self.__update_sprites()
这时候运行主程序就会发现背景图已经显示出来了,但是随着背景图片移动有背景图案已经消失。
老师给的案例中整个背景是一直移动的,就出来了一个问题:当图片移出游戏窗口时怎么回到原位继续移动?答案就是通过两个一样的图片一起移动并回到原始位置,产生一种图片一直向下移动的错觉。
再设置一个背景图片精灵bg2,把bg2的y值设为图片高度的负值,这样最开始我们看到的还是bg,但是随着bg移动,我们就会看到移动下来的bg2。当我们看到的完全是bg2时再把bg的y值设为图片高度的负值,一直循环。
那么我们怎么判断背景精灵的位置呢?我们将初始化方法再设置一个变量is_alt,如果is_alt是True,说明精灵位置为-height,再用if判断精灵高度是否需要调整。
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
再到主程序中修改一下__create_sprites,增加bg2并添加进精灵组。将bg2的is_alt设置成True,这样它最初y的位置就是-height。
def __create_sprite(self):
bg = Background()
bg2 = Background(is_alt=True)
self.back_group = pygame.sprite.Group(bg,bg2)
但是这样背景动画会看起来特别快,为了让动画看起来更加平滑,所以我们需要创建游戏时钟设置帧率。
我们将每秒帧率设为60,这也是个常量,所以也写在plane_sprites中。
FRAME_PER_SEC = 60
然后在主程序的初始化方法中创建游戏时钟,再在__start_game中设置游戏的帧率。
self.clock = pygame.time.Clock()
self.clock.tick(FRAME_PER_SEC)
再次运行背景动画就显得平滑许多了!
飞机大战中,敌机应该同时出现很多个,为了简单操作,我们先让一个敌机从左上角出现并滑行。
首先在plane_sprites中设置敌机的精灵类,重写__init__、update函数。
为了看懂显著看出敌机也是在运动的,我们把其速度设置为和背景图片速度不一样的值,我将其设置为2.
class Enemy(GameSprite):
def __init__(self):
super().__init__("./images/enemy1.png",2)
def update(self):
super().update()
然后在主程序的__create_sprites中创建敌机和精灵组,在__update_sprites中更新和绘制后,敌机即可显示出来。
enemy = Enemy()
self.enemy_group = pygame.sprite.Group(enemy)
self.enemy_group.update()
self.enemy_group.draw(self.screen)
运行主程序后就会有一个敌机从左上角出现了!
同一事件段出现多个敌机,可以使用定时器事件,当定时器触发时,就生成一个敌机。
在plane_sprites中创建敌机定时器常量。
CREATE_ENEMY_EVENT = pygame.USEREVENT
接下来在plane_main的__init__中创建生成敌机的定时器事件,假设每1s生成1个敌机,单位为毫秒,所以最后一个参数是1000.
pygame.time.set_timer(CREATE_ENEMY_EVENT,1000)
那么,怎么判断定时器事件的发生呢?这样就需要进行事件监听。如果监测到了敌机定时器事件,就会创建一个敌机。
在主程序的PlaneGame中定义一个__event_handler类,用来监听各种事件,捕捉事件用到pygame.event.get().
由于事件可能有很多种,所以用for in 进行事件的判断,首先我们来设置点击关闭按钮来关闭程序。关闭程序的操作方法是固定的,所以我们先来定义一个静态方法来封装退出游戏的步骤。
@staticmethod
def __game_over():
pygame.quit()
exit()
接下来定义__event_handler. 其中,退出按钮的类别是pygame.QUIT
def __event_handler(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.__game_over()
定义在事件监听了,但是还要调用,所以当__start_game运行时,就应该进行事件监听。
self.__event_handler()
然后再判断敌机定时器的发生。
elif event.type == CREATE_ENEMY_EVENT:
enemy = Enemy()
self.enemy_group.add(enemy)
注意,这个时候要把之前创建敌机精灵组前的创建一个敌机的代码删掉,因为和上述代码重复了,再把括号中的enemy删掉,因为enemy已经不存在了。
但是这个时候运行程序会每个一秒在相同的位置产生一辆敌机,这显然是不符合逻辑的,因此我们还需要在设置一下敌机的初始位置和速度。
在plane_sprites中,在Enemy类中修改。为了游戏效果,我们选择随机敌机的位置和速度,需要导入random模块。
import random
接下来就是在Enemy类中随机初始位置和速度,用到了random.randint(),因为敌机的要在屏幕里出现,所以要设置范围边界。同时,速度也是同理,所以把速度设置为1~3
这里把调用父类的__init__的速度参数删除。
def __init__(self):
super().__init__("./images/enemy1.png")
max_x = SCREEN_RECT.width - self.rect.width
self.rect.x = random.randint(0,max_x)
self.speed = random.randint(1,3)
但是这个时候敌机的飞出动作很生硬,为了显得更自然,把敌机的y值设为负数,这样在我们看不见的地方开始起飞,再出现就显得平滑许多。
4.2.4飞出屏幕销毁敌机
当敌机被子弹销毁或是飞出屏幕的时候就应该被删除,先来设计当敌机飞出屏幕时如何销毁。
首先就是判断敌机的y的位置,如果大于屏幕长度就应该被销毁,使用kill()杀死,再调用一下__del__。
def update(self):
super().update()
if self.rect.y > SCREEN_RECT.height:
self.kill()
def __del__(self):
print("敌机销毁")
再次运行程序,当敌机飞出屏幕时就会出现“敌机销毁”字样。
首先在plane_sprites创建一个Hero类,考虑飞机的位置:他不会主动垂直移动所以速度是0.水平位置应该在屏幕的居中位置,居中位置用到centerx属性。
class Hero(GameSprite):
def __init__(self):
super().__init__("./images/me1.png",0)
self.rect.centerx = SCREEN_RECT.centerx
self.rect.bottom = SCREEN_RECT.bottom - 120
def update(self):
super().update()
再和之前敌机、背景一样,在主程序__create_sprites中创建精灵,并在__update_sprites更新。
self.hero = Hero()
self.hero_group = pygame.sprite.Group(self.hero)
self.hero_group.update()
self.hero_group.draw(self.screen)
运行程序,飞机就出现了!
这时候就需要用到事件监听了,需要用event.type判断,键盘按的是↑还是↓。
首先定义一个keys_pressed变量用来接收pygame.key.get_pressed()的元组,这个函数是将对应按键的值设为1,其他为0。接下来用if判断是否是左或右并改变对应的x的值。
keys_pressed = pygame.key.get_pressed()
if keys_pressed[pygame.K_RIGHT]:
self.hero.rect.x += 2
elif keys_pressed[pygame.K_LEFT]:
self.hero.rect.x += -2
但是飞机有可能左右移动会移出游戏窗口,所以我们应该对其进行限制。
if self.rect.x < 0:
self.rect.x = 0
elif self.rect.right >SCREEN_RECT.right:
self.rect.right = SCREEN_RECT.right
这时候运行主程序,按下小键盘的左右移动键就可以移动飞机了!
首先在create_sprites创建一个Bullet类,因为子弹是往上飞的所以速度应该为负数。其他有关子弹的代码在Hero类体现,因为飞机要发射子弹,所以在Hero中定义一个fire()方法,并在Hero的__init__中创建子弹精灵组。
class Bullet(GameSprite):
def __init__(self):
super().__init__("./images/bullet1.png",-2)
def update(self):
super().update()
因为子弹从属于飞机,所以在Hero的__init__中建立子弹精灵组。
self.bullets = pygame.sprite.Group()
假设飞机发射子弹时是一下发了3枚子弹,可以用for循环建立bullet()子弹精灵,为了让子弹离飞机不是很近,让子弹的bottom属于离飞机y有一定距离,再把子弹精灵添加进子弹精灵组。
def fire(self):
for i in (0,1,2):
bullet = Bullet()
bullet.rect.centerx = self.rect.centerx
bullet.rect.bottom = self.rect.y - i * 20
self.bullets.add(bullet)
接下来在plane_main中创建子弹组和在__update_sprites中更新。
self.bullet_group.update()
self.bullet_group.draw(self.screen)
这时候还没有完善,因为一直没有调用fire(),我们定义一个发射子弹的定时器来调用fire()
首先在plane_sprites中设置子弹发射定时器事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1
在plane_main中将子弹发射设置为每0.5s一次
pygame.time.set_timer(HERO_FIRE_EVENT,500)
在__event_handler中判断是否是子弹发射事件,如果是调用fire()
elif event.type == HERO_FIRE_EVENT:
self.hero.fire()
销毁子弹与销毁敌机是一样的道理,在plane_sprites中判断子弹的y值是否飞出屏幕如果是,就销毁。
def update(self):
super().update()
if self.rect.y < 0:
self.kill()
def __del__(self):
print("子弹销毁")
接下来运行程序,子弹就顺利的跟着飞机走了!子弹的逻辑比较复杂,我在再次编写的时候遇到了创建精灵、精灵组、添加精灵的种种问题,希望大家注意。
在plane_main中定义一个__check_collide方法进行碰撞检测。
pygame.sprite.groupcollide(x,y,z,w)的意思分为是当x和y撞到时,x进行z(True销毁),y进行w。
pygame.sprite.groupcollide(self.hero.bullets,self.enemy_group,True,True)
使用spritecollide时会返回一个数,如果返回是数就说明碰撞,那么就调用之前设置好的__game_over.
enemies = pygame.sprite.spritecollide(self.hero,self.enemy_group,True)
if len(enemies):
self.__game_over()
此时飞机大战的项目就已经全局结束啦!记录一下这么长时间的python学习成果!
两个py文件和一个images文件夹资源:
链接:https://pan.baidu.com/s/1wEAraFTm8jRjgiHqOX7r2g?pwd=mcps
提取码:mcps
cmd进入存储位置后输入:python plane_main.py即可玩游戏啦。