目录

python-基础学习六pygame游戏开发飞机大战

python | 基础学习(六)pygame游戏开发:飞机大战

文章目录

一、pygame模块

pygame 模块,转为电子游戏设计

1、安装:

$ sudo pip3 install pygame

windows: pip install pygame

2、使用pygame创建图形窗口

(1)新建项目 飞机大战

(2)新建文件 pygame.py

(3)建立游戏窗口:

①pygame的初始化和退出

pygame.init() :导入并初始化所有pygame模块,使用其他模块之前,必须先调用init方法。

pygame.quit() :卸载所有pygame模块,在游戏结束之前调用。

代码框架

import pygame 

pygame.init()

print("游戏的代码")

pygame.quit()
②pygame的坐标系

原点在左上角 (0, 0)

x轴 水平方向向右,逐渐增加

y轴 垂直方向向下,逐渐增加

在游戏中,所有可见的元素都是以 矩形区域 来描述位置的。四要素 (x, y)(width, height)(x, y) 指定矩形的左上角位置, (width, height) 指定矩形区域的大小。

pygame专门提供了一个类 pygame.Rect 用于描述矩形区域,【其中,size表示(width, height)】:

Rect(x, y, width, height)

https://i-blog.csdnimg.cn/blog_migrate/0e3e02d2c32b1242575c4142022097d2.png#pic_center

import pygame

hero_rect = pygame.Rect(100, 500, 120, 125)
print("hero的原点%d %d" % (hero_rect.x, hero_rect.y))
print("hero的尺寸%d %d" % (hero_rect.width, hero_rect.height))
print("hero的size%d %d" % hero_rect.size )

输出:

https://i-blog.csdnimg.cn/blog_migrate/5512693795a6d64690f786e2b1d6aec4.png#pic_center

③创建游戏主窗口pygame.display

pygame.diaplay.set_mode() :初始化游戏显示窗口

set_mode 方法:

det_mode(resolution = (0, 0), flags = 0, depth = 0)

参数 :(因为每个参数都有 = ,也就是都有默认值,因此调用时不需要指定值)

resolution:指定屏幕的 宽、高 ,默认创建的窗口大小和屏幕大小一致

flags:参数指定屏幕的附加选项,例如是否全屏等等,默认不需要传递

depth:参数表示颜色的位数,默认自动匹配

返回值

暂时可以理解为 游戏的屏幕游戏的元素 都需要被绘制到 游戏的屏幕上

例如:指定screen接收返回值。

screen = pygame.display.set_mode((480, 700))。

pygame.display.update() :刷新屏幕内容显示,稍后使用

④导入游戏背景图片

1、将图片加载到内存中。 pygame.image.load() 加载图像的数据

:(./images/1.jpg)其中的 . 表示当前目录

bg = pygame.image.load("D:/My life/music/some photos/sunrise.jpg")

2、使用 游戏屏幕 对象,调用 bilt(图像,位置) 方法将图像绘制到指定位置

screen.blit(bg, (0, 0)) # (0, 0)表示从屏幕左上角开始绘制

3、调用 pygame.display.update() 方法更新整个屏幕的显示。

:如果想在屏幕上看到绘制的结果,一定要调用这个方法

pygame.display.update()

运行结果:

https://i-blog.csdnimg.cn/blog_migrate/1fc9f68baab5c4025c4ca3cd934853b5.png#pic_center

⑤把一些精致的图像绘制到游戏窗口中

1、加载hero_plane图像

png 格式是支持透明的。在绘制图像时,透明区域不会显示任何内容。如果下方有内容,会透过 透明区域 显示出来

hero = pygame.image.load("D:/My life/music/some photos/plane.png")

2、将其绘制在屏幕的(150, 500)位置

screen.blit(hero, (150, 500))

3、调用屏幕更新显示飞机图像

pygame.diaplay.update()

输出:

