文章目录
0. 运行结果
1. 目标
Python面向对象;Pygame模块;游戏开发;飞机大战
2. 知识准备
2.1 创建图形窗口
(1)游戏的初始化和退出
pygame.init() # 导入并初始化所有pygame模块
pygame.quit() # 卸载所有pygame模块
(2)游戏坐标系
原点(0,0)在左上角
x轴水平向右,逐渐增加。
y轴水平向下,逐渐增加。
所有可见的元素都是以矩形区域描述的。(x,y)(width,height)
# pygame提供的Rect类用于描述矩形区域。
Rect(x,y,width,height)
例子:
hero_rect = pygame.Rect(100,500,120,125)
print("英雄的原点:%d %d " % (hero_rect.x,hero_rect.y))
print("英雄的尺寸:%d %d" % hero_rect.size)
(3)创建游戏主窗口
# pygame提供的display模块用于创建、管理游戏窗口
pygame.display.set_mode() # 初始化游戏显示窗口
pygame.display.updata() # 刷新屏幕内容显示,稍后使用
例子:
# 创建主窗口
screen = pygame.display.set_mode((512, 768))
# 1.创建游戏窗
self.screen = pygame.display.set_mode((SCREEN_RECT.width+200, SCREEN_RECT.height))
pygame.display.set_caption('飞机大战') # 窗口标题
self.screen.fill(pygame.Color("lightskyblue")) # 背景填充颜色
2.2 图像绘制
在屏幕看到一个图像的内容:
① 加载:使用 pygame.image.load() 加载图像的数据
② 绘制:使用游戏屏幕对象screen,调用 blit() 方法,将图像绘制在指定位置。
③ 显示:调用 pygame.diaplay.update() 方法更新屏幕显示
## 例子1 绘制背景图和英雄飞机
import pygame
pygame.init()
screen = pygame.display.set_mode((512, 768)) # 创建主窗口
bg = pygame.image.load("./images/bg/bg2.jpg") # 加载
screen.blit(bg, (0, 0)) # 绘制
### 注:blit()方法的第二个元素可以传入一个元组,也可以是一个矩形对象hero_Rect
hero = pygame.image.load("./images/hero/hero_b_04.png")
screen.blit(hero, (200, 600))
pygame.display.update() # 最后统一更新显示
while True:
pass
pygame.quit()
附加1:
绘制文字:
myfont = pygame.font.Font(None, 48, bold=False, italic=False) # 字体(字体、大小、粗体、斜体)
txt = myfont.render("pygame", True, (255, 0, 0)) # 内容(文字、取消锯齿、颜色)
screen.blit(txt, (10, 100)) # 绘制(txt、位置)
注意:
print(pygame.font.get_fonts()) # 查找当前电脑的字体列表
附加2:
pygame的颜色定义(R,G,B)
(255, 255, 255) # 白色
(0, 0, 0) # 黑色
(255, 0, 0) # 红色
(0, 255, 0) # 绿色
(0, 0, 255) # 蓝色
(255, 128, 0) # 橘色
(128, 128, 128) # 灰色
2.3 游戏循环和游戏时钟
(1)动画原理
多张静止的照片连续、快速的播放,产生连贯的视觉效果。
每次绘制的结果称为帧,也就是每次执行pygame.diaplay.update()的结果。
一般在电脑每秒绘制60次,就能够达到高品质的动画效果。
(2)游戏循环 ----> 游戏的开始
游戏的两个组成部分:
游戏初始化 | 游戏循环 |
---|---|
设置游戏窗口 | 设置刷新帧率 |
绘制图像初始位置 | 检测用户交互 |
设置游戏时钟 | 更新所有图像位置 |
更新屏幕显示 |
(3)游戏时钟 —> 控制游戏循环的速度
pygame.time.Clock可以设置刷新帧率。要使用时钟对象需要两步:
在游戏初始化设置一个时钟对象;游戏循环中让时钟对象调用tick(帧率)方法。
(tick方法可以根据上次被调用的时间,自动设置游戏循环中的延时)
clock = pygame.time.Clock()
clock.tick(60) # 游戏循环内部的代码,每秒钟执行60次
(4)监听事件
事件:游戏启动后,用户对游戏所作的操作。点击鼠标,点击关闭按钮,按下按键。
监听:捕获用户的具体操作,做出针对性的反应。
## pygame中提供的 pygame.event.get() 可以捕获用户当前所有操作的事件列表
# 以下代码非常固定,适用于编写,退出所有pygame游戏。
while True:
# 设置帧率
clock.tick(60)
# 监听事件
for event in pygame.event.get():
# 判断用户是否点击了关闭按钮
if event.type == pygame.QUIT:
print("游戏退出...")
pygame.quit() # 卸载pygame模块
exit() # 终止当前程序,退出系统
(5)英雄的简单动画
实现英雄飞机从底部飞到顶部,再回到底部,再飞往顶部,如此循环。
提示:每一次调用update()方法之前,需要把需要的游戏图像都重新绘制一遍。而且要最先绘制背景图像,消除残影。
import pygame
###################################### 1.游戏初始化 ###############################
pygame.init() # 初始化pygame模块
screen = pygame.display.set_mode((512, 768)) # 创建主窗口
# 图片的加载与绘制
bg = pygame.image.load("./images/bg/bg2.jpg")
screen.blit(bg, (0, 0))
hero = pygame.image.load("./images/hero/hero_b_04.png")
screen.blit(hero, (200, 600))
pygame.display.update()
# 设置游戏时钟
clock = pygame.time.Clock()
# 设置飞机初始位置
hero_rect = pygame.Rect(200, 600, 122, 105)
######################################## 2.游戏循环 ##################################
while True:
# 1. 设置帧率
clock.tick(60)
# 2. 监听事件
for event in pygame.event.get():
# 判断用户是否点击了关闭按钮
if event.type == pygame.QUIT:
print("游戏退出...")
pygame.quit() # 卸载pygame模块
exit() # 退出系统
# 3. 修改飞机位置
hero_rect.y -= 5
if hero_rect.y <= 0-hero_rect.height:
hero_rect.y = 768
# 4. 调用 blit方法绘制
screen.blit(bg, (0, 0)) # 为了消除残影,应在绘制新飞机之前,重新绘制背景图
screen.blit(hero, hero_rect)
# 5. update更新显示
pygame.display.update()
pygame.quit() # 卸载pygame模块
2.4 精灵和精灵组(两个高级类)
(1)概念
上述案例,图像加载、绘制和位置变化,需要程序员编写大量代码。为了简化开发程序,pygame提供了两个类:
① 精灵:pygame.sprite.Sprite ---- 存储图像数据、位置的对象
② 精灵组:pygame.sprite.Group ----包含 多个精灵的对象
精灵(需要派生子类) | |
---|---|
image | 记录图像数据 |
rect | 记录图像位置 |
update() | 更新精灵位置 |
kill() | 从所有组中删除 |
精灵组 | |
---|---|
__init __(self,*精灵): | |
add(*sprites) | 向组中增加精灵 |
sprites() | 返回所有精灵列表 |
update() | 让组中所有的精灵调用各自的update方法 |
draw(screen) | 将组中所有精灵的图像,各自绘制到屏幕的rect位置上 |
所以,游戏可以分为当前的两部分:
游戏初始化 | 游戏循环 |
---|---|
创建精灵 | 精灵组.update() |
创建精灵组 | 精灵组.draw(screen) |
pygame.display.update() |
(2)派生精灵子类
GameSprite 继承自 pygame.sprite.Sprite
GameSprite | |
---|---|
属性 | image、rect、speed |
方法 | __init __(self,image_name,speed=1) 、update(self) |
注意:
在重写__init __方法的时候,应该super()一下继承父类的__init __方法。
image 的 get_rect() 方法,可以返回 pygame.Rect(0,0,图像宽,图像高)
(3)创建敌机
## 1. 初始化中:
# 创建敌机的精灵
enemy = GameSprite("./images/enemy/enemy.png")
enemy1 = GameSprite("./images/enemy/enemy11.png", speed=2)
# 创建敌机的精灵组
enemy_group = pygame.sprite.Group(enemy,enemy1)
# 2. 游戏循环中:
# 让敌机精灵组调用两个方法
enemy_group.update() # 让组中的所有精灵更新位置
enemy_group.draw(screen) # 绘制精灵组中的所有的精灵
附加:在另一个文件新建精灵类,在主文件中导入,创建精灵。
from sprites import Player
square = Player()
group = pygame.sprite.Group(square)
3. 编写游戏代码
3.1 搭建游戏框架
设计 PlaneGame 类如下:
游戏初始化 | 游戏循环 |
---|---|
设置游戏窗口 | 设置刷新帧率 |
创建游戏时钟 | 事件监听 |
创建精灵、精灵组 | 碰撞检测 |
更新绘制精灵组 | |
更新屏幕显示 |
属性 | 方法 |
---|---|
screen | __init __(self):完成所有的游戏初始化动作 |
clock | __create__sprites(self) |
精灵组或精灵 | __start_game(self):开启整个游戏循环 |
__check_collide(self) :碰撞检测 | |
__event_handler(self):事件监听 | |
__update_sprites(self) :更新/绘制图像 | |
__game_over:游戏结束 |
① 由初始化方法完成游戏的初始化动作:使用__init __方法完成游戏初始化的全部内容,调用其中的私有方法__create_sprites()创建精灵或精灵组。
3.2 实现飞机大战主游戏类
(1)文件
plan_main:
- 封装主游戏类
- 创建游戏对象
- 启动游戏
import pygame
from plane_sprites import *
# 封装主游戏类
class PlaneGame(object):
"""飞机大战主游戏"""
def __init__(self):
print("游戏初始化")
def start_game(self):
print("游戏开始...")
if __name__ == '__main__':
# 创建游戏对象
game = PlaneGame()
# 启动游戏
game.start_game()
plane_sprites:
- 封装游戏中所有需要使用的精灵子类
- 提供游戏的相关工具
注意:
① 一般设置超参数(每个字母都大写),在提供工具的palne_sprites文件中。
3.3 游戏背景
原理:两张相同的背景图像的交替向下滚动
(1)设计背景类:
# 由GameSprite派生出背景类
class BackGround(GameSprite):
"""游戏背景精灵"""
def __init__(self, is_alt=False): # is_alt判断是否为替换背景图像
# 1.调用父类方法实现背景精灵的创建(加载图像、位置、速度)
super().__init__("./images/bg/bg2.jpg")
# 2.判断是否为交替图像,设置初始位置
if is_alt:
self.rect.y = -self.rect.height
def update(self): # 重写update方法
# 1.调用父类方法,实现向下移动
super().update()
# 2.判断是否移出屏幕,将图像设置到屏幕上方
if self.rect.y >= SCREEN_RECT.height:
self.rect.y = -self.rect.height
(2)设置对象
def __create_sprites(self):
# 1.创建背景精灵和精灵组
bg1 = BackGround()
bg2 = BackGround(is_alt=True)
self.back_group = pygame.sprite.Group(bg1, bg2)
3.4 敌机
(1)原理:使用定时器添加敌机。
敌机规律:
- 每隔1秒出现1架飞机
- 飞机向下移动,速度不相同
- 飞机出现的水平位置也不相同
- 从屏幕底部飞出后,不再返回,销毁。
(2)定时器(每隔一段时间,去执行一些动作)
## 在pygame中使用 pygame.time.set_timer()添加定时器
set_timer(eventid, milliseconds) # 事件代号,间隔时间(毫秒)
步骤:
① 定义定时器常量 – eventid
② 初始化中,调用set_timer方法设置定时器事件
③ 游戏循环中,监听游戏事件
说明:
① 使用定时器可以间隔一定的时间创建事件(代号),然后我们在事件监听中捕获该事件,执行相应操作。
② eventid 基于常量 pygame.USEREVENT 来指定
③ 捕获:pygame.event.get()获取当前所有的事件列表,遍历列表,判断event.type == eventid 。
(3)设计敌机类
说明:
导入库的顺序:
import random # 导入官方标准模块
import pygame # 导入第三方模块
# 导入应用程序模块
(4)敌机销毁
精灵中的kill()方法,可以将精灵从所有精灵组中删除。
__del __内置方法会在对象被销毁前自动调用,可以用来判断敌机是否已经销毁。
3.5 英雄飞机
要点:
(1)捕获按键的两种方式:
① 事件监听
for event in pygame.event.get(): # 返回当前时刻所有的事件列表
if event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT
print("向右移动...")
此方法的效果,是按下一次,捕获一次;连续一直按着按键,只能捕获一次。
② 使用键盘提供的方法,获取键盘按键
# 使用键盘提供的方法,获取键盘按键 - 返回按键元组
keys_pressed = pygame.key.get_pressed()
# 判断元组中对应的按键索引值
if keys_pressed[pygame.K_RIGHT]:
print("向右移动...")
此方法的效果,是连续一直按着按键,也会连续捕获。
(2)控制英雄边界,不离开屏幕,需对x进行限制,不能在初始化中编写,要在update方法中。
(3)矩形区域 rect 的几个因素
pygame.rect | |
---|---|
x(原点横坐标) | y(原点纵坐标) |
left(矩形左边界) | right(矩形右边界) |
centerx(矩形水平中心线) | centery(矩形垂直中心线) |
size(元组(width,height)) | width(矩形宽度) |
height(矩形高度) | center |
(4)两个定时器常量
定义定时器的常量需要基于pygame.USEREVENT。若出现多个定时器时,常量的定义,可以依次+1。
# 创建敌机的定时器常量
ENEMY_EVENT = pygame.USEREVENT
# 英雄发射子弹定时器常量
HERO_FIRE = pygame.USEREVENT + 1
(5)同一页的类,也能够被用在其他类的方法中。
3.6 碰撞检测
pygame提供了两个非常方便的实现碰撞检测的方法:
① 两个精灵组中的精灵发生碰撞:
pygame.sprite.groupcollide(group1,group2,dokill1,dikill2,collied=None)
说明:
dokill1和dokill2都是布尔型变量。group1和group2发生碰撞,当dokill1为True时,group1被自动移除;同理,当dokill2为True时,group2被自动移除。
② 某个精灵和精灵组中的精灵发生碰撞:
pygame.sprite.spritecollide(sprite,group,dokill,collided=None) 返回一个与英雄碰撞的敌机列表
说明:
dokill为True时,碰撞时,group自动销毁;为False,碰撞时,group安然无恙。
③ 某个精灵和某个精灵发生碰撞:
pygame.sprite.collide_mask(sprite1,sprite2) # 如果发生碰撞,则返回True
例子:
for flag_zombie in self.flag_zombie_group:
if pygame.sprite.collide_mask(bullet, flag_zombie):
self.peashooter_bullet.remove(bullet)
flag_zombie.energy -= 1
④ 缩小矩形比例的检测
这个函数还有一个非常有用的变体:pygame.sprite.collide_rect_ratio()。这个函数需要一个额外的浮点类型的参数。这个参数用来指定检测矩形的百分比。
有的时候我们希望冲突检测更精准一些的话,就可以收缩检测的区域,让矩形更小一些,就是通过这个参数控制的。使用方法如下:
result = pygame.sprite.collide_rect_ratio( 0.5 )(sprite_1,sprite_2)
3.7 添加BGM
在主程序中加入:
import pygame # 导入pygame资源包
file=r'E:\Python_Exercise\123.mp3' # 音乐的路径
pygame.mixer.init() # 初始化
pygame.mixer.music.load(file) # 加载音乐文件
pygame.mixer.music.play() # 开始播放音乐流