赞
踩
Windows系统下的安装参考如下文章:
注:应在PyCharm的系统解释器的 Scripts 目录安装了 pygame 再新建工程
Linux系统下的安装:
安装pygame
sudo pip3 install pygame
验证安装(aliens是一个内置小游戏)
python3 -m pygame.examples.aliens
图片素材直接在下述链接下载即可:
(同时感谢素材的分享者)
方法 | 含义 |
---|---|
pygame.init() | 导入并初始化所有pygame模块,使用其他模块之前,必须先调用init方法 |
pygame.quit() | 卸载所有pygame模块,在游戏结束之前调用 |
在pygame中提供了一个类 pygame.Rect,其用于描述矩形区域:
矩形区域对象名 = pygame.Rect(x, y, width, height)
创建一个矩形区域的对象
其中:x, y, width, height 均属于对象属性,可以通过对象名来访问
另有一个 size 的元组属性:它是一个元组——(width, height),可以直接访问到 width 和 height,可以作为参数值来传递到 pygame.display.set_mode(…) 中,之后会讲到
例:在坐标系原点创建一个width=100,height=140的 plane 对象
import pygame
pygame.init()
plane = pygame.Rect(0, 0, 100, 140)
print("飞机所在位置:(%d,%d),飞机的大小:(%d,%d)" % (plane.x, plane.y, plane.width, plane.height))
pygame.quit()
第6行代码更改为下述代码也可以:
print("飞机所在位置:(%d,%d)" % (plane.x, plane.y), "飞机的大小:(%d,%d)" % plane.size)
在pygame中提供了一个模块 pygame.display,其用于创建、管理游戏主窗口:
方法 | 含义 |
---|---|
pygame.display.set_mode() | 初始化游戏显示窗口 |
pygame.display.update | 刷新显示游戏窗口 |
游戏主窗口对象名 = pygame.display.set_mode(resulution=(0, 0), flags=0, depth=0)
创建一个游戏显示窗口的对象
其中:resoluthon, flags, depth均属于对象属性
resolution 是一个元组,需要传递一个元组,它指定屏幕的宽和高,缺省时默认值为整个屏幕的大小,这个元组可以是 pygame.Rect(…) 函数返回的矩形区域对象的 size 属性 ——它是个元组
flags 指定屏幕的附加选项,例如是否全屏等,默认不需要传递
depth 表示颜色的位数,默认自动匹配
返回值 游戏的主窗口,游戏的元素都需要被绘制到游戏的主窗口上
例:创建一个 (480,700) 的游戏主窗口
import pygame
import time
"""窗口大小:(480,700)"""
window = pygame.display.set_mode((480, 700))
"""系统休眠5秒"""
time.sleep(5)
pygame.quit()
在游戏中,能够看到的游戏元素大多数是图像,图像文件初始是保存在磁盘上的,如果需要使用,第一步就需要把图像加载到内存中,一般要在屏幕上看到某一个图像的内容,需要安装下述三个步骤来进行:
图片对象名 = pygame.image.load(图片所在路径)
把图像打开并把数据加载到内存中,其中路径可以是图片相对源代码所在 .py文件 的 相对路径 或 绝对路径,返回图片对象
游戏主窗口对象名.blit(图片对象名, 矩形区域对象或(x, y)坐标)
在游戏主窗口中绘制图像
矩形区域对象 图片对象在游戏主窗口中矩形区域原点对应的位置,一般所设置的这个矩形区域与图片的大小一致,以此来让图片对应这片矩形区域——图片负责动画的实现,矩形区域负责游戏的逻辑控制
(x, y) 坐标元组,即图片对象在游戏主窗口的位置pygame.display.update()
刷新显示游戏主窗口
提示:要想在屏幕上看到绘制的结果,就一定要调用 pygame.display.update() 方法
例:我们尝试将飞机大战的背景图像 background.png 文件加载到游戏主窗口上
import pygame import time pygame.init() """创建游戏主窗口""" window = pygame.display.set_mode((480, 700)) """加载图片到内存""" background = pygame.image.load("./游戏素材/background.png") """在游戏主窗口中显示图片""" window.blit(background, (0, 0)) """刷新显示""" pygame.display.update() """系统休眠5秒""" time.sleep(5) pygame.quit()
提示:我们可以通过 blit() 方法将游戏主窗口的所有的图像布置完成后,再使用 pygame.display.update() 刷新游戏主窗口即可
需求:把英雄飞机 me1.png 或 me2.png 文件显示在游戏主窗口的背景上
import pygame import time pygame.init() """创建游戏主窗口""" window = pygame.display.set_mode((480, 700)) """加载需要的图片""" background = pygame.image.load("./游戏素材/background.png") me1 = pygame.image.load("./游戏素材/me1.png") """绘制背景和飞机""" window.blit(background, (0, 0)) window.blit(me1, (189, 574)) """刷新显示""" pygame.display.update() time.sleep(5) pygame.quit()
我们使用 PyCharm 或 PS 打开图片,发现英雄飞机图片的背景是一系列灰白相间的小格子,这些灰白相间的小格子代表英雄飞机是透明背景:
本文只针对 pygame 模块中一些简单的功能进行介绍,实际上 pygame 还拥有很多强大的功能,若有兴趣学习的可以参考下述文章:
我们知道,视频是一帧一帧地播放的,其实质是由一帧一帧变化的图像的动态刷新显示来呈现运动的效果,其利用了肉眼的视觉暂留原理,因此我们可以利用计算机对多张图片的快速刷新显示,即可达到动画的效果
通常,游戏循环意味着游戏的正式开始
游戏循环的作用:
在Python中,while True: 循环的速度可达每秒钟数十万次,而我们对图像帧率的要求不需要这么高,pygame 中有一个时钟类 pygame.time.Clock 可以非常方便地设置游戏主窗口的绘制速度——刷新帧率
时钟对象名 = pygame.time.Clock()
创建一个时钟对象
时钟对象名.tick(帧)
可以设置时钟的帧率,相当于在调用 tick() 方法的位置处暂停 1/帧 秒的时间,以此来实现固定帧率的刷新
例:
import pygame
pygame.init()
clock = pygame.time.Clock()
i = 0
while True:
clock.tick(1)
print(i)
i += 1
pygame.quit()
这里的0、1、2、3、4是每秒钟增加一个的
注:由于 blit() 方法可以传递 坐标元组 或者 矩形区域对象,因此在创建了一个矩形区域对象后,我们就可以直接用矩形区域对象来作为动画播放时图片的位置,让图片位置与矩形区域位置一一对应
例:利用 Rect 的矩形区域在y轴上的移动来实现飞机的移动
import pygame pygame.init() """创建游戏主窗口""" window = pygame.display.set_mode((480, 700)) """加载需要的图片""" background = pygame.image.load("./游戏素材/background.png") me1 = pygame.image.load("./游戏素材/me1.png") """创建一个矩形区域对象,设置初始位置""" hero_plane = pygame.Rect(189, 574, 102, 126) """设定一个时钟""" clock = pygame.time.Clock() """绘制背景""" window.blit(background, (0, 0)) while True: window.blit(me1, hero_plane) """刷新显示""" pygame.display.update() hero_plane.y -= 50 clock.tick(1) pygame.quit()
注:由于在同一张背景下绘制的图片不会消失,因此如果想要在一张背景下实现英雄飞机的动画式的变化,可以在游戏主窗口中再绘制一张背景,把原来的图像覆盖掉,再绘制英雄飞机图片,这样就可以实现英雄飞机的动画式变化
以上述代码为基础,只需把window.blit(background, (0, 0))
放在while True:
中即可实现
while True:
"""绘制背景"""
window.blit(background, (0, 0))
window.blit(me1, hero_plane)
"""刷新显示"""
pygame.display.update()
hero_plane.y -= 50
clock.tick(1)
注:这里飞机会飞出屏幕,而我们只需给矩形区域的位置做简单的判断即可实现飞机周而复始的运动;另外,我们调整一下帧率和飞机的移动距离,实现动画的连贯播放
if hero_plane.y + hero_plane.height <= 0:
hero_plane.y = 700
else:
hero_plane.y -= 1
clock.tick(240)
利用两张或多张不同的图片在同一位置的显示,来完成英雄飞机的动作——喷气
例:利用 me1.png 和 me2.png 的不同来刷新显示英雄飞机的变化
注:由于在同一个背景下绘制的图像不会消失,因此要想在背景下显示不同的影响,则需要绘制新的背景覆盖原背景
import pygame pygame.init() """创建游戏主窗口""" window = pygame.display.set_mode((480, 700)) """加载需要的图片""" background = pygame.image.load("./游戏素材/background.png") me1 = pygame.image.load("./游戏素材/me1.png") me2 = pygame.image.load("./游戏素材/me2.png") """设定一个时钟""" clock = pygame.time.Clock() while True: """绘制背景和飞机""" window.blit(background, (0, 0)) window.blit(me2, (189, 574)) """刷新显示""" pygame.display.update() clock.tick(5) """重新绘制背景,以覆盖原来的图像""" window.blit(background, (0, 0)) window.blit(me1, (189, 574)) """刷新显示""" pygame.display.update() clock.tick(5)
事件 event,即游戏启动后,用户针对游戏所做的交互操作,如:按下键盘,点击鼠标
Python中提供了一个 pygame.event.get() 方法可以获得用户当前所做的动作的事件列表,用户可以在同一时间做很多事件,该方法的返回值即为游戏主窗口上发生的事件列表
例如在上面的例子中添加如下代码可以监听事件:
event_list = pygame.event.get()
if len(event_list) > 0:
print(event_list)
例:监听用户 × 退出 游戏界面
"""游戏循环"""
while True:
"""设置屏幕刷新帧率"""
clock.tick(60)
"""事件监听"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
print("退出游戏...")
pygame.quit()
"""直接退出游戏"""
exit()
提示:这段代码非常的固定,几乎所有的pygame游戏都大同小异
在上述的开发中,图像加载、位置变化、绘制图像都需要我们自己编写代码来分别进行处理,而为了简化这些开发步骤,pygame提供了两个类:
pygame.sprite.Sprite 和 pygame.sprite.Group 自带的方法如下图所示:
精灵规定两个固有实例属性:
self.image属性 为图像,一般通过 pygame.image.load(图片路径) 来加载获得
self.rect属性 为在游戏主窗口上的位置,一般通过 self.image.get_rect() 获得
pygame.image.load(图片路径) 方法中,图片路径可以是绝对路径,也可以是相对路径,用于将图片加载到内存中,返回一个图像对象,一般应将图片放在当前工程文件下
self.image.get_rect() 方法会返回同 pygame.Rect(0, 0, 图像宽, 图像高) 类似的对象,注意:所在坐标系的位置为(0, 0),大小为 self.image 的大小,坐标位置可以通过其返回值对象的属性 x 来对横坐标进行调整
精灵和精灵组的原生内置方法都是基于 image 和 rect 这两个名字来调用的属性的,因此,如果不按这两个名字来赋予属性,那么精灵和精灵组的内置方法将不可用,精灵就变成具空壳(重要)
精灵需要派生子类,这是因为原生精灵的 __init__() 方法没有定义 self.image 和 self.rect——当然,这个肯定是留给开发者来写的,精灵用什么图像,大小如何,自然是由开发者来决定
我们需要通过派生类来重写 __init__() 方法,来传递 self.image 和 self.rect 需要的参数,不过在此之前我们需要用 super().init() 调用一下精灵的初始化方法,因为里面有定义其他内容,否则直接重写方法会覆盖掉(重要)
image 是精灵图像的图片, rect一般通过self.image.get_rect() 方法获得,只要我们得到了 image,那么 rect 也自然得到
其次,精灵规定了一个固有方法:update(),但没有内容,需要在派生类中重写,这个方法用来写精灵的运动,通过 self.rect.x 和 self.rect.y 来设计
在创建完精灵后,我们需要把同一派生类的精灵归入一个精灵组,通过:
精灵组名 = pygame.sprite.Group(精灵1, 精灵2…精灵N) 来归入同一个精灵组
当然,如果想要向已定义的精灵组中增加精灵,可以调用 精灵组名.add(精灵列表) 方法
当一个精灵阵亡了——即不需要再绘制在屏幕上了,则需要该精灵移出精灵组,调用精灵的 kill() 方法可以把精灵移出其所属的精灵组,同时该精灵的内存会被释放
对于同一个精灵组的精灵来说,精灵组名.update() 方法可以让属于该精灵组的所有精灵调用精灵的 update() 方法,即调用该方法后,该精灵组的所有精灵发生 update() 规定的运动,这也是为什么精灵里有一个固有方法 update() 的原因,是因为精灵组的 update() 方法是基于精灵的 update() 方法来写的(重要)
对于同一个精灵组的精灵来说, draw(Surface) 方法可以让属于该精灵组的所有精灵绘制在游戏主窗口 Surface 上(重要)
draw(Surface) 中 Surface 传递的是游戏主窗口对象
图中 screen 指的是游戏主窗口
调用 random 模块可以实现随机创建不同类型的敌机;随机设置敌机不同的出场位置;随机设置敌机不同的出场速度
创建游戏精灵类——派生于精灵类:
新建 plane_sprites.py 文件,定义游戏精灵类 GameSprite 继承自 pygame.sprite.Sprite
属性:
方法:
注:
plane_sprites.py 文件的 代码:
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):
"""精灵在游戏主窗口垂直方向下移动"""
self.rect.y += self.speed
敌机是游戏中会动的对象,可以作为精灵
需求:
步骤:
职责:
(1) 精灵:
(2) 精灵组:
import pygame from plane_sprites import * pygame.init() """创建游戏主窗口""" window = pygame.display.set_mode((480, 700)) """加载需要的图片""" background = pygame.image.load("./游戏素材/background.png") """创建敌机精灵和精灵组""" enemy_plane1 = GameSprite("./游戏素材/enemy1.png", speed=3) enemy_plane2 = GameSprite("./游戏素材/enemy2.png", speed=2) enemy_plane3 = GameSprite("./游戏素材/enemy3_n1.png") enemy_group = pygame.sprite.Group(enemy_plane1, enemy_plane2, enemy_plane3) """设定一个时钟""" clock = pygame.time.Clock() while True: """绘制背景""" window.blit(background, (0, 0)) """绘制所有敌机精灵""" enemy_group.draw(window) enemy_group.update() """刷新显示""" pygame.display.update() """设置帧率""" clock.tick(60) pygame.quit()
注:image 中的 get_rect() 方法默认返回 pygame.Rect(0, 0, 图像宽, 图像高) 的对象,其在坐标系中的位置为(0, 0),但其横坐标可以通过其返回值对象的属性 x 来进行调节
目标:使用面向对象设计飞机大战游戏类
根据需求,我们实际上只需创建2个 .py 文件即可,plane_main.py 文件作为飞机大战游戏的主程序文件, plane_sprites.py 文件作为各个精灵类的定义文件
plane_main.py :
plane_sprites.py:
英雄飞机直接创建,敌机使用精灵与精灵组来创建,需求如下图所示:
plane_sprites.py 文件代码:
import pygame # 屏幕大小的常量 SCREEN_RECT = pygame.Rect(0, 0, 480, 700) class GameSprite(pygame.sprite.Sprite): """游戏主类——继承自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): # 在屏幕的垂直方向x向下移动 self.rect.y += self.speed
注:为了代码的可更改性(需求)与可阅读性——后续修改代码时只需修改常量即可,我们定义了矩形区域对象常量:
——该矩形区域对象用于代表游戏主窗口,因此我们后续将通过:
plane_main.py 文件代码:
from plane_sprites import * class PlaneGame(object): """主游戏类""" def __init__(self): print("游戏正在初始化...") # 创建游戏主窗口 self.screen = pygame.display.set_mode(SCREEN_RECT.size) # 创建游戏时钟 self.clock = pygame.time.Clock() # 调用私有方法,创建精灵和精灵组 self.__create_sprites() def __create_sprites(self): """用于创建精灵和精灵组""" pass def start_game(self): """启动游戏""" # 游戏主循环 while True: # 设置刷新帧率为60 self.clock.tick(120) # 事件监听 self.__event_handler() # 碰撞检测 self.__check_collide() # 位置更新 self.__update_sprites() # 游戏主窗口刷新显示 pygame.display.update() def __check_collide(self): """碰撞检测""" pass def __event_handler(self): """事件监听""" pass def __update_sprites(self): """位置更新""" self.background_group.update() self.background_group.draw(self.screen) @staticmethod def __game_over(): # 结束游戏 print("游戏结束...") pygame.quit() exit() if __name__ == '__main__': # 初始化pygame pygame.init() # 创建游戏对象 game = PlaneGame() # 启动游戏 game.start_game()
虽然在 三、游戏主功能 5.英雄飞机的正确动画显示 中,我们讨论了游戏飞机向上移动的问题
但实际上,在许多跑酷类游戏的开发套路中,我们使用背景图像的运动来代替英雄的运动,那么英雄只需停留在屏幕的某一位置(可以左右移动),即可在视觉上产生英雄飞机正在向前移动的错觉
思路构图如图所示:
实现上述功能即可实现图像的连续滚动。在原 plane_sprites.py 文件中,我们只定义了敌机精灵的向下移动,而屏幕精灵也可以使用这个向下移动方法,但需要增加屏幕图像的轮换滚动,就需要在原 GameSprite 类中派生一个子类,再重写 update() 方法(这个方法扩展了对图像位置的判断);其次,我们还需对方法 __init__(…) 进行扩展,即在创建背景图像精灵时,判断创建的是不是背景图像2,如果是背景图像2,则应该设置其初始位置所在坐标应为游戏主窗口的正上方
plane_sprites.py 文件新增代码——BackGround 类:
class BackGround(GameSprite):
"""背景图像类,继承自GameSprite"""
def __init__(self, is_alt=False):
# 调用父类方法创建精灵对象
super().__init__("./游戏素材/background.png")
# 判断是否为背景图像2,若是则改变初始坐标位置
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
根据上述思想所得——类的继承关系:
补全部分pass代码和其他部分代码后的代码——主框架:
plane_sprites.py 文件代码:
import pygame # 屏幕大小的常量 SCREEN_RECT = pygame.Rect(0, 0, 480, 700) class GameSprite(pygame.sprite.Sprite): """游戏主类——继承自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): # 在屏幕的垂直方向x向下移动 self.rect.y += self.speed class BackGround(GameSprite): """背景类,继承自GameSprite""" def __init__(self, is_alt=False): # 调用父类方法创建精灵对象 super().__init__("./游戏素材/background.png") # 判断是否为背景图像2,若是则改变初始坐标位置 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
plane_main.py 文件代码:
from plane_sprites import * class PlaneGame(object): """主游戏类""" def __init__(self): print("游戏正在初始化...") # 创建游戏主窗口 self.screen = pygame.display.set_mode(SCREEN_RECT.size) # 创建游戏时钟 self.clock = pygame.time.Clock() # 调用私有方法,创建精灵和精灵组 self.__create_sprites() def __create_sprites(self): # 创建背景精灵 background1 = BackGround() background2 = BackGround(is_alt=True) # 创建背景精灵组 self.background_group = pygame.sprite.Group(background1, background2) def start_game(self): """启动游戏""" # 游戏主循环 while True: # 设置刷新帧率为60 self.clock.tick(120) # 事件监听 self.__event_handler() # 碰撞检测 self.__check_collide() # 位置更新 self.__update_sprites() # 游戏主窗口刷新显示 pygame.display.update() def __check_collide(self): """碰撞检测""" pass def __event_handler(self): """事件监听""" for event in pygame.event.get(): if event.type == pygame.QUIT: PlaneGame.__game_over() def __update_sprites(self): """位置更新""" self.background_group.update() self.background_group.draw(self.screen) @staticmethod def __game_over(): # 结束游戏 print("游戏结束...") pygame.quit() exit() if __name__ == '__main__': # 初始化pygame pygame.init() # 创建游戏对象 game = PlaneGame() # 启动游戏 game.start_game()
约定:
定时器:
在pygame中可以使用 pygame.time.set_time() 来添加定时器,所谓定时器,就是中断——即每隔固定的一个时间段就发出一次信号
pygame.time.set_time(eventid, millisecond)
添加定时器,没有返回值
eventid 事件代号,需要基于常量 pygame.USEREVENT来指定,USEREVENT 是一个整数,再增加的事件可以使用 USEREVERT + 1 指定,以此类推
millisecond 单位:毫秒,即定时器的触发时间间隔
提示:设置定时器后,定时器每隔一段时间就会发出一个事件——eventid,而这个事件是可以被pygame.event.get() 监听到的,因此,我们只需在 for event in pygame.event.get() 中设定当监听到 事件eventid 需要做的事情即可
pygame的定时器使用套路十分固定:
在plane_sprites.py 文件的顶部定义:
把 pygame.USEREVENT 记作 CREATE_ENEMY_EVENT事件
# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
在plane_main.py 文件的 PlaneGame 类的 __init__() 中增加 定时创建敌机事件 的设置:
class PlaneGame(object):
"""主游戏类"""
def __init__(self):
print("游戏正在初始化...")
# 创建游戏主窗口
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
# 创建游戏时钟
self.clock = pygame.time.Clock()
# 调用私有方法,创建精灵和精灵组
self.__create_sprites()
# 设置定时器事件——每隔1秒创建敌机
pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
在plane_main.py 文件的 __event_handler() 方法中监听该事件:
def __event_handler(self):
"""事件监听"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
PlaneGame.__game_over()
elif event.type == CREATE_ENEMY_EVENT:
pass
设计 Enemy 类:
由于每驾敌机在游戏主窗口中向下方飞行,飞行速度各不相同,且每架敌机出现的水平位置也不尽相同;此外,当敌机飞出屏幕后,应该把对应的精灵从精灵组中删除,要实现这些特有的功能,就要在原 GameSprite 类中派生一个子类 enemy,扩展功能:
因此新的架构图如图所示:
为了实现随机化,我们需要导入 random 模块
提示:self.rect 中有一个属性:self.rect.bottom,实际上就是精灵图像的底部y轴的位置,即也可以设置 self.rect.bottom = 0,即为 self.rect.y = -self.rect.height 的意思
在plane_sprites.py 文件中新增代码——Enemy 类:
import random class Enemy(GameSprite): """敌机精灵类,继承自GameSprite""" def __init__(self): # 随机抽取敌机 number = random.randint(1, 3) if number == 1: # 调用父类方法创建精灵对象 super().__init__("./游戏素材/enemy1.png") # 随机抽取出场位置 elif number == 2: # 调用父类方法创建精灵对象 super().__init__("./游戏素材/enemy2.png") elif number == 3: # 调用父类方法创建精灵对象 super().__init__("./游戏素材/enemy3_n1.png") # 随机抽取出场位置 self.rect.x = random.randrange(0, (SCREEN_RECT.width - self.rect.width), self.rect.width) # 随机抽取出场速度 self.speed = random.randint(1, 3) # 初始位置应该在游戏主窗口的上方 self.rect.y = -self.rect.height def update(self): # 调用父类方法——向下移动 super().update() # 判断是否飞出屏幕,是则移出精灵组释放内存 if self.rect.y >= SCREEN_RECT.height: self.kill()
在plane_main.py 文件的 PlaneGame 类的 __create_sprites(self) 中创建敌机精灵组:
def __create_sprites(self):
# 创建背景精灵
background1 = BackGround()
background2 = BackGround(True)
# 创建背景精灵组
self.background_group = pygame.sprite.Group(background1, background2)
# 创建敌机精灵组
self.enemy_group = pygame.sprite.Group()
在plane_main.py 文件的 __event_handler() 方法中监听的定时器事件,将pass改为创建敌机,该事件就是用来间隔地创建敌机精灵的:
def __event_handler(self):
"""事件监听"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
PlaneGame.__game_over()
elif event.type == CREATE_ENEMY_EVENT:
# 创建敌机精灵同时加入精灵组
self.enemy_group.add(Enemy())
在 plane_main.py 文件的 __update_sprites() 方法中的代码如下:
def __update_sprites(self):
"""位置更新"""
self.background_group.update()
self.background_group.draw(self.screen)
self.enemy_group.update()
self.enemy_group.draw(self.screen)
英雄飞机类——Hero类 需求:
子弹类——Bullet类 需求:
英雄飞机——设计 Hero 类:
提示:self.rect 中有一个属性:self.rect.centerx,实际上就是精灵图像中心的横坐标的位置,可以设置 self.rect.centerx = SCREEN_RECT.centerx,即让精灵图像属于游戏主窗口的中心位置。同理,还有 self.rect.centery 图像中心的纵坐标属性…
提示:self.rect 中有一个属性:self.rect.right,实际上就是精灵图像右边界的坐标;代码中 elif self.rect.right > SCREEN_RECT.width: 就是判断图像的右边界是否越界。同理,还有 self.rect.left 左边界属性…
在plane_sprites.py 文件的中新增代码——Hero类,定义 fire() 英雄飞机发射子弹的方法——先用pass跳过:
class Hero(GameSprite): """英雄飞机类,继承自GameSprite""" def __init__(self): # 设置速度为0 super().__init__("./游戏素材/me1.png", speed=0) # 位于游戏主窗口的中央 self.rect.centerx = SCREEN_RECT.centerx self.rect.bottom = SCREEN_RECT.height - 10 def update(self): # 英雄飞机在水平方向移动且不能移出边界 if self.rect.x < 0: self.rect.x = 0 elif self.rect.right > SCREEN_RECT.width: self.rect.right = SCREEN_RECT.width else: self.rect.x += self.speed def fire(self): """英雄飞机发射子弹""" pass
在plane_main.py 文件的 __create_sprites 方法中增加英雄飞机对象的定义和英雄飞机精灵组的定义:
def __create_sprites(self):
# 创建背景精灵
background1 = BackGround()
background2 = BackGround(True)
# 创建英雄飞机精灵——作为属性
self.hero_plane = Hero()
# 创建背景精灵组
self.background_group = pygame.sprite.Group(background1, background2)
# 创建游戏飞机精灵组
self.hero_group = pygame.sprite.Group(self.hero_plane)
# 创建敌机精灵组
self.enemy_group = pygame.sprite.Group()
在plane_main.py 文件的 __update_sprites 方法中调用 update() 和 draw() 方法:
def __update_sprites(self):
"""位置更新"""
self.background_group.update()
self.background_group.draw(self.screen)
self.enemy_group.update()
self.enemy_group.draw(self.screen)
self.hero_group.update()
self.hero_group.draw(self.screen)
在pygame中针对键盘按键的捕获:
首先使用 pygame.key.get_pressed() 返回所有按键的元组,然后通过键盘常量——下标,判断元组中某一个按键是否被按下——如果被按下,对应的数值为1,否则为0
键盘常量实际上是 pygame 内部定义的常量,实际上是一个整型数:
pygame中的键盘常量 | 含义 |
---|---|
K_RIGHT | 键盘的 → 按键 |
K_LEFT | 键盘的←按键 |
K_UP | 键盘的 ↑ 按键 |
K_DOWN | 键盘的 ↓ 按键 |
K_a | 键盘的 a 按键 |
… | … |
由于键盘常量实际上是在 pygame 内部定义一个整型数常量,因此可以直接使用 键盘常量 作为下标来在按键元组中找到对应的按键,当元组中某一按键对应的值为1时,则表示已按下
在plane_main.py 文件的 __event_handler() 事件监听方法中增加键盘按键判断:
def __event_handler(self): """事件监听""" for event in pygame.event.get(): if event.type == pygame.QUIT: PlaneGame.__game_over() elif event.type == CREATE_ENEMY_EVENT: # 创建敌机精灵同时加入精灵组 self.enemy_group.add(Enemy()) # 按键判断 keys_pressed = pygame.key.get_pressed() if keys_pressed[pygame.K_RIGHT]: self.hero_plane.speed = 1 if keys_pressed[pygame.K_LEFT]: self.hero_plane.speed = -1 else: # 当没有按下左右方向键时,速度应该设置为0 self.hero_plane.speed = 0
发射子弹:英雄飞机每隔 0.5 秒发射一次子弹,每次三连发,由其特性可知,可以使用定时器来实现这个功能
还是那套固定的定时器使用套路:
在plane_sprites.py 文件的顶部定义:
把 pygame.USEREVENT + 1 记作 HERO_FIRE_EVENT 事件
# 英雄飞机发射子弹的事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1
在plane_main.py 文件的 PlaneGame 类的 __init__() 中增加 英雄飞机定时发射子弹事件 的设置:
class PlaneGame(object):
"""主游戏类"""
def __init__(self):
print("游戏正在初始化...")
# 创建游戏主窗口
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
# 创建游戏时钟
self.clock = pygame.time.Clock()
# 调用私有方法,创建精灵和精灵组
self.__create_sprites()
# 设置定时器事件——每隔1秒创建敌机
pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)
# 设置定时器事件——每隔0.5秒发射一次子弹
在plane_main.py 文件的 __event_handler() 方法中监听该事件——执行 fire() 发射子弹方法:
def __evnet_handler(self): """事件监听""" for event in pygame.event.get(): if event.type == pygame.QUIT: PlaneGame.__game_over() elif event.type == CREATE_ENEMY_EVENT: # 创建敌机精灵同时加入精灵组 self.enemy_group.add(Enemy()) elif event.type == HERO_FIRE_EVENT: self.hero_plane.fire() # 按键判断 keys_pressed = pygame.key.get_pressed() if keys_pressed[pygame.K_RIGHT]: self.hero_plane.speed = 2 elif keys_pressed[pygame.K_LEFT]: self.hero_plane.speed = -2 else: # 当没有按下左右方向键时,速度应该设置为0 self.hero_plane.speed = 0
子弹——设计 Bullet 类:
以下操作与敌机 Enemy 类的设计十分相识:
在plane_sprites.py 文件中新增代码——Bullet 类:
class Bullet(GameSprite):
"""子弹类,继承自GameSprite"""
def __init__(self):
super().__init__("./游戏素材/bullet1.png", speed=-2)
def update(self):
# 调用父类方法——向下移动
super().update()
# 判断子弹是否飞出屏幕,是则释放
if self.rect.bottom <= 0:
self.kill()
在plane_sprites.py 文件的 Hero 类的 __init__(self) 中创建子弹精灵组:
class Hero(GameSprite):
"""英雄飞机类,继承自GameSprite"""
def __init__(self):
# 设置速度为0
super().__init__("./游戏素材/me1.png", speed=0)
# 位于游戏主窗口的中央
self.rect.centerx = SCREEN_RECT.centerx
self.rect.bottom = SCREEN_RECT.height - 10
# 创建子弹精灵组
self.bullet_group = pygame.sprite.Group()
在 plane_main.py 文件的 __update_sprites() 方法中的代码如下:
def __update_sprites(self):
"""位置更新"""
self.background_group.update()
self.background_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_plane.bullet_group.update()
self.hero_plane.bullet_group.draw(self.screen)
在plane_sprites.py 文件的 Hero 类的 fire() 中,实现子弹的发射和三连发功能:
def fire(self):
"""英雄飞机发射子弹"""
for i in (0, 1, 2):
# 创建子弹精灵
bullet = Bullet()
# 设定子弹精灵的位置,应该与英雄飞机的正上方中央发射
bullet.rect.y = self.rect.y - 2 * i * bullet.rect.height
bullet.rect.centerx = self.rect.centerx
# 子弹精灵加入精灵组
self.bullet_group.add(bullet)
在pygame提供了两个非常方便的方法可以实现碰撞检测
pygame.sprite.groupcollide()
两个精灵组的精灵之间的碰撞检测
pygame.sprite.spritecollide()
某个精灵和指定精灵组之间的碰撞检测
在 plane_main.py 文件的 __check_collide() 方法中的代码如下:
def __check_collide(self):
"""碰撞检测"""
# 子弹与敌机之间的碰撞检测
pygame.sprite.groupcollide(self.enemy_group, self.hero_plane.bullet_group, True, True)
# 英雄飞机与敌机之间的碰撞检测
enemy_list = pygame.sprite.spritecollide(self.hero_plane, self.enemy_group, True)
# 如果发生了碰撞,游戏结束
if len(enemy_list) > 0:
self.hero_plane.kill()
self.__game_over()
至此,飞机大战完成
import random import pygame # 屏幕大小的常量 SCREEN_RECT = pygame.Rect(0, 0, 480, 700) # 创建敌机的事件 CREATE_ENEMY_EVENT = pygame.USEREVENT # 英雄飞机发射子弹的事件 HERO_FIRE_EVENT = pygame.USEREVENT + 1 class GameSprite(pygame.sprite.Sprite): """游戏主类——继承自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 class BackGround(GameSprite): """背景类,继承自GameSprite""" def __init__(self, is_alt=False): # 调用父类方法创建精灵对象 super().__init__("./游戏素材/background.png") # 判断是否为背景图像2,若是则改变初始坐标位置 if is_alt: self.rect.bottom = 0 def update(self): # 调用父类方法——向下移动 super().update() if self.rect.y >= SCREEN_RECT.height: self.rect.bottom = 0 class Enemy(GameSprite): """敌机精灵类,继承自GameSprite""" def __init__(self): # 随机抽取敌机 number = random.randint(1, 3) if number == 1: # 调用父类方法创建精灵对象 super().__init__("./游戏素材/enemy1.png") # 随机抽取出场位置 elif number == 2: # 调用父类方法创建精灵对象 super().__init__("./游戏素材/enemy2.png") elif number == 3: # 调用父类方法创建精灵对象 super().__init__("./游戏素材/enemy3_n1.png") # 随机抽取出场位置 self.rect.x = random.randrange(0, (SCREEN_RECT.width - self.rect.width), 1) # 随机抽取出场速度 self.speed = random.randint(1, 3) # 初始位置应该在游戏主窗口的上方 self.rect.bottom = 0 def update(self): # 调用父类方法——向下移动 super().update() # 判断是否飞出屏幕,是则释放 if self.rect.y >= SCREEN_RECT.height: self.kill() class Hero(GameSprite): """英雄飞机类,继承自GameSprite""" def __init__(self): # 设置速度为0 super().__init__("./游戏素材/me1.png", speed=0) # 位于游戏主窗口的中央 self.rect.centerx = SCREEN_RECT.centerx self.rect.bottom = SCREEN_RECT.height - 10 # 创建子弹精灵组 self.bullet_group = pygame.sprite.Group() def update(self): # 英雄飞机在水平方向移动且不能移出边界 if self.rect.x < 0: self.rect.x = 0 elif self.rect.right > SCREEN_RECT.width: self.rect.right = SCREEN_RECT.width else: self.rect.x += self.speed def fire(self): """英雄飞机发射子弹""" for i in (0, 1, 2): # 创建子弹精灵 bullet = Bullet() # 设定子弹精灵的位置,应该与英雄飞机的正上方中央发射 bullet.rect.y = self.rect.y - 2 * i * bullet.rect.height bullet.rect.centerx = self.rect.centerx # 子弹精灵加入精灵组 self.bullet_group.add(bullet) class Bullet(GameSprite): """子弹类,继承自GameSprite""" def __init__(self): super().__init__("./游戏素材/bullet1.png", speed=-3) def update(self): # 调用父类方法——向下移动 super().update() # 判断子弹是否飞出屏幕,是则释放 if self.rect.bottom <= 0: self.kill()
from plane_sprites import * class PlaneGame(object): """主游戏类""" def __init__(self): print("游戏正在初始化...") # 创建游戏主窗口 self.screen = pygame.display.set_mode(SCREEN_RECT.size) # 创建游戏时钟 self.clock = pygame.time.Clock() # 调用私有方法,创建精灵和精灵组 self.__create_sprites() # 设置定时器事件——每隔1秒创建敌机 pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000) # 设置定时器事件——每隔0.5秒发射一次子弹 pygame.time.set_timer(HERO_FIRE_EVENT, 500) def __create_sprites(self): # 创建背景精灵 background1 = BackGround() background2 = BackGround(True) # 创建英雄飞机精灵——作为属性 self.hero_plane = Hero() # 创建背景精灵组 self.background_group = pygame.sprite.Group(background1, background2) # 创建游戏飞机精灵组 self.hero_group = pygame.sprite.Group(self.hero_plane) # 创建敌机精灵组 self.enemy_group = pygame.sprite.Group() def start_game(self): """启动游戏""" # 游戏主循环 while True: # 设置刷新帧率为60 self.clock.tick(120) # 事件监听 self.__event_handler() # 碰撞检测 self.__check_collide() # 位置更新 self.__update_sprites() # 游戏主窗口刷新显示 pygame.display.update() def __check_collide(self): """碰撞检测""" # 子弹与敌机之间的碰撞检测 pygame.sprite.groupcollide(self.enemy_group, self.hero_plane.bullet_group, True, True) # 英雄飞机与敌机之间的碰撞检测 enemy_list = pygame.sprite.spritecollide(self.hero_plane, self.enemy_group, True) # 如果发生了碰撞,游戏结束 if len(enemy_list) > 0: self.hero_plane.kill() self.__game_over() def __event_handler(self): """事件监听""" for event in pygame.event.get(): if event.type == pygame.QUIT: PlaneGame.__game_over() elif event.type == CREATE_ENEMY_EVENT: # 创建敌机精灵同时加入精灵组 self.enemy_group.add(Enemy()) elif event.type == HERO_FIRE_EVENT: self.hero_plane.fire() # 按键判断 keys_pressed = pygame.key.get_pressed() if keys_pressed[pygame.K_RIGHT]: self.hero_plane.speed = 2 elif keys_pressed[pygame.K_LEFT]: self.hero_plane.speed = -2 else: # 当没有按下左右方向键时,速度应该设置为0 self.hero_plane.speed = 0 def __update_sprites(self): """位置更新""" self.background_group.update() self.background_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_plane.bullet_group.update() self.hero_plane.bullet_group.draw(self.screen) @staticmethod def __game_over(): # 结束游戏 print("游戏结束...") pygame.quit() exit() if __name__ == '__main__': # 初始化pygame pygame.init() # 创建游戏对象 game = PlaneGame() # 启动游戏 game.start_game()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。