https://i-blog.csdnimg.cn/blog_migrate/0ed79f0dadb30db1489e2663aef7c96c.png#pic_center

注: screen.blit 方法可以在屏幕上绘制很多图像,这些图像之间可能会彼此重叠。编程时可以在screen对象 完成所有bilt 之后, 统一执行一次 display.update() 。而不是每执行一步update一次,可以减少程序的繁琐。

3、游戏循环

while True: 称为游戏循环(无限循环、死循环)。进入 游戏循环 ,意味着游戏正式开始。

设置刷新频率

检测用户交互

更新所有图像位置

更新屏幕显示

①动画效果——游戏时钟

根据 用户的交互 或者其他情况,快速 移动 这些图像,产生动画效果。一般在电脑上 每绘制60次 ,就能够达到非常 连续 高品质 的效果。每次绘制的结果被称为 帧Frame

pygame专门提供了一个类 pygame.time.Clock 可以非常方便地设置屏幕绘制速度。

1、在游戏初始化 创建一个时钟对象

2、在游戏循环中让时钟对象调用 tick(频率) 方法。

如: clock.tick(20) 表示每秒20次

tick 方法会根据上次被调用的时间,自动设置 游戏循环 中的延时。

代码示例:

i = 0
while True:
	clock.tick(60)
	print(i)

②动画的简单实现

每次调用 update() 之前,需要把所有的游戏图像都重新绘制一遍,而且 最先 重新绘制 背景图像

clock = pygame.time.Clock()
plane_rect = pygame.Rect(250, 500, 107, 113)  # 107和113表示所插入的飞机图片的宽度和高度

#死循环,确保上方执行的游戏界面不会退出
while True:
    # 1、指定循环体内部代码执行的频率
    clock.tick(60)

    # 2、修改飞机的位置
    plane_rect.y -= 1
    # 如果移出顶部,就从底部重新开始移动
    if plane_rect.y <= -113:  # -113表示导入飞机图片的高度
        plane_rect.y = 700

    # 3、调用blit方法绘制图像
    screen.blit(bg, (0, 0))# 先绘制背景图像,再绘制飞机,这样就可以遮住上一次飞机的残影
    screen.blit(plane, plane_rect)

    # 4、调用update方法更新显示
    pygame.display.update()

③在游戏循环中监听事件

监听 :在 游戏循环 中,判断用户 具体的操作(也称为“事件”) 。只有 捕获 到用户的具体操作,才能有针对性地作出相应。

pygame中通过 pygame.event.get() 可以获得 用户当前所做的动作事件列表 ,用户可以同一时间做很多事情。

下面这段代码非常固定,几乎所有的pygame游戏都大同小异。

#游戏循环
while True:

	#设置屏幕刷新频率
	clock.tick(60)
	
	#事件监听
	for event in pygame.event.get():
		
		#判断用户是否点击了关闭按钮
		if event.type == pygame.QUIT:
			print("退出游戏...")

			#卸载所有的模块
			pygame.quit()

			#直接退出系统,终止当前正在执行的程序
			exit()

④精灵sprite.Sprite和精灵组sprite.Group

为了简化开发步骤,pygame提供了两个类:

pygame.sprite.Sprite——存储 图像数据image位置Rect对象

pygame.sprite.Group

https://i-blog.csdnimg.cn/blog_migrate/1af022831cefe541a7937d734b892de7.png#pic_center

⑤派生精灵子类

从本节开始,面向对象开发

新建plane_sprites.py文件

定义GameSprite继承自pygame.sprite.Sprite

注意:

如果一个类的父类不是object,在重写 初始化方法 时,一定要先super()一下父类的初始化 __init__ 方法,保证父类中实现的 __init__ 代码能够被正常执行。

https://i-blog.csdnimg.cn/blog_migrate/f1c466fe7c46b1c777e7592250c7f748.png#pic_center

属性:

image 精灵图像,使用 image_name 加载

rect 精灵大小,默认使用图像大小

