Python学习笔记 | 使用pygame模块编写飞机大战游戏(三)

Python学习笔记 | 使用pygame模块编写飞机大战游戏(三)

首页休闲益智飞机碰撞大战更新时间:2024-05-07

通过前面的体验性代码,我们已经理清了飞机大战游戏的编写思路和方法,现在开始按照面向对象编程的理念,重新设计游戏框架。框架搭起来后,再向相应模块中添加实现代码,逐步完善游戏功能。

一、项目文件及框架介绍

整个飞机大战游戏项目一共包含两个py文件和一个images图片素材文件夹,两个文件分别为plane_sprites.py和plane_main.py。

1、plane_sprites.py文件

这是一个模块文件,在主文件中导入使用,它包含了两个自定义类和主程序使用的常量,基本情况如下:

代码如下:

import pygame # 定义屏幕区域常量对象,常量的size属性就是屏幕的宽和高元组 SCREEN_RECT = pygame.Rect(0, 0, 500, 800) # 定义刷新率常量 FRAME = 60 # 定义精灵类的派生类GameSprite,命名为游戏精灵 class GameSprite(pygame.sprite.Sprite): # 重写精灵类的__init__方法 # 参数image_name用来接收要显示的图片地址 # 参数speed用来接收移动速度,默认值为1,其实就是原来我们设定的移动步长 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 # 定义精灵类的update方法 def update(self): # 因为敌机都是纵向移动,因此每次纵坐标都加上speed self.rect.y = self.speed

2、plane_main.py文件——框架

这是游戏的主文件,所有的功能实现都在这个文件中。项目的主体框架是文件中定义的PlaneGame类,我们把整体项目按照功能砍成块,再把功能模块放到主体框架PlaneGame类中,由PlaneGame类的私有方法去实现对应的功能。主体框架如下:

PlaneGame类:

# 事件监听私有方法 def __event_handle(self): # 获取全部事件,并遍历判断 for event in pygame.event.get(): # 如果是点击退出按钮事件,则调用__game_over方法退出游戏 if event.type == pygame.QUIT: self.__game_over()

# 游戏结束静态方法 # 静态方法没有参数传递,不需要实例化,可以直接调用,主要用来存放逻辑性代码 # 静态方法存在的意义其实就是让函数归类,紧耦合。 @staticmethod def __game_over(): pygame.quit() exit()

# 开始游戏方法 def start_game(self): while True: # 设置刷新帧率 self.clock.tick(FRAME) # 事件监听 self.__event_handle() # 碰撞检测 self.__check_collide() # 更新/绘制精灵组 self.__update_sprites() # 更新显示 pygame.display.update()

以上就是飞机大战项目的整体框架和文件构成,到这里就已经把框架建立起来了,以后的工作就是逐步向窗口里添加滚动背景、己方飞机和敌方飞机等元素,也就是要不断向各个功能模块(私有方法)中添加代码。


二、添加滚动背景

游戏中要让背景图片不间断地向下方无缝滚动,产生己方飞机向上飞行的效果,这就必须有两个相同的背景图片(背景1和背景2,特意重新制作了一个背景图片,为了上下边缘易于对接),背景1正常显示在窗口区域,背景2位于窗口上方,跟背景1无缝连接,用来补位。两个背景图片同时向下移动,一个背景图片移出窗口后马上回到窗口最上面,周而复始。这项功能采用精灵和精灵组来完成,分三步来完成:

1、定义背景精灵类

打开模块文件plane_sprites.py,在里面新定义一个背景精灵类,继承游戏精灵GameSprite类,然后重写__init__和update方法。代码如下:

# 定义背景图片精灵类,继承游戏精灵GameSprite类 class BackGround(GameSprite): # 重写__init__方法 # 背景图片要实现滚动效果,必须有两个相同的背景图片,背景1和背景2循环交替滚动 # 背景1正常显示在窗口区域,背景2位于窗口上方,跟背景1无缝连接,用来补位 # 两个背景同时向下移动,一个背景图片移出窗口后马上回到窗口最上面,即把图片坐标y值设成窗口高度的负数 def __init__(self, is_alt=False): # 首先调用父类的初始化方法,传入背景图片路径 # 调用父类的初始化方法后,背景精灵的image、rect和speed就都建立了 super().__init__(r'images\background.jpg') # 参数is_aot用来确定是否为替换图片,如果为真则将该精灵的纵坐标值设成窗口高度的负数 if is_alt: self.rect.y = -SCREEN_RECT.height def update(self): super().update() if self.rect.y >= SCREEN_RECT.height: self.rect.y = -SCREEN_RECT.height

2、创建背景精灵和精灵组

背景精灵类定义好之后,还要在主文件的PlaneGame类的__create_sprites方法中创建背景精灵和精灵组,也就是要创建两个背景精灵,用于交替滚动。代码如下:

# 创建精灵私有方法 def __create_sprites(self): # 创建背景精灵和精灵组 # 创建两个背景图片精灵,用于交替显示背景 # bg1采用默认参数,即is_alt为假,表示bg1正常显示在窗口区域 bg1 = BackGround() # bg2的参数is_alt为真,表示bg2是替换背景,精灵内部会将其纵坐标自动设为窗口高度的负数 bg2 = BackGround(is_alt=True) # 将两个背景精灵加入到一个精灵组中 self.back_group = pygame.sprite.Group(bg1, bg2)

3、更新update和draw方法

背景精灵和精灵组都创建完毕后,还有一个重要的工作就是调用__update_sprites方法,让背景精灵组里面的两个背景精灵各自调用自己的update方法更新位置,再将各自的背景图片绘制到相应矩形区域内。在__update_sprites方法中添加代码如下:

# 更新/绘制精灵组私有方法 def __update_sprites(self): # 调用背景精灵组的update和draw方法 self.back_group.update() self.back_group.draw(self.screen)


三、添加己方飞机

添加并控制己方飞机同样要使用精灵和精灵组,实现步骤跟添加滚动背景差不多,首先创建精灵和精灵组,接着更新精灵组的update和draw方法,最后精灵图片就显示到游戏窗口中了。己方飞机还需要能够使用键盘来操控,所以还要在事件监听功能模块里添加按键检测处理的代码。

1、定义己方飞机精灵类

由于己方飞机具有自己独有的特点,游戏精灵GameSprite类不能够完全满足需要,因此要重新定义己方飞机精灵类,同样要继承游戏精灵GameSprite类。在模块文件plane_sprites.py中定义己方飞机类,代码如下:

# 定义己方飞机精灵类,继承游戏精灵GameSprite类 class Own_Plane(GameSprite): # 重写__init__方法 def __init__(self): # 调用父类初始化方法,搭建基础环境 # 初始speed设置为0,即飞机静止 super().__init__(r'images\plane.png', speed=0) # 飞机区域的中心位置与窗口的中心位置相同,即纵向居中 self.rect.center = SCREEN_RECT.center # 飞机区域的底部位于窗口底部向上100像素处 self.rect.bottom = SCREEN_RECT.bottom - 100 # 重写update方法 def update(self): 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

2、创建己方飞机精灵和精灵组

在主文件的PlaneGame类的__create_sprites方法中创建己方飞机精灵和精灵组,代码如下:

# 创建己方飞机精灵和精灵组 self.plane = Own_Plane() self.plane_group = pygame.sprite.Group(self.plane)

3、更新update和draw方法

在__update_sprites方法中添加代码如下:

# 更新己方飞机精灵组 self.plane_group.update() self.plane_group.draw(self.screen)

4、利用键盘事件控制飞机移动

己方飞机正常显示后,还需要使用左右方向键来控制飞机左右移动(上下移动暂不考虑),这就要在事件检测功能模块(__event_handle私有方法)里添加按键检测代码,代码如下:

# 获取所有按键状态的元组,注意返回值是元组类型 keys_pressed = pygame.key.get_pressed() # 如果左方向键被按下,则飞机的横坐标每次减2 if keys_pressed[pygame.K_LEFT]: self.plane.speed = -2 # 如果右方向键被按下,则飞机的横坐标每次加2 elif keys_pressed[pygame.K_RIGHT]: self.plane.speed = 2 # 如果这两个键都没被按下,则飞机静止 else: self.plane.speed = 0四、添加敌机

添加敌机比添加己方飞机要复杂一些,因为敌机的数量多、样式多、位置随机、速度随机。敌机的数量不是一次性生成的,而是采取定时生成的方式,游戏中设定一秒钟生成一个敌机,这就需要使用pygame提供的定时器来完成。同时,在敌机的处理上还要增加销毁操作,如果一个敌机没有被己方飞机子弹打中,又没有与己方飞机发生碰撞,而是正常飞出了窗口下方,这时就要将这个敌机精灵从精灵组中删除,也就是使用精灵类自带的kill方法*死这个敌机精灵,为的是释放内存。

1、定时器操作

pygame提供了一个定时器模块,允许自行设定时钟周期,定时产生自定义事件标识,通过检测这个事件标识来添加敌机精灵。定时器基本操作如下:

在模块文件plane_sprites.py中定义一个事件常量,让这个常量等于pygame自带的USEREVENT即可,如果直接使用pygame自带的常量也可以,但是代表意义不够明显,代码如下:

# 定义敌机定时器事件标识常量 CREATE_ENEMY_EVENT = pygame.USEREVENT

在主文件中调用定时器,设定时钟周期为1000毫秒,把上面定义的敌机定时器事件标识常量作为定时器的参数,这样每隔1秒钟就会产生一个CREATE_ENEMY_EVENT事件标识。打开主文件plane_main.py,在PlaneGame类的__init__初始化方法中添加如下代码:

# 调用定时器,每1秒钟产生一个创建敌机定时器事件常量 pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)

在__event_handle事件监听方法中的遍历event事件代码部分,添加创建敌机精灵和加入精灵组代码,如下:

# 事件监听私有方法 def __event_handle(self): # 获取全部事件,并遍历判断 for event in pygame.event.get(): if event.type == pygame.QUIT: # 如果是点击退出按钮事件,则调用__game_over方法退出游戏 self.__game_over() elif event.type == CREATE_ENEMY_EVENT: # 如果事件类型是自定义的创建敌机定时器事件,则创建敌机精灵并加入到敌机精灵组 en_plane = EnemyPlane() self.enemy_group.add(en_plane)

在模块文件和主文件中加入以上几部分代码后,就实现了定时生成敌机的功能。现在敌机精灵类还没有定义,敌机精灵组也没有创建,下面马上就来补充这两个部分。

2、定义敌机精灵类

打开模块文件plane_sprites.py,在下面定义敌机精灵类,继承游戏精灵GameSprite类,然后重写__init__和update方法。我制作了三个敌机图片,在初始化部分随机创建敌机图片文件名,同时随机设定敌机的横坐标x和速度speed,再配合上面创建的定时器,这样每秒钟就会生成一个横向位置和速度都随机的敌机。代码如下:

# 定义敌机精灵类,继承游戏精灵GameSprite类 class EnemyPlane(GameSprite): def __init__(self): # 生成1-3的随机数 num = random.randint(1, 3) # 根据随机数确定加载的敌机图片文件名,即en_plane1.png/en_plane2.png/en_plane3.png filename = r'images\en_plane{}.png'.format(num) super().__init__(filename) # 设置敌机速度是1-3之间的随机数 self.speed = random.randint(1, 3) # 设置敌机纵向的初始位置,敌机进入窗口时会非常顺滑,否则敌机进入时会有突然跳入的感觉 self.rect.bottom = 0 # 设置敌机横向的初始位置,在X轴的0到窗口宽度减去飞机宽度之间随机出现 max_x = SCREEN_RECT.width - self.rect.width self.rect.x = random.randint(0, max_x) def update(self): super().update() if self.rect.y > SCREEN_RECT.height: # 如果敌机飞出了屏幕下方,则敌机精灵自己调用kill方法将自己在所有精灵组中删除,释放内存 self.kill()

