从零基于python构建飞机大战
文章目录
涉及知识点:
python基础语法:分支,循环,基本数据类型,高级结构(列表,元组,字典),python面向对象知识,面向对象中封装、继承、多态的应用,普通2d游戏的基本原理,pygame模块的使用 设计层面:
面向对象的设计思路,面向对象分析,对于程序复用性的考虑,
有待完善点:多线程的引入
参考资料:think in python、 pygame官网文档
#环境的构建 pygame简介:Pygame是跨平台Python模块,专为电子游戏设计,包含图像、声音。建立在SDL基础上,允许实时电子游戏研发的模块。 pygame的安装: 使用命令 pip install pygame
最终效果
所需资料/源码:链接:https://pan.baidu.com/s/1UdevAqz48AqE4uMW5_Copg
提取码:ui7p
项目概述
本项目较好的巩固了python基础语法内容,在实现过程中充分的应用到了面向对象的编程思想,对于初学者而言是一个较为不错的训练项目.本文注重描述实现过程中面向对象的思想的应用,对于pygame的部分细节描述较少,所以读者可以根据自己所需进行针对性的阅读。
前置知识
#本节主要叙述pygame中用于本次项目的一些内容,欲获详细信息请查阅官方文档
游戏初始化
#要使用 `pygame` 提供的所有功能之前,需要调用 `init` 方法 (编写游戏前必须进行初始化!)
`pygame.init()`导入并初始化所有 `pygame` 模块,使用其他模块之前,必须先调用 `init` 方法
创建游戏主窗口
pygame
专门提供了一个 模块pygame.display
用于创建、管理 游戏窗口
方法 | 说明 |
---|---|
pygame.display.set_mode() |
初始化游戏显示窗口 |
pygame.display.update() |
刷新屏幕内容显示,稍后使用 |
set_mode
方法
set_mode(resolution=(0,0), flags=0, depth=0) -> Surface
-
作用 —— 创建游戏显示窗口
-
参数
resolution
指定屏幕的宽
和高
,默认创建的窗口大小和屏幕大小一致flags
参数指定屏幕的附加选项,例如是否全屏等等,默认不需要传递depth
参数表示颜色的位数,默认自动匹配
-
返回值
- 暂时 可以理解为 游戏的屏幕,游戏的元素 都需要被绘制到 游戏的屏幕 上
-
注意:必须使用变量记录
set_mode
方法的返回结果!因为:后续所有的图像绘制都基于这个返回结果
# 创建游戏主窗口
screen = pygame.display.set_mode((480, 700))
简单的游戏循环
基本思路:让程序进行无限循环( while True : ),若要进行退出游戏,此处使用的是获取到pygame提供的事件队列来进行退出处理。
- 为了做到游戏程序启动后,不会立即退出,通常会在游戏程序中增加一个 游戏循环
- 所谓 游戏循环 就是一个 无限循环
- 在 创建游戏窗口 代码下方,增加一个无限循环
- 注意:游戏窗口不需要重复创建
# 创建游戏主窗口
screen = pygame.display.set_mode((480, 700))
# 游戏循环
while True:
#获取事件队列,判断是否是退出事件
for event in pygame.event.get():
#退出游戏
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pass
#游戏处理主要逻辑
绘制图像
-
在游戏中,能够看到的 游戏元素 大多都是 图像
- 图像文件 初始是保存在磁盘上的,如果需要使用,第一步 就需要 被加载到内存
-
要在屏幕上 看到某一个图像的内容,需要按照三个步骤:
- 使用
pygame.image.load()
加载图像的数据 - 使用 游戏屏幕 对象,调用
blit
方法 将图像绘制到指定位置 - 调用
pygame.display.update()
方法更新整个屏幕的显示
基本逻辑
- 使用
"""
1. 加载图像
2. 绘制图像
3. 更新显示 注意:要想在屏幕上看到绘制的结果,就一定要调用 `pygame.display.update()` 方法
"""
# 绘制背景图像
# 1> 加载图像
bg = pygame.image.load("./images/background.png")
# 2> 绘制在屏幕
screen.blit(bg, (0, 0)) # 后面(0,0)标定位置
# 3> 更新显示
pygame.display.update()
精灵 和精灵组
为了简化开发步骤,pygame
提供了两个类
pygame.sprite.Sprite
—— 存储 图像数据 image 和 位置 rect 的 对象pygame.sprite.Group
精灵
-
在游戏开发中,通常把 显示图像的对象 叫做精灵
Sprite
-
精灵 需要 有 两个重要的属性
image
要显示的图像rect
图像要显示在屏幕的位置
-
默认的
update()
方法什么事情也没做- 子类可以重写此方法,在每次刷新屏幕时,更新精灵位置
-
注意:
pygame.sprite.Sprite
并没有提供image
和rect
两个属性- 需要程序员从
pygame.sprite.Sprite
派生子类 - 并在 子类 的 初始化方法 中,设置
image
和rect
属性
- 需要程序员从
精灵组、
- 一个 精灵组 可以包含多个 精灵 对象
- 调用 精灵组 对象的
update()
方法- 可以 自动 调用 组内每一个精灵 的
update()
方法
- 可以 自动 调用 组内每一个精灵 的
- 调用 精灵组 对象的
draw(屏幕对象)
方法- 可以将 组内每一个精灵 的
image
绘制在rect
位置
- 可以将 组内每一个精灵 的
#派生精灵组
import pygame
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, *args):
# 默认在垂直方向移动
self.rect.y += self.speed
注:引入精灵、精灵组概念是为了更好管理游戏中所需部件信息,类似于java中对象和容器的概念
类比于容器用来存放特定的对象信息,而精灵组就是用来管理精灵。
2d游戏基本原理
- 跟 电影 的原理类似,游戏中的动画效果,本质上是 快速 的在屏幕上绘制 图像
- 电影是将多张 静止的电影胶片 连续、快速的播放,产生连贯的视觉效果!
- 一般在电脑上 每秒绘制 60 次,就能够达到非常 连续 高品质 的动画效果
- 每次绘制的结果被称为 帧 Frame (通俗来讲就是重绘频率)
总结
游戏在开发中用到了加载媒体音乐、字体等功能,其本质同加载图片逻辑一致,查阅官方文档即可掌握基本使用。
开发本游戏重点并不是pygame类库的使用,重点在于游戏整体游戏逻辑的思考,各个对象模块间的依赖性处理,对象抽象性设计,代码后期维护性、复用性是重点关注点。
技术的更迭永远大于学习的速度,重点并不是类库的功能的学习,而在于业务整体逻辑性的思考,提升对于复杂问题抽象的能力,这才是一个本项目核心所在。莫要本末倒置。
游戏页面的搭建
页面搭建完成如下任务:
- 绘制背景图像
- 加入游戏主循环
- 处理游戏事件
- 更新游戏状态
- 刷新屏幕内容
游戏版本一
import pygame import sys import constants from game.plane import OurPlane, SmallEnemyPlane from game.war import PlaneWar def main(): pygame.init() width , height = 480,852 # screen = pygame.display.set_mode((width,height)) screen = pygame.display.set_mode((480, 700)) # 绘制背景图像 bg = pygame.image.load("image/bg.png") while True : for event in pygame.event.get(): #退出游戏 if event.type == pygame.QUIT: pygame.quit() sys.exit() #绘制 screen.blit(bg,bg.get_rect()) #更新内容 pygame.display.flip()
游戏版本二
import pygame import sys import constants from game.war import PlaneWar def main(): """ 游戏入口,main方法 """ # 声明一个war对象,其内部构造会在后文叙述!!! war = PlaneWar() # 添加小型敌方飞机 war.add_small_enemies(6) # 运行游戏 war.run_game()
#分析 如上两份代码那份写的更好?(暂时对于版本二中war考虑) 如果有过面向对象设计的基础知识,我想大部分人都会认为第二段代更比较符合面向对象的设计思维 面向对象要求设计者面向解决问题进行建模,将对象当做一个解空间来看待,处理流程可以看做:向某个对象发送消息,这个对象便知道此消息目的然后执行一段程序代码,进而完成某一项功能。此之谓面向对象。 至于代码一,则像是传统面向过程的设计思路,根据既定的顺序完成某项功能。 #诸如如下代码这样采用硬编码的方式提供路径资源的应该在程序设计中尽最大可能避免 bg = pygame.image.load("image/bg.png") 原因很简单: 你的代码不可能“天衣无缝”,随着代码的演进,资源可能会发生改变。在开发阶段你可以这样任性,但是当代码发布至服务器端或交付用户后,用户不一定有着同你一样的文件目录形式,这是你必须面对的问题。 对于硬编码的形式的代码段只会加重日后维护难度,所以不应该提倡。
资源统一管理
将所需资源信息加载至一个常量类,便于信息维护
import os import pygame #定义一个常量类用于保存资源信息 # 项目的根目录 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # 静态文件的目录 \\ / ASSETS_DIR = os.path.join(BASE_DIR, 'assets') # 背景图片 BG_IMG = os.path.join(ASSETS_DIR, 'images/background.png') BG_IMG_OVER = os.path.join(ASSETS_DIR, 'images/game_over.png') # 标题图片 IMG_GAME_TITLE = os.path.join(ASSETS_DIR, 'images/game_title.png') # 开始游戏的按钮 IMG_GAME_START_BTN = os.path.join(ASSETS_DIR, 'images/game_start.png') # 背景音乐 BG_MUSIC = os.path.join(ASSETS_DIR, 'sounds/game_bg_music.mp3') # 游戏分数颜色 TEXT_SOCRE_COLOR = pygame.Color(255, 255, 0) # 击中小型飞机添加10分 SCORE_SHOOT_SMALL = 10 # 游戏结果存储的文件地址 PLAY_RESULT_STORE_FILE = os.path.join(BASE_DIR, 'store/rest.txt') # 我方飞机的静态资源 OUR_PLANE_IMG_LIST = [ os.path.join(ASSETS_DIR, 'images/hero1.png'), os.path.join(ASSETS_DIR, 'images/hero2.png') ] OUR_DESTROY_IMG_LIST = [ os.path.join(ASSETS_DIR, 'images/hero_broken_n1.png'), os.path.join(ASSETS_DIR, 'images/hero_broken_n2.png'), os.path.join(ASSETS_DIR, 'images/hero_broken_n3.png'), os.path.join(ASSETS_DIR, 'images/hero_broken_n4.png'), ] # 子弹图片和发射声音 BULLET_IMG = os.path.join(ASSETS_DIR, 'images/bullet1.png') BULLET_SHOOT_SOUND = os.path.join(ASSETS_DIR, 'sounds/bullet.wav') # 敌方小型飞机图片及音效 SMALL_ENEMY_PLANE_IMG_LIST = [os.path.join(ASSETS_DIR, 'images/enemy1.png')] SMALL_ENEMY_DESTROY_IMG_LIST = [ os.path.join(ASSETS_DIR, 'images/enemy1_down1.png'), os.path.join(ASSETS_DIR, 'images/enemy1_down2.png'), os.path.join(ASSETS_DIR, 'images/enemy1_down3.png'), os.path.join(ASSETS_DIR, 'images/enemy1_down4.png'), ] # 小型飞机坠毁时播放音乐 SMALL_ENEMY_PLANE_DOWN_SOUND = os.path.join(ASSETS_DIR, 'sounds/enemy1_down.wav')
#优化的代码 #通过类.属性的方式调用资源信息 bg = pygame.image.load(constants.BG_IMG) """ 可能会觉得这样写更加繁琐,但这样繁琐所带来的好处就是日后维护仅需修改一行代码即可 孰轻孰重,经历过的人会懂 """
游戏关键类设计
总体概述:
本游戏逻辑相对简单,所以涉及到的类也相对较少,具体包括飞机类,子弹类,战场类。
其中战场类相对一个整合其他类的容器,主要完成游戏逻辑功能。
飞机类
分析:
游戏所需的任务需要我方英雄战机和敌方战机。
#误区:不加思考直接创建两个英雄战机,敌方战机类
这种设计的主要误区在于忽略了继承的特性,在书写代码时必定会有大量逻辑相似代码重复出现。
解决策略:抽象出飞机共有特性,让英雄飞机和敌方战机继承,所以有了上述的继承图示
#飞机基类 class Plane(pygame.sprite.Sprite): """" 飞机的基础类 所有飞机的公共信息 飞机图片,爆炸图片,坠毁图片,坠毁音效,存活状态,子弹精灵组 """ # 飞机的图片 plane_images = [] # 飞机爆炸的图片 destroy_images = [] # 坠毁的音乐地址 down_sound_src = None # 飞机的状态: True,活的,False,死的 active = True # 该飞机发射的子弹精灵组 bullets = pygame.sprite.Group() def __init__(self,screen, speed=None): # 如果子类重写了__init__方法,那么在方法内必须显式的调用父类的__init__方法 super().__init__() #类的不同信息,不同实现类,资源信息不同 self.screen = screen # 加载静态资源 self.img_list = [] self._destroy_img_list = [] self.down_sound = None self.load_src() #设定下落速度 self.speed = speed or 10 #获取飞机位置信息 self.rect = self.img_list[0].get_rect() #获取飞机宽高信息 self.plane_w , self.plane_h = self.img_list[0].get_size() #获取窗口信息 self.width , self.height = self.screen.get_size() # 改变飞机的初始化位置, 放在屏幕的下方 self.rect.left = int((self.width - self.plane_w) / 2) self.rect.top = int(self.height / 2) @property def image(self): return self.img_list[0] #在屏幕上绘制飞机信息 def blit_me(self): self.screen.blit(self.image, self.rect) """ 加载图像,声音等资源信息 :return: """ def load_src(self): #装载飞机图片 此处需要借助于pygame load image方法记载信息,提供的信息为路径信息 for img in self.plane_images: self.img_list.append(pygame.image.load(img)) #装载坠毁图片 for img in self.destroy_images: self._destroy_img_list.append(pygame.image.load(img)) #装载音乐信息 if self.down_sound_src : self.down_sound = pygame.mixer.Sound(self.down_sound_src) """ 控制飞机移动的方法 """ def move_up(self): """ 飞机向上移动 """ self.rect.top -= self.speed def move_down(self): """ 飞机向下移动 """ self.rect.top += self.speed def move_left(self): """ 飞机向左移动 """ self.rect.left -= self.speed def move_right(self): """ 飞机向右移动 """ self.rect.left += self.speed """ 记录飞机被击毁后的操作 """ def broken_down(self): """ 播放坠毁音乐 加载坠毁动画 生命标记置为false :return: """ #播放坠毁音乐 if self.down_sound: self.down_sound.play() #加载坠毁动画 for img in self._destroy_img_list: #绘制到屏幕上 self.screen.blit(img,self.rect) #生命标记改变 self.active = False "发射子弹" def shoot(self): """ 飞机发射子弹 """ bullet = Bullet(self.screen, self, 15) self.bullets.add(bullet)
#具体分析 #属性信息 # 飞机的图片 plane_images = [] # 飞机爆炸的图片 destroy_images = [] # 坠毁的音乐地址 down_sound_src = None # 飞机的状态: True,活的,False,死的 active = True # 该飞机发射的子弹精灵组 bullets = pygame.sprite.Group() """ 由于不同的飞机会有不同的图片信息所以利用一个 plane_images的变量来存放。 随后的 destroy_images, destroy_images也是同样的设计理念 在每个类初始化时增设一个 img_list信息,用来记录战机的飞机信息 """ #初始化构造器方法 def __init__(self,screen, speed=None): # 如果子类重写了__init__方法,那么在方法内必须显式的调用父类的__init__方法 super().__init__() #类的不同信息,不同实现类,资源信息不同 self.screen = screen # 加载静态资源 self.img_list = [] #保存飞机信息 self._destroy_img_list = [] #保存坠毁后的图片信息 self.down_sound = None self.load_src() #设定下落速度 self.speed = speed or 10 #获取飞机位置信息 self.rect = self.img_list[0].get_rect() #获取飞机宽高信息 self.plane_w , self.plane_h = self.img_list[0].get_size() #获取窗口信息 self.width , self.height = self.screen.get_size() # 改变飞机的初始化位置, 放在屏幕的下方 self.rect.left = int((self.width - self.plane_w) / 2) self.rect.top = int(self.height / 2) #加载plane_images中的资源信息 def load_src(self): #装载飞机图片 此处需要借助于pygame load image方法记载信息,提供的信息为路径信息 for img in self.plane_images: self.img_list.append(pygame.image.load(img)) #装载坠毁图片 for img in self.destroy_images: self._destroy_img_list.append(pygame.image.load(img)) #装载音乐信息 if self.down_sound_src : self.down_sound = pygame.mixer.Sound(self.down_sound_src)
""" 处理移动逻辑的核心在于座标的移动,通过控制座标的加减来实现对飞机的控制效果 注:pgame的座标系不同于传统的座标,pygame中其实座标在左上方,即左上方为原点! """ #移动对象方法 def move_up(self): """ 飞机向上移动 """ self.rect.top -= self.speed def move_left(self): """ 飞机向左移动 """ self.rect.left -= self.speed
英雄飞机
""" 英雄飞机 """ class OurPlane(Plane): """ 我方的飞机 """ # 飞机的图片 plane_images = constants.OUR_PLANE_IMG_LIST # 飞机爆炸的图片 destroy_images = constants.OUR_DESTROY_IMG_LIST # 坠毁的音乐地址 down_sound_src = None #更新飞机动画信息 def update(self, war): #传入移动键盘信息到move中控制飞机的移动处理 self.move(war.key_down) if war.frame % 5 : self.screen.blit(self.img_list[0],self.rect) else: self.screen.blit(self.img_list[1],self.rect) # 飞机碰撞检测 rest = pygame.sprite.spritecollide(self, war.enemies, False) if rest : # 1. 游戏结束 war.status = war.OVER # 2. 敌方飞机清除 war.enemies.empty() war.small_enemies.empty() # 3. 我方飞机坠毁效果 self.broken_down() # 4. 记录游戏成绩 def move(self, key): """ 飞机移动自动控制 """ if key == pygame.K_w or key == pygame.K_UP: self.move_up() elif key == pygame.K_s or key == pygame.K_DOWN: self.move_down() elif key == pygame.K_a or key == pygame.K_LEFT: self.move_left() elif key == pygame.K_d or key == pygame.K_RIGHT: self.move_right() def move_up(self): """ 向上移动,超出范围之后,拉回来 """ super().move_up() if self.rect.top <= 0: self.rect.top = 0 def move_down(self): super().move_down() if self.rect.top >= self.height - self.plane_h: self.rect.top = self.height - self.plane_h def move_left(self): super().move_left() if self.rect.left <= 0: self.rect.left = 0 def move_right(self): super().move_right() if self.rect.left >= self.width - self.plane_w: self.rect.left = self.width - self.plane_w
#英雄类信息分析 """ 英雄飞机应该可供玩家操作,本游戏操作核心相对简单,通过监听键盘按键来完成具体操作的处理 """ #根据不同的按键信息,响应不同的事件 def move(self, key): """ 飞机移动自动控制 """ if key == pygame.K_w or key == pygame.K_UP: self.move_up() elif key == pygame.K_s or key == pygame.K_DOWN: self.move_down() elif key == pygame.K_a or key == pygame.K_LEFT: self.move_left() elif key == pygame.K_d or key == pygame.K_RIGHT: self.move_right() #根据按键不同进行不同的移动处理 """" 在处理移动信息时,注意飞机的移动,控制飞机移动核心在于避免飞机移出屏幕 之后敌机类同该功能类似 """ def move_down(self): super().move_down() if self.rect.top >= self.height - self.plane_h: self.rect.top = self.height - self.plane_h
class SmallEnemyPlane(Plane): """ 敌方的小型飞机 """ # 飞机的图片 plane_images = constants.SMALL_ENEMY_PLANE_IMG_LIST # 飞机爆炸的图片 destroy_images = constants.SMALL_ENEMY_DESTROY_IMG_LIST # 坠毁的音乐地址 down_sound_src = constants.SMALL_ENEMY_PLANE_DOWN_SOUND def __init__(self, screen, speed): super().__init__(screen, speed) # 每次生成一架新的小型飞机的时候,随机的位置出现在屏幕中 # 改变飞机的随机位置 self.init_pos() def init_pos(self): """ 改变飞机的随机位置 """ # 屏幕的宽度-飞机的宽度 self.rect.left = random.randint(0, self.width - self.plane_w) # 屏幕之外随机高度 self.rect.top = random.randint(-5 * self.plane_h, -self.plane_h) def update(self, *args): """ 更新飞机的移动 """ super().move_down() # 画在屏幕上 self.blit_me() # 超出范围后如何处理 # 1. 重用 if self.rect.top >= self.height: self.active = False # self.kill() self.reset() # todo 2. 多线程、多进程 def reset(self): """ 重置飞机的状态,达到复用的效果 """ self.active = True # 改变飞机的随机位置 self.init_pos() def broken_down(self): """ 飞机爆炸 """ super().broken_down() # 重复利用飞机对象 self.reset()
子弹类
主要用于战机发射子弹,核心难点就在于子弹的碰撞检测
class Bullet(pygame.sprite.Sprite): """ 子弹类 """ # 子弹状态,True: 活着 active = True def __init__(self, screen, plane, speed=None): super().__init__() self.screen = screen # 速度 self.speed = speed or 10 self.plane = plane # 加载子弹的图片 self.image = pygame.image.load(constants.BULLET_IMG) #self.image = pygame.image.load("./assets/images/bullet1.png") # 改变子弹的位置 self.rect = self.image.get_rect() self.rect.centerx = plane.rect.centerx self.rect.top = plane.rect.top # 发射的音乐效果 self.shoot_sound = pygame.mixer.Sound(constants.BULLET_SHOOT_SOUND) self.shoot_sound.set_volume(0.3) self.shoot_sound.play() def update(self,war): """ 更新子弹的位置 """ self.rect.top -= self.speed # 超出屏幕的范围 if self.rect.top < 0: self.remove(self.plane.bullets) # 绘制子弹 self.screen.blit(self.image, self.rect) # 碰撞检测,检测子弹是否已经碰撞到了敌机 rest = pygame.sprite.spritecollide(self, war.enemies, False) for r in rest: # 1. 子弹消失 self.kill() # 2. 飞机爆炸,坠毁效果 r.broken_down() # 3. 统计游戏成绩 war.rest.score += constants.SCORE_SHOOT_SMALL # 保存历史记录 war.rest.set_history()
""" 2个矩形如果发生碰撞(即:图形有重叠区域),按上图的判断条件就能检测出来,如果是圆形,则稍微变通一下,用半径检测。如果是其它不规则图形,大多数游戏中,并不要求精确检测,可以在外层套一个矩形,大致用上图的原理检测。 """ def collision_check(a, b): temp1 = (b.x <= a.x + a.width <= b.x + b.width) temp2 = (b.y <= a.y + a.height <= b.y + b.height) return temp1 and temp2 #由于使用pygame框架,所以并不用这样繁琐处理碰撞检测问题 #利用pygame提供的spritecollide来进行碰撞的测试处理 rest = pygame.sprite.spritecollide(self, war.enemies, False) #注意此时返回一个列表内容,包含发生碰撞飞机信息 #让碰撞的敌机消失 for r in rest: # 1. 子弹消失 self.kill() # 2. 飞机爆炸,坠毁效果 r.broken_down() # 3. 统计游戏成绩 war.rest.score += constants.SCORE_SHOOT_SMALL # 保存历史记录 war.rest.set_history()
战场类(war)
主要涵盖如下功能
游戏页面初始化
事件监听绑定
开始游戏
添加敌机/英雄
import pygame import sys import constants from game.plane import OurPlane, SmallEnemyPlane from store.result import PlayRest class PlaneWar(object): """ 飞机大战 """ # 游戏状态 READY = 0 # 游戏准备中 PLAYING = 1 # 游戏中 OVER = 2 # 游戏结束 status = READY # 0准备中,1 游戏中,2 游戏结束 our_plane = None frame = 0 # 播放帧数 # 一架飞机可以属于多个精灵组 small_enemies = pygame.sprite.Group() enemies = pygame.sprite.Group() # 游戏结果 rest = PlayRest() def __init__(self): # 初始化游戏 pygame.init() self.width, self.height = 480, 852 # 屏幕对象 self.screen = pygame.display.set_mode((self.width, self.height)) # 设置窗口标题 pygame.display.set_caption('飞机大战') # 加载背景图片 self.bg = pygame.image.load(constants.BG_IMG) self.bg_over = pygame.image.load(constants.BG_IMG_OVER) # 游戏的标题 self.img_game_title = pygame.image.load(constants.IMG_GAME_TITLE) self.img_game_title_rect = self.img_game_title.get_rect() # 宽度和高度 t_width, t_height = self.img_game_title.get_size() self.img_game_title_rect.topleft = (int((self.width - t_width) / 2), int(self.height / 2 - t_height)) # 开始按钮 self.btn_start = pygame.image.load(constants.IMG_GAME_START_BTN) self.btn_start_rect = self.btn_start.get_rect() btn_width, btn_height = self.btn_start.get_size() self.btn_start_rect.topleft = (int((self.width - btn_width) / 2), int(self.height / 2 + btn_height)) # 游戏文字对象 self.score_font = pygame.font.SysFont('华文隶书', 32) # 加载背景音乐 pygame.mixer.music.load(constants.BG_MUSIC) pygame.mixer.music.play(-1) # 无限循环播放 pygame.mixer.music.set_volume(0.2) # 设置音量 # 我方飞机对象 self.our_plane = OurPlane(self.screen, speed=4) self.clock = pygame.time.Clock() # 上次按的键盘上的某一个键,用于控制飞机 self.key_down = None def bind_event(self): """ 绑定事件 """ # 1. 监听事件 for event in pygame.event.get(): # 退出游戏 if event.type == pygame.QUIT: pygame.quit() sys.exit() elif event.type == pygame.MOUSEBUTTONDOWN: # 鼠标点击进入游戏 # 游戏正在准备中,点击才能进入游戏 if self.status == self.READY: self.status = self.PLAYING self.rest.score = 0 elif self.status == self.PLAYING: # 点击鼠标发射子弹 self.our_plane.shoot() elif self.status == self.OVER: self.status = self.READY self.add_small_enemies(6) elif event.type == pygame.KEYDOWN: # 键盘事件 self.key_down = event.key # 游戏正在游戏中,才需要控制键盘 ASWD if self.status == self.PLAYING: if event.key == pygame.K_w or event.key == pygame.K_UP: self.our_plane.move_up() elif event.key == pygame.K_s or event.key == pygame.K_DOWN: self.our_plane.move_down() elif event.key == pygame.K_a or event.key == pygame.K_LEFT: self.our_plane.move_left() elif event.key == pygame.K_d or event.key == pygame.K_RIGHT: self.our_plane.move_right() elif event.key == pygame.K_SPACE: # 发射子弹 self.our_plane.shoot() def add_small_enemies(self, num): """ 随机添加N架小型敌机 :param num: 飞机的产生数量 :return: """ # 随机添加6架小型敌机 for i in range(num): plane = SmallEnemyPlane(self.screen, 4) plane.add(self.small_enemies, self.enemies) def run_game(self): """ 游戏主循环部分 """ while True: # 1. 设置帧速率 self.clock.tick(60) self.frame += 1 if self.frame >= 60: self.frame = 0 # 2. 绑定事件 self.bind_event() # 3.更新游戏的状态 if self.status == self.READY: # 游戏正在准备中 # 绘制背景 self.screen.blit(self.bg, self.bg.get_rect()) # 标题 self.screen.blit(self.img_game_title, self.img_game_title_rect) # 开始按钮 self.screen.blit(self.btn_start, self.btn_start_rect) self.key_down = None elif self.status == self.PLAYING: # 游戏进行中 # 绘制背景 self.screen.blit(self.bg, self.bg.get_rect()) # 绘制飞机 self.our_plane.update(self) # 绘制子弹 self.our_plane.bullets.update(self) # 绘制敌方飞机 self.small_enemies.update() # 游戏分数 score_text = self.score_font.render( '得分: {0}'.format(self.rest.score), False, constants.TEXT_SOCRE_COLOR ) self.screen.blit(score_text, score_text.get_rect()) elif self.status == self.OVER: # # 游戏结束 # 游戏背景 self.screen.blit(self.bg_over, self.bg_over.get_rect()) # 分数统计 # 1. 本次总分 score_text = self.score_font.render( '{0}'.format(self.rest.score), False, constants.TEXT_SOCRE_COLOR ) score_text_rect = score_text.get_rect() text_w, text_h = score_text.get_size() # 改变文字的位置 score_text_rect.topleft = ( int((self.width - text_h) / 2), int(self.height / 2) ) self.screen.blit(score_text, score_text_rect) # 2. 历史最高分 score_his = self.score_font.render( '{0}'.format(self.rest.get_max_core()), False, constants.TEXT_SOCRE_COLOR ) self.screen.blit(score_his, (150, 40)) pygame.display.flip()
#关键方法分析 """" 如下代码看到可能会比较多,但是主要逻辑功能如下 由于游戏有三种状态:开始,进行中,结束三种状态 游戏操作逻辑如下: 开始 READY 首先用户鼠标点击触发pygame提供的 pygame.MOUSEBUTTONDOWN事件,此状态下要做的就是绘制游戏登录页面信息 进行中 PLAYING 此时要做的就是更新页面信息,进入游戏主页面,绘制敌机信息,播放游戏背景音乐,绘制子弹信息,实时更新成绩信息。如果英雄战机被击中则将状态置为OVer 结束 OVER 此时游戏结束,显示结束画面,展示用户得分情况 """" def run_game(self): """ 游戏主循环部分 """ while True: # 1. 设置帧速率 self.clock.tick(60) self.frame += 1 if self.frame >= 60: self.frame = 0 # 2. 绑定事件 self.bind_event() # 3.更新游戏的状态 if self.status == self.READY: # 游戏正在准备中 # 绘制背景 self.screen.blit(self.bg, self.bg.get_rect()) # 标题 self.screen.blit(self.img_game_title, self.img_game_title_rect) # 开始按钮 self.screen.blit(self.btn_start, self.btn_start_rect) self.key_down = None elif self.status == self.PLAYING: # 游戏进行中 # 绘制背景 self.screen.blit(self.bg, self.bg.get_rect()) # 绘制飞机 self.our_plane.update(self) # 绘制子弹 self.our_plane.bullets.update(self) # 绘制敌方飞机 self.small_enemies.update() # 游戏分数 score_text = self.score_font.render( '得分: {0}'.format(self.rest.score), False, constants.TEXT_SOCRE_COLOR ) self.screen.blit(score_text, score_text.get_rect()) elif self.status == self.OVER: # # 游戏结束 # 游戏背景 self.screen.blit(self.bg_over, self.bg_over.get_rect()) # 分数统计 # 1. 本次总分 score_text = self.score_font.render( '{0}'.format(self.rest.score), False, constants.TEXT_SOCRE_COLOR ) score_text_rect = score_text.get_rect() text_w, text_h = score_text.get_size() # 改变文字的位置 score_text_rect.topleft = ( int((self.width - text_h) / 2), int(self.height / 2) ) self.screen.blit(score_text, score_text_rect) # 2. 历史最高分 score_his = self.score_font.render( '{0}'.format(self.rest.get_max_core()), False, constants.TEXT_SOCRE_COLOR ) self.screen.blit(score_his, (150, 40)) #刷新页面信息 pygame.display.flip()
成绩信息读取
读取记录中的信息,由于并未采用数据库,所以此处通过一个score.txt文件来保存成绩的最高分,主要就是文件读写模块的应用
import constants
class PlayRest(object):
""" 玩家的结果统计 """
__score = 0 # 总分
__life = 3 # 生命数量
__blood = 1000 # 生命值
@property
def score(self):
""" 单次游戏分数 """
return self.__score
@score.setter
def score(self, value):
""" 设置游戏分数 """
if value < 0:
return None
self.__score = value
def set_history(self):
""" 记录最高分 """
# 1. 读取文件中的存储的分数
# 2. 如果新的分数比文件中的分数要大,则进行存储
# 如果小于文件中的分数,不需要做处理
# 3. 存储分数,不是追加的模式a+,而是替换的模式w
if int(self.get_max_core()) < self.score:
with open(constants.PLAY_RESULT_STORE_FILE, 'w') as f:
f.write('{0}'.format(self.score))
def get_max_core(self):
""" 读取文件中的历史最高分 """
rest = 0
with open(constants.PLAY_RESULT_STORE_FILE, 'r') as f:
r = f.read()
if r:
rest = r
return rest
``
__blood = 1000 # 生命值
@property
def score(self):
""" 单次游戏分数 """
return self.__score
@score.setter
def score(self, value):
""" 设置游戏分数 """
if value < 0:
return None
self.__score = value
def set_history(self):
""" 记录最高分 """
# 1. 读取文件中的存储的分数
# 2. 如果新的分数比文件中的分数要大,则进行存储
# 如果小于文件中的分数,不需要做处理
# 3. 存储分数,不是追加的模式a+,而是替换的模式w
if int(self.get_max_core()) < self.score:
with open(constants.PLAY_RESULT_STORE_FILE, 'w') as f:
f.write('{0}'.format(self.score))
def get_max_core(self):
""" 读取文件中的历史最高分 """
rest = 0
with open(constants.PLAY_RESULT_STORE_FILE, 'r') as f:
r = f.read()
if r:
rest = r
return rest
总结
由于时间仓促,作品难免有些不足。对于可以优化的地方,文中开头也进行了阐述,总的来说还是较好的实现了基本游戏逻辑。同时在源码中也提供了详细的注释,方便读者阅读,如果您好的想法和观点,欢迎在评论区留言讨论。