speed 精灵移动速度,默认为 1

方法:

update 每次更新屏幕时在游戏循环内调用。

让精灵 self,rect.y += self.speed

提示:

imageget_rect() 方法,可以返回 **pygame.Rect(0, 0, 图像宽,图像高)**的对象。

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、使用 from 导入 plane_sprite 模块

from 导入的模块可以 直接使用

import 导入的模块需要通过 模块名. 来使用

from plane_sprites import *

可以直接使用模块的工具,而不用

模块名.

2、在游戏初始化 创建 精灵对象精灵组对象

#创建敌机的精灵

enemy = GameSprite("D:/My life/music/some photos/enemyplane.png")

enemy1 = GameSprite("D:/My life/music/some photos/enemyplane.png", 2)

第二架敌机的速度是2

#创建敌机的精灵组

enemy_group = pygame.sprite.Group(enemy, enemy1)

3、在 游戏循环中精灵组 分别调用 update()draw(screen) 方法

#1、update

enemy_group.update()

#2、draw 在screen上绘制所有的精灵

enemy_group.draw(screen)

职责:

精灵

封装 图像image、位置rect 和速度speed

提供 update() 方法、根据游戏需求、 更新位置rect

精灵组

包含 多个 精灵对象

update 方法,让精灵组中的所有精灵各自调用各自的 update 方法更新位置;

draw(screen) 方法,在 screen 上绘制精灵组中的所有精灵

4、游戏框架搭建

目标——使用 面向对象 设计 飞机大战游戏类。

(1)明确主程序职责

一个游戏的主程序有两个职责:

游戏初始化

游戏循环

https://i-blog.csdnimg.cn/blog_migrate/acf8449593f3a3572a5c9a18f0f70eab.png#pic_center

https://i-blog.csdnimg.cn/blog_migrate/18cd70c8a5ba0ddd1a572d71342057ba.png#pic_center

根据 职责 封装 私有方法 ,可以避免某一个方法的代码写得太冗长。如果某一个方法编写得太长,不方便阅读。

(2)实现飞机大战主游戏类

明确文件职责

https://i-blog.csdnimg.cn/blog_migrate/9f9e292cf31cb1f2280d7fc46ed0b3c5.png#pic_center

plane_main

封装 主游戏类

创建 游戏对象

启动游戏

plane_sprites

封装游戏中 所有 需要使用的 精灵子类

提供游戏的 相关工具

常量

变量:可以变化的量

常量:不变化的量。

在开发时,可能会需要使用 固定的值 ,这个时候,建议 不要 直接使用固定的数值,而应该使用 常量 。这样也可以保证代码的可维护性。

常量的定义

定义常量的语法与定义变量一样,都是使用 赋值语句

常量的命名应该 所有字母都使用大写,单词与单词之间使用下划线连接

常量的好处

阅读代码时,通过 常量名 见名知意,不用猜测数字的含义。

如果需要调整值,只需要 修改常量定义 就可以实现 统一修改

#屏幕大小的常量

SCREN_RECT = pygame.Rect(0, 0, 480, 700)

使用时仅需要 SCREN_RECT.size 就可以表示(480, 700)

#刷新的帧率

FRAME_PER_SECOND = 60

(3)游戏背景

①背景交替滚动的思路

在游戏启动后, 背景图像 会连续不断地 向下方 移动, 游戏主角的位置 始终保持不变,在 视觉上 产生英雄的飞机不断向上飞行的 错觉 ——在很多跑酷类游戏的套路。

解决办法:

1、创建2张背景图像精灵:第1张完全和屏幕重合,第2张在屏幕的 正上方

2、两张图像 一起向下运动 self.rect.y += self.speed

3、当任意背景精灵的 rect.y >= 屏幕的高度 说明已经 移动到屏幕下方

4、当移动到屏幕下方的这张图像 设置到 屏幕的正上方 rect.y = -rect.height