在update方法中,检测到敌机飞出窗口就使用kill方法将它*死,释放内存。

3、创建敌机精灵组

在主文件的__create_sprites方法中,创建一个空的敌机精灵组,这里不创建精灵,因为要在事件监听方法里去创建。代码如下:

# 创建敌机精灵组 self.enemy_group = pygame.sprite.Group()

4、更新敌机精灵组

在__update_sprites方法中,调用敌机精灵组的update和draw方法,代码如下:

# 更新敌机精灵组 self.enemy_group.update() self.enemy_group.draw(self.screen)

至此,整个添加敌机的部分就全部完成了。


五、运行效果和完整代码

1、运行效果

到目前为止,飞机大战这个项目的基本元素都已经形成了,下一步就是添加子弹和碰撞检测功能。下面是目前的运行效果:

2、完整代码

以下是到目前为止的完整代码:

import pygame import random # 定义屏幕区域常量对象,常量的size属性就是屏幕的宽和高元组 SCREEN_RECT = pygame.Rect(0, 0, 500, 800) # 定义刷新率常量 FRAME = 60 # 定义敌机定时器事件标识常量 CREATE_ENEMY_EVENT = pygame.USEREVENT # 定义精灵类的派生类GameSprite,命名为游戏精灵 class GameSprite(pygame.sprite.Sprite): # 重写精灵类的__init__方法 # 参数image_name用来接收要显示的图片地址 # 参数speed用来接收移动速度,默认值为1,其实就是原来我们设定的移动步长 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 # 定义精灵类的update方法 def update(self): # 因为敌机都是纵向移动,因此每次纵坐标都加上speed self.rect.y = self.speed # 定义背景图片精灵类,继承游戏精灵GameSprite类 class BackGround(GameSprite): # 重写__init__方法 # 背景图片要实现滚动效果,必须有两个相同的背景图片,背景1和背景2循环交替滚动 # 背景1正常显示在窗口区域,背景2位于窗口上方,跟背景1无缝连接,用来补位 # 两个背景同时向下移动,一个背景图片移出窗口后马上回到窗口最上面,即把图片坐标y值设成窗口高度的负数 def __init__(self, is_alt=False): # 首先调用父类的初始化方法,传入背景图片路径 # 调用父类的初始化方法后,背景精灵的image、rect和speed就都建立了 super().__init__(r'images\background.jpg') # 参数is_aot用来确定是否为替换图片,如果为真则将该精灵的纵坐标值设成窗口高度的负数 if is_alt: self.rect.y = -SCREEN_RECT.height def update(self): super().update() if self.rect.y >= SCREEN_RECT.height: self.rect.y = -SCREEN_RECT.height # 定义己方飞机精灵类,继承游戏精灵GameSprite类 class OwnPlane(GameSprite): # 重写__init__方法 def __init__(self): # 调用父类初始化方法,搭建基础环境 # 初始speed设置为0,即飞机静止 super().__init__(r'images\plane.png', speed=0) # 飞机区域的中心位置与窗口的中心位置相同,即纵向居中 self.rect.center = SCREEN_RECT.center # 飞机区域的底部位于窗口底部向上100像素处 self.rect.bottom = SCREEN_RECT.bottom - 100 # 重写update方法 def update(self): 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 # 定义敌机精灵类,继承游戏精灵GameSprite类 class EnemyPlane(GameSprite): def __init__(self): # 生成1-3的随机数 num = random.randint(1, 3) # 根据随机数确定加载的敌机图片文件名,即en_plane1.png/en_plane2.png/en_plane3.png filename = r'images\en_plane{}.png'.format(num) super().__init__(filename) # 设置敌机速度是1-3之间的随机数 self.speed = random.randint(1, 3) # 设置敌机纵向的初始位置,敌机进入窗口时会非常顺滑,否则敌机进入时会有突然跳入的感觉 self.rect.bottom = 0 # 设置敌机横向的初始位置,在X轴的0到窗口宽度减去飞机宽度之间随机出现 max_x = SCREEN_RECT.width - self.rect.width self.rect.x = random.randint(0, max_x) def update(self): super().update() if self.rect.y > SCREEN_RECT.height: # 如果敌机飞出了屏幕下方,则敌机精灵自己调用kill方法将自己在所有精灵组中删除,释放内存 self.kill()