update()方法:

判断 是否移出屏幕 ,如果是,将图像设置到 屏幕的正上方 ,从而实现 交替滚动

背景精灵的代码实现:

class Background(GameSprite):
    """游戏背景精灵"""

    def update(self):
        # 1、调用父类的方法实现
        super().update()
        # 2、判断是否移出屏幕,如果移出屏幕,将图片移动到正上方
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height
②在plane_main.py中显示背景精灵

__creat_sprites 方法中创建 精灵 和 精灵组

def __create_sprites(self):
    # 创建背景精灵和精灵组
    bg1 = Background("D:/My life/music/some photos/sunrise.jpg")
    bg2 = Background("D:/My life/music/some photos/sunrise.jpg")
    bg2.rect.y = -bg2.rect.height
    self.back_group = pygame.sprite.Group(bg1, bg2)

__update_sprites 方法中,让 精灵组 调用 update()和draw()方法

def __update_sprites(self):
    self.back_group.update()
    self.back_group.draw(self.screen)
③简化背景精灵的创建

精灵的 初始位置 的设置,由 精灵自己 负责,不由主程序负责。

https://i-blog.csdnimg.cn/blog_migrate/2a55002cd855355b4dff0a993397c4b1.png#pic_center

初始化方法:

直接指定背景图片

is_alt 判断是否是另一张图像

Flase 表示 第一张图像,需要与屏幕重合

True 表示 另一张图像,在屏幕的正上方

继承:

如果父类提供的方法不能满足子类的需求:

派生一个子类

在子类中针对特有的需求,重写父类方法,并且进行扩展

def __init__(self, is_alt = False):  # 默认alt为False
	#1、调用父类方法实现精灵的创建(image / rect / speed)
	super().__init__("D:/My life/music/some photos/sunrise.jpg")
	#2、判断是否是交替图像,如果是,需要设置初始位置
	if is_alt:
		self.rect.y = -self.rect.height

由此一来,主程序的代码就可以简化为:

def __create_sprites(self):
    # 创建背景精灵和精灵组
    bg1 = Background()
    bg2 = Background(True)

    self.back_group = pygame.sprite.Group(bg1, bg2)

(4)敌机

设计Enemy类

①使用定时器添加敌机

在pygame中,可以使用 pygame.time.set_timer() 来添加 定时器(每隔一段时间,去执行一些动作)

set_timer(eventid, milliseconds)

set_timer 可以创建一个事件,可以在 游戏循环 的事件监听方法中捕获到该事件。

第一个参数 事件代号 需要基于常量 pygame.USEREVENT 来指定。

USERENEVT是一个整数,再增加的事件可以使用 USEREVENT + 1 指定,以此类推…

第2个参数是 事件触发 间隔的 毫秒值

通过 pygame.event.get() 获取当前时刻所有的 事件列表

遍历列表 并且判断 event.type 是否等于 eventid ,如果相等,表示 定时器事件 发生。

pygame的定时器使用套路非常固定:

1、定义 定时器常量—— eventid

2、在初始化方法中,调用 set_timer 方法设置定时器事件

3、在游戏循环中,监听定时器事件。

定义事件:

plane_sprites.py 的顶部定义 事件常量

#创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
②设计Enemy类

游戏启动后, 每隔1秒出现一架敌机

每架敌机 向屏幕下方飞行 ,飞行的 速度各不相同

没架敌机出现的 水平位置 也不尽相同

当敌机 从屏幕下方飞出 ,不会再飞回屏幕中

https://i-blog.csdnimg.cn/blog_migrate/1be6b3ef5fca9adb2516c9b696de0bda.png#pic_center

初始化方法:

指定 敌机图片

随机 敌机的初始位置和初始速度

重写update()方法

判断是否飞出屏幕,如果是,从 精灵组 删除

③创建敌机

1、在 __creat_sprites 添加敌机精灵组。

敌机是 定时被创建的 ,因此在初始化方法中,不需要创建敌机

self.enemy_group = pygame.sprite.Group()

2、在 __event_handler 创建敌机,并且添加到精灵组

调用 精灵组add 方法,可以向精灵组添加精灵

#创建敌机

enemy = Enemy()

#将敌机精灵添加到精灵组

self.enemy_group.add(enemy)

3、在 __update_sprites敌机精灵组 调用 updatedraw 方法

self.enemy_group.update()

self.enemy_group.draw(self.screen)

https://i-blog.csdnimg.cn/blog_migrate/6d2cf1d45139dd12252fb0e0a490fde9.png#pic_center

可以看到敌机一架架陆续出现

https://i-blog.csdnimg.cn/blog_migrate/337bf88699d368129878ee6b717dc505.png#pic_center

https://i-blog.csdnimg.cn/blog_migrate/0063246b89d7c076e72ce7b3ad5369ec.png#pic_center

④ 随机 敌机位置和速度
导入模块时,建议按照以下顺序导入

官方标准模块导入

第三方模块导入

应用程序模块导入

修改 plane_sprites.py 增加 random 的导入

import random # 官方标准模块

随机化敌机初始速度

self.speed = random.randint(1, 3) # (随机数的最小值,随机数的最大值)

https://i-blog.csdnimg.cn/blog_migrate/f050270721dbd8e6102f6089996c7505.png#pic_center

敌机从屏幕顶部缓缓进入

self.rect.bottom = 0

随机化敌机出现的水平位置

max_x = SCREEN_RECT.width - self.rect.width

self.rect.x = random.randint(0, max_x)

#水平位置最小值为0,最大位置为“屏幕宽度-飞机宽度”

https://i-blog.csdnimg.cn/blog_migrate/37c5210255bab6203dc4285ff60d7708.png#pic_center

https://i-blog.csdnimg.cn/blog_migrate/026bde0c423cd197e034fc154ff3a6da.png#pic_center

⑤移出屏幕销毁敌机

敌机移出屏幕后,如果没有撞到飞机,敌机的历史生命已经终结。需要从 敌机组 删除,否则会造成 内存浪费

检测敌机被销毁:

__del__ 内置方法会在对象被销毁前调用。在开发中,可以用于 判断对象是否被销毁

def __del__(self):
	print("敌机销毁%s" %self.rect)

https://i-blog.csdnimg.cn/blog_migrate/b475e318976ff6744adb9c43cd09b70b.png#pic_center

判断敌机是否飞出屏幕,如果是,调用 kill() 方法将精灵精灵组中移出,敌机自动销毁

def update(self):
	super().update(args)

	#判断敌机是否移出屏幕
	if self.rect.y >= SCREEN_RECT.height:
		#将精灵从精灵组中删除
		self.kill()

(5)英雄飞机

游戏启动后,飞机出现在屏幕的 水平中间 位置,距离 屏幕底部 120 像素

飞机每隔 0.5 秒发射一次子弹,每次 连发三枚子弹

飞机默认不会移动,需要通过 左 / 右 方向键,控制 英雄 在水平方向移动。

(6)子弹需求

子弹英雄飞机 的正上方发射 沿直线上方 飞行

飞出屏幕后 ,需要从 精灵组 中删除

https://i-blog.csdnimg.cn/blog_migrate/5c6831dae1ee7b6fa708bec3bcb2cfed.png#pic_center

plane_sprites 新建 Hero

初始化方法

指定 英雄图片

初始速度=0 ——英雄默认静止不动

定义 bullets 子弹精灵组 保存子弹精灵

重写update()方法

英雄需要 水平移动

并且需要保证不能 移出屏幕

增加 bullets 属性,记录所有 子弹精灵

增加 file 方法,用于发射子弹

center

centerx = x + 0.5 * width # x的中心点

centery = y + 0.5 * height # y的中心点

bottom = y + height

①绘制英雄

__creat_sprites 添加 英雄精灵 和 英雄精灵组

英雄需要单独定义成属性,因为后续要针对 英雄 做 碰撞检测 以及发射子弹

#创建英雄的精灵和 英雄精灵组

self.hero = Hero()

self.hero_group = pygame.sprite.Group(self.hero)

__update_sprites ,让 英雄精灵组 调用 update和 draw 方法。

self.hero_group.update()

self.hero_group.draw(self.screen)

https://i-blog.csdnimg.cn/blog_migrate/413a11a1de628b70d657b23f11dcb23b.png#pic_center

②移动英雄位置

在pygame中针对 键盘按键的捕获,有两种方式:

第一种方式:

判断 event.type == pygame.KEYDOWN

elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
	print("向右移动...")

缺点:如果用户按住不放,默认只按了一次。

第二种方式:

首先使用 pygame.key.get_pressed() 返回 所有 按键元组

通过 键盘常量 ,判断元组中某一个键是否被按下——如果被按下,对应数值为 1

keys_pressed = pygame.key.get_pressed()
#判断是否按下了方向键
if keys_pressed[pygame.K_RIGHT]:
	print("持续向右移动...")

优点:可以 按住键盘不放 ,实现 持续移动

Hero 类中重写 update 方法

用速度 speed 和英雄 rect.x 进行叠加

不需要调用父类方法——父类方法只是实现了单纯的垂直运动

def update(self):
	self.rect.x += self.speed

__event_handler 方法中根据 左右方向键 设置英雄的 速度

向右 => speed = 2

self.hero.speed = 2

向左 => speed = -2

elif keys_pressed[pygame.K_LEFT]:
	self.hero.speed = -2

其他 => speed = 0

else:
	self.hero.speed = 0
③控制英雄的移动边界

right = x + width ,利用 right 属性可以非常容易地针对右侧设置精灵位置。

https://i-blog.csdnimg.cn/blog_migrate/58f22771dea1a0df7a2f03896154bfac.png#pic_center

#控制英雄的移动边界

if self.rect.x < 0:

self.rect.x = 0

elif self.rect.right > SCREEN_RECT.right:

self.rect.right = SCREEN_RECT.right

④发射子弹

回顾 “四(4)①”的知识点

pygame 的定时器使用套路非常固定:

1、定义 定时器常量—— eventid

2、在初始化方法中,调用 set_timer 方法设置定时器事件

3、在游戏循环中,监听定时器事件。

第一步:

Hero 中定义 fire 方法

def fire(self):
	print("发射子弹")

第二步:

#英雄发射子弹事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1

第三步:

pygame.time.set_timer(HERO_FIRE_EVENT, 500)  # 500ms

第四步:

elif event.type == HERO_FIRE_EVENT:
   self.hero.fire()
⑤定义子弹类

Bullet

初始化方法:

指定子弹图片

初始速度 = -2——实现子弹向 上方 飞行

重写update()方法

判断是否飞出屏幕,如果是,从 精灵组 删除

定义子弹类

plane_sprites 新建 Bullet 继承自 GameSprite

重写 初始化方法,直接指定 图片名称,并且设置初始速度

重写 update() 方法,判断子弹 飞出屏幕从精灵组删除

class Bullet(GameSprite):
	"""子弹精灵"""	
	def __init__(self):

		# 调用父类方法,指定 图片名称,并且设置初始速度
		super().__init__("D:/My life/music/some photos/bullet.png", -2)
		
	def update(self):

		# 调用父类方法,让子弹沿垂直方向飞行
		super().update()
		# 判断子弹是否飞出屏幕
		if self.rect.bottom < 0:
			self.kill()
		
	def __del__(self):
		print("子弹被销毁...")
⑥发射子弹

1、在 Hero初始化方法 中创建 子弹精灵组 属性

#3、创建子弹精灵组

self.bullets = pygame.sprite.Group()

2、修改 plane_main.py__update_sprites 方法,让 子弹精灵组 调用 updatedraw 方法

self.hero.bullets.update()
self.hero.bullets.draw(self.screen)

3、实现 fire() 方法

# 1、创建子弹精灵
bullet = Bullet()

# 2、设置初始位置——在英雄的正上方
bullet.rect.bottom = self.rect.y - 20
bullet.rect.centerx = self.rect.centerx

# 3、将 **子弹** 添加到精灵组
self.bullet.add(bullet)
⑦一次发射三枚子弹

修改 fire ,一次发射三枚子弹

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)
⑧碰撞检测

了解碰撞检测的方法:

方法一:pygame.sprite.groupcollide()

两个精灵组 中 所有的精灵 的 碰撞检测

groupcollide(group1, group2, dokill1, dokill2, collided = None)

如果将 dokill 设置为 True ,则 发生碰撞的精灵将被自动移除。

collided 参数是用于 计算碰撞的回调函数 。如果没有指定,则每个精灵必须有一个 rect 属性。

方法二:pygame.sprite.spritecollie()

判断 某个精灵 和 指定精灵组 中的精灵的碰撞

spritecollide(sprite, froup, dokill, collided = None)

如果将 dokill 设置为 True ,则 指定精灵组 中 发生碰撞的精灵将被自动移除。

collided 参数是用于 计算碰撞的回调函数 。如果没有指定,则每个精灵必须有一个 rect 属性。

返回 精灵组 中 跟 精灵 发生碰撞的 精灵列表

def __check_collide(self):

	#1、子弹摧毁敌机
	pygame.sprite.groupcollide(self.hero.bullets, self.enemy_group, True, True)
	#2、敌机撞毁英雄
	enemies = pygame.sprite.spritecollide(self.hero, self.enemyy_group, True)
	#判断列表是否有内容
	if len(enemies) >0:
		#让英雄牺牲
		self.hero.kill()
		#结束游戏
		PlaneGame.__game_over()

五、游戏设计完成,运行图片

https://i-blog.csdnimg.cn/blog_migrate/02942be977102c63abbd72f3d9582c5a.png#pic_center


飞机大战游戏共包含两个python文件: plane_main.pyplane_sprites.py

附录一:plane_main.py

import pygame
pygame.init()  # 这行代码可以解决“pygame is not initialized”问题
from plane_sprites import *


class PlaneGame(object):
    def __init__(self):
        print("游戏初始化")
        # 1、创建游戏的窗口
        self.screen = pygame.display.set_mode(SCREEN_RECT.size)
        # 2、创建游戏的时钟
        self.clock = pygame.time.Clock()
        # 3、调用私有方法,精灵和精灵组的创建
        self.__create_sprites()
        # 4、设置定时器事件-创建敌机 1s
        pygame.time.set_timer(CREATE_ENEMY_EVENT, 1000)  # 1000ms
        pygame.time.set_timer(HERO_FIRE_EVENT, 500)  # 500ms


    def __create_sprites(self):
        # 创建背景精灵和精灵组
        bg1 = Background()
        bg2 = Background(True)

        self.back_group = pygame.sprite.Group(bg1, bg2)

        # 创建敌机的精灵组
        self.enemy_group = pygame.sprite.Group()
        # 创建英雄的精灵和 英雄精灵组
        self.hero = Hero()
        self.hero_group = pygame.sprite.Group(self.hero)

    def start_game(self):
        print("游戏开始")
        while True:
            # 1、设置刷新频率
            self.clock.tick(FRAME_PER_SECOND)
            # 2、事件监听
            self.__event_handler()
            # 3、碰撞检测
            self.__check_collide()
            # 4、更新 / 绘制精灵组
            self.__update_sprites()
            # 5、更新显示
            pygame.display.update()

    # 事件监听
    def __event_handler(self):
        for event in pygame.event.get():

            # 判断是否退出游戏
            if event.type == pygame.QUIT:
                PlaneGame.__game_over()
            elif event.type == CREATE_ENEMY_EVENT:
                # print("敌机出场...")
                # 创建敌机
                enemy = Enemy()
                # 将敌机精灵添加到精灵组
                self.enemy_group.add(enemy)
            elif event.type == HERO_FIRE_EVENT:
                self.hero.fire()

            # elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
            #     print("向右移动...")

        # 首先使用`pygame.key.get_pressed()`返回所有按键元组
        # 通过键盘常量,判断元组中某一个键是否被按下——如果被按下,对应数值为`1`
        keys_pressed = pygame.key.get_pressed()
        # 判断是否按下了方向键
        if keys_pressed[pygame.K_RIGHT]:
            # print("持续向右移动...")
            self.hero.speed = 2
        elif keys_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)
        # 判断列表是否有内容
        if len(enemies) > 0:
            # 让英雄牺牲
            print("英雄牺牲了")
            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)

    @staticmethod
    def __game_over():
        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_SECOND = 60

# 创建敌机的定时器常量
CREATE_ENEMY_EVENT = pygame.USEREVENT
# 英雄发射子弹事件
HERO_FIRE_EVENT = pygame.USEREVENT + 1


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


class Background(GameSprite):
    """游戏背景精灵"""

    def __init__(self, is_alt=False):  # 默认alt为False
        # 1、调用父类方法实现精灵的创建(image / rect / speed)
        super().__init__("D:/My life/music/some photos/sunrise.jpg")
        # 2、判断是否是交替图像,如果是,需要设置初始位置
        if is_alt:
            self.rect.y = -self.rect.height

    def update(self):
        # 1、调用父类的方法实现
        super().update()
        # 2、判断是否移出屏幕,如果移出屏幕,将图片移动到正上方
        if self.rect.y >= SCREEN_RECT.height:
            self.rect.y = -self.rect.height


class Enemy(GameSprite):
    """敌机精灵"""

    def __init__(self):
        # 1、调用父类方法,创建敌机精灵,同时指定敌机图片
        super().__init__("D:/My life/music/some photos/enemyplane.png")

        # 2、指定敌机的初始位置
        self.speed = random.randint(1, 3)  # (随机数的最小值,随机数的最大值)
        # 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("敌机销毁成功%s" % self.rect)
        pass


class Hero(GameSprite):
    """英雌精灵"""

    def __init__(self):
        # 1、调用父类方法,设置image和速度
        super().__init__("D:/My life/music/some photos/plane.png", 0)
        # 2、设置英雌的初始位置
        self.rect.centerx = SCREEN_RECT.centerx
        self.rect.bottom = SCREEN_RECT.bottom - 120

        # 3、创建子弹精灵组
        self.bullets = pygame.sprite.Group()

    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

    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__("D:/My life/music/some photos/bullet.png", -2)

    def update(self):
        # 调用父类方法,让子弹沿垂直方向飞行
        super().update()
        # 判断子弹是否飞出屏幕
        if self.rect.bottom < 0:
            self.kill()

    def __del__(self):
        # print("子弹被销毁...")
        pass

附录三:飞机大战设计图片

“英雄”“敌机”“子弹”均为PNG格式——便于设置透明背景。

英雄:

https://i-blog.csdnimg.cn/blog_migrate/5c3208ae9c97e83674d3731dbe9450c0.png#pic_center

敌机:

https://i-blog.csdnimg.cn/blog_migrate/7eaa7be6f7776bf08c6d61c4261f22d2.png#pic_center

子弹:

https://i-blog.csdnimg.cn/blog_migrate/fdf7bd1b709d0ed84c89dc60f51717fc.png#pic_center

背景图片:

https://i-blog.csdnimg.cn/blog_migrate/5a0ba841b0665fb9daa82291478e5ec1.jpeg#pic_center