import pygame from plane_sprites import * # 飞机游戏类 class PlaneGame: def __init__(self): # 创建游戏窗口 # 屏幕尺寸使用plane_sprites模块里定义的常量SCREEN_RECT的size属性 self.screen = pygame.display.set_mode(SCREEN_RECT.size) # 设置窗口标题 pygame.display.set_caption('飞机大战游戏V1.0') # 创建游戏时钟 self.clock = pygame.time.Clock() # 调用定时器,每1秒钟产生一个创建敌机定时器事件常量 pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000) # 调用__create_sprites私有方法,创建精灵和精灵组 self.__create_sprites() # 创建精灵私有方法 def __create_sprites(self): # 创建背景精灵和精灵组 # 创建两个背景图片精灵,用于交替显示背景 # bg1采用默认参数,即is_alt为假,表示bg1正常显示在窗口区域 bg1 = BackGround() # bg2的参数is_alt为真,表示bg2是替换背景,精灵内部会将其纵坐标自动设为窗口高度的负数 bg2 = BackGround(is_alt=True) # 将两个背景精灵加入到一个精灵组中 self.back_group = pygame.sprite.Group(bg1, bg2) # 创建己方飞机精灵和精灵组 self.plane = OwnPlane() self.plane_group = pygame.sprite.Group(self.plane) # 创建敌机精灵组 self.enemy_group = pygame.sprite.Group() # 事件监听私有方法 def __event_handle(self): # 获取全部事件,并遍历判断 for event in pygame.event.get(): if event.type == pygame.QUIT: # 如果是点击退出按钮事件,则调用__game_over方法退出游戏 self.__game_over() elif event.type == CREATE_ENEMY_EVENT: # 如果事件类型是自定义的创建敌机定时器事件,则创建敌机精灵并加入到敌机精灵组 en_plane = EnemyPlane() self.enemy_group.add(en_plane) # 获取所有按键状态的元组,注意返回值是元组类型 keys_pressed = pygame.key.get_pressed() # 如果左方向键被按下,则飞机的横坐标每次减2 if keys_pressed[pygame.K_LEFT]: self.plane.speed = -2 # 如果右方向键被按下,则飞机的横坐标每次加2 elif keys_pressed[pygame.K_RIGHT]: self.plane.speed = 2 # 如果这两个键都没被按下,则飞机静止 else: self.plane.speed = 0 # 碰撞检测私有方法 def __check_collide(self): pass # 更新/绘制精灵组私有方法 def __update_sprites(self): # 调用背景精灵组的update和draw方法 self.back_group.update() self.back_group.draw(self.screen) # 更新己方飞机精灵组 self.plane_group.update() self.plane_group.draw(self.screen) # 更新敌机精灵组 self.enemy_group.update() self.enemy_group.draw(self.screen) # 游戏结束静态方法 # 静态方法没有参数传递,不需要实例化,可以直接调用,主要用来存放逻辑性代码 # 静态方法存在的意义其实就是让函数归类,紧耦合。 @staticmethod def __game_over(): pygame.quit() exit() # 开始游戏方法 def start_game(self): while True: # 设置刷新帧率 self.clock.tick(FRAME) # 事件监听 self.__event_handle() # 碰撞检测 self.__check_collide() # 更新/绘制精灵组 self.__update_sprites() # 更新显示 pygame.display.update() if __name__ == '__main__': # 使用自定义的PlaneGame类创建游戏对象 game = PlaneGame() # 调用游戏对象的开始游戏方法运行游戏 game.start_game()

未完,待续!在第四篇里继续完善!

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved