Python基础:第012课——最小的程序框架(代码解析)
观看视频
参考:pygame详细教程 参考案例:游戏模块 Pygame 作为一个入门级的游戏开发库,其实并不难学,只要掌握 Python 编程的相关知识就能很轻松地掌握它。
Pygame 语法简单、明了,秉持了 Python 语言一贯的风格。同时,它作为一个游戏开发库来说,具有图形编程的基本特点,如果您对于图形编程从未了解过,即使您有 Python 编程基础,也会略感迷茫。因此,在接下来的学习中会详细介绍 Pygame 的常用模块,以及图形编程中的相关概念,帮助您快速掌握 Pygame 的使用。
屏幕坐标系
#导入所需的模块
import pygame, sys
# 使用pygame之前必须初始化
pygame.init()
# 设置主屏窗口
screen = pygame.display.set_mode((600,400))
# 设置窗口的标题,即游戏名称
pygame.display.set_caption("小小工坊")
# 加载图像,存储在ball变量中,ball是一个图像对象
ball = pygame.image.load('pygame/images/ball.gif')
#获得显示对象的rect区域坐标
ball_rect = ball.get_rect()
# 设置显示对象居中
ball_rect.center = (300, 200)
# 将准备好的图像绘制到主屏幕 Screen 上。
screen.blit(ball, ball_rect)
# 建立时钟对象
fclock = pygame.time.Clock()
# 固定代码段,实现点击关闭按钮退出游戏的功能,几乎所有的pygame都会使用该段代码
while True :
# 循环获取事件,监听事件状态
for event in pygame.event.get():
# 判断用户是否点了"X"关闭按钮,并执行if代码段
if event.type == pygame.QUIT:
#卸载所有模块
pygame.quit()
#终止程序,确保退出程序
sys.exit()
ball_rect = ball_rect.move(speed,speed)
screen.fill((0,0,0))
screen.blit(ball,ball_rect)
#更新屏幕内容
pygame.display.update()
fclock.tick(60)
12. 代码解析
Python:pygame包的详细使用方法
12.1 游戏的初始化和退出
# 使用pygame之前必须初始化
pygame.init()
12.1.1 方法说明
pygame.init() 导入并初始化所有pygame模块,使用其他模块前,必须先调用iinit方法 pygame.quit() 卸载所有pygame模块,在游戏结束之前调用
12.2 Surface对象
12.2.1 坐标系
- 原点在左上角 (0,0)
- x轴水平方向向右,逐渐增加
- y轴垂直方向向下,逐渐增加
如图:
- 在游戏中,所有可见的元素都是以矩形区域来描述位置的
- 要描述一个矩形区域有四个要素:
(x, y) (width, height)
(x,y) 是物体坐标,(width, height) 是物体像素大小
- pygame 专门提供了一个类pygame.Rect 用于描述矩形区域
x, y left,top,bottom,right center,centerx,centery size,width,height
12.2.2 如何创建Surface对象
那么我们应该如何创建 Surface 对象呢?Pygame 提供了多种创建 Surface 对象的方法,这里先介绍以下几种方法。
上述示例,使用如下方式创建了一个 surface 对象:
#也叫screen对象,本质上是一个Surface,大小400*400
screen = pygame.display.set_mode((400,400))
screen 的本质上就是一个 Surface 对象,它是游戏的主窗口,也就是整个游戏中尺寸最大的“纸”,任何其他的 Surface 对象都需要附着在这张最大的“纸”上,比如创建一个图像的 Surface 对象,通过以下方法将它绘制在主屏幕上:
# 加载图像,存储在ball变量中,ball是一个图像对象
ball = pygame.image.load('pygame/images/ball.gif')
#获得显示对象的rect区域坐标
ball_rect = ball.get_rect()
# 设置显示对象居中
ball_rect.center = (300, 200)
# 将准备好的图像绘制到主屏幕 Screen 上。
screen.blit(ball, ball_rect)
比如创建一个包含文本的 Surface 对象,通过以下方法将它绘制在主屏幕上:
# 引入字体类型
f = pygame.font.Font('C:/Windows/Fonts/simhei.ttf',50)
#创建一个包含文字的Surface对象
# 生成文本信息,第一个参数文本内容;第二个参数,字体是否平滑;
# 第三个参数,RGB模式的字体颜色;第四个参数,RGB模式字体背景颜色;
text = f.render("小小工坊",True,(255,0,0),(0,0,0))
textRect =text.get_rect()
#通过blit方法将其绘制在主屏幕上,这里的textRect表示位置坐标
screen.blit(text,textRect)
set_mode()方法
set_ mode( resolution=(0,0),flags=0, depth=0) -> Surface(名字随意)
- 作用:创建游戏显示窗口
- 参数
- resolution 指定屏幕的宽和高,默认创建的窗口大小和屏幕大小一致
- flags参数指定屏幕的附加选项,例如是否全屏等等,默认不需要传递
- depth参数表示颜色的位数,默认自动匹配
- 返回值:Surface对象
Surface对象是内存中的屏幕数据对象,可以理解为游戏的屏幕,游戏的元素都需要被绘制到游戏的屏幕上 1. 在开发时,可能会需要使用固定的数值,例如屏幕的高度是700 2. 这个时候,建议不要直接使用固定数值,而应该使用常量 3. 当实验需求发生变化时,只需要修改常量的值就可,不需要在一个一个去找数值更改
常量的命名方式:
所有字母都使用大写,单词与单词之间使用下划线连接
12.2.3 *Rect对象
pygame.Rect 是一个比较特殊的类,内部只是封装了一些数字计算
实例:
import pygame
hero = pygame.Rect(100,200,50,120)
print("英雄的x,y坐标分别是 %d, %d" % (hero.x, hero.y))
print("英雄的宽高分别是:%d,%d" % (hero.width, hero.height))
# Rect中的size属性是一个元组(宽,高)
print("英雄的宽高分别是:%d,%d" % hero.size))
# Rect中的bottom属性表示底部的纵坐标 y+height,top属性表示元素顶部纵坐标y
print("英雄的底部y坐标为:%d,顶部y坐标为:%d" % (hero.bottom, hero.top))
# Rect的right属性表示元素右边横坐标 x+width, left表示元素横坐标x
print("英雄的最右边x坐标为%d,最左边x坐标为:%d" % (hero.right, hero.left))
# Rect的center属性是一个元组(centerx,centery)
print("英雄的中心点坐标为:%d,%d" % hero.center)
12.3 图像绘制
那么要在创建的窗口中按照指定要求显示图片,则需要三个步骤:
1. 使用`pygame.image.load()`加载图像的数据
2. 使用游戏屏幕对象,调用`blit`方法将图像绘制到指定位置
3. 调用`pygame.display.update()`方法更新整个屏幕的显示
*
*
*
*
load
-File 图像文件
blit
-image 图像对象
+tuple 位置
update
-screen update
注:如果不调用pygame.display.update() 方法,则运行程序后图片并不会显示出来,在所有图像绘制完成后可放在最后统一更新
12.4 事件监听
游戏,在我们日常生活中经常接触到,无论是手游、还是电脑端游戏,已经成了信息社会中,不可或缺的一部分。
游戏大致来讲是由动画和人机交互的体验两部分构成,其中动画则是由一系列连续静止的图片,经过一定频率的刷新构成的,这个频率被称为 FPS,如果频率值越大则画面越流畅;如果频率值越小则画面会出现卡顿的感,在游戏过程中一般人能接受的最低 FPS 约为 30Hz,如果想要画面流畅则 FPS 要大于 60 Hz。
FPS 越高,细节越好,体验也越好,但是文件容量也越高
动画保证了玩家的视觉体验,而人机交互则是操作上的体验。通过移动和点击鼠标、按下键盘上的技能键,或是滑动手机屏幕等操作来实现人机交互,这些与游戏程序交互的操作被称为事件(Event )。
Pygame 作为一个游戏开发库,同样具有设置和监听事件的功能。它提供了一个 event 事件模块,这个模块中包含了所有常用到游戏事件。下面是退出游戏的代码示例(其他事件类型,后续会做介绍):
# 循环获取事件,监听事件状态,使用get()获取事件
for event in pygame.event.get():
# 判断事件类型,用户是否点了"X"关闭按钮
# pygame.QUIT 指点击右上角窗口的"X"号
if event.type == pygame.QUIT:
#点击后,卸载所有pygame模块
pygame.quit()
12.5 游戏循环
当打我们游戏时可能会触发游戏中的各种事件,比如鼠标事件、键盘按键事件、摄像拍照事件等等,因此游戏程序需要一直循环监听玩家的操作,只有当用户点击了游戏“关闭”按钮时,监听才会结束。如果想要达到“循环监听”目的,此时就需要设置一个游戏循环(Game Loop)也称为游戏的主循环,这样才能保证人机交互的体验感。代码示例如下:
#游戏主循环(游戏循环)
while True:
# 循环获取事件,监听事件
for event in pygame.event.get():
# 判断用户是否点了关闭按钮
if event.type == pygame.QUIT:
# 当用户关闭游戏窗口时执行以下操作
# 这里必须调用quit()方法,退出游戏
pygame.quit()
#终止系统
sys.exit()
#更新并绘制屏幕内容
pygame.display.flip()
游戏主循环是每个 Pygame 游戏程序中必不可少的一部分,它主要承担着以下三个重要任务:
- 处理游戏事件
- 更新游戏状态
- 把更新后的游戏状态绘制到屏幕上
图2:主循环示意图
游戏画面和游戏操作状态会因为动画效果和玩家的操作而改变,因此需要以循环的方式实时地更新主屏幕(screen)的显示内容。把下列代码放入游戏主循环中即可实现实时更新和绘制屏幕内容,如下所示:
#刷新界面显示
pygame.display.flip()
除了上述方法外,Pygame 还提供了另一个方法。如下所示:
pygame.display.update()
这两个方法的主要区别是:后者可以根据选定的区域来更新部分内容,而前者则是更新整个待显示的内容。如果后者没有提供区域位置参数时,其作用和 display.flip() 相同。
12.6 关于动画实现的原理
跟电影的原理类似,游戏中的动画效果,本质上是快速的在屏幕上绘制图像,电影是将多张静止的电影胶片连续、快速的播放,产生连贯的视觉效果!
一般在电脑上每秒绘制60次,就能够达到非常连续高品质的动画效果,每次绘制的结果被称为帧Frame
在每次调用pygame.display.update() 方法产生的结果为一帧,也就是说需要每秒钟调用60次pygame.display.update() 方法,就可以达到高品质动画效果
如何实现每秒60帧? pygame专门提供了一个类pygame.time.Clock 可以非常方便的设置屏幕绘制速度- -刷新帧率
要使用时钟对象需要两步:
- 在游戏初始化创建一个时钟对象
- 在游戏循环中让时钟对象调用
tick (帧率) 方法,tick 方法会根据.上次被调用的时间, 自动设置游戏循环中的延时
实现方法:
# 创建时钟对象
clock = pygame.time.Clock()
while True:
# 在游戏循环内部调用tick方法,里面传入想要的帧数
? clock.tick(60)
动画实现案例:飞机向上飞行 例 :
import pygame
pygame.init()
# 创建游戏窗口
screen = pygame.display.set_mode((480,700))
# 加载游戏中需要的图像
background = pygame.image.load("./pygame/images/xx.jpg")
plane = pygame.image.load("./pygame/images/xxx.jpg)
# 将图像显示到游戏初始化的位置
screen.blit(background, (0, 0))
screen.blit(plane, (120, 500))
# 定义飞机显示的矩形区域
plane_Rect = pygame.Rect(120, 500, 120, 90)
# 创建时钟对象
clock = pygame.time.Clock()
while True:
# 定义每秒60帧
clock.tick(60)
# 移动飞机显示矩形区域的位置
plane_Rect.y = plane_Rect.y -5
# 重绘背景图像,避免出现残影
screen.blit(background, (0, 0))
# 重新绘制图像
screen.blit(plane, place_Rect)
# 更新屏幕
pygame.display.update()
pygame.quit()
12.7 针对键盘按键捕获的两种方式
●第一种方式判断event.type == pygame.KEYDOWN
实现代码:
if event.type == pygame. KEYDOWN and event.key == pygame.K RIGHT:
print("向右移动...")
●第二种方式(按住方向键不放可持续移动)
- 首先使用
pygame. key.get_ pressed() 返回所有按键元组 - 通过键盘常量,判断元组中某一个键是否被按下——如果被按下,对应数值为1
实现代码:
变量 = pygame.key.get_pressed()
if 变量[pygame.K_RIGHT]:
print("向右移动")
● 两种方式的区别
第一种方式 event.type 用户必须抬起按键才算一次按键事件(利用列表也可实现持续向某一方向移动,具体见) 第二种方式 用户可按键不放,能够实现持续向某一个方向移动
*12.8 pygame中提供的高级类精灵和精灵组
图像加载、位置变化、绘制图像都需要程序员编写代码分别处理,为了简化代码,避免过多类似重复的代码,pygame提供了精灵和精灵组类
心得: 其根本就是,将需要创建的多个执行相同动作的元素通过精灵类统一规划到一起,再通过精灵组统一执行每个元素的动作并绘制到屏幕上,将多次重复的代码缩减成一次代码。
pygame.sprite.Sprite —— 存储图像数据image 和位置rect 的对象pygame.sprite.Group
注:在开发时,如果子类的父类不是Object 类,则需要通过super(),__init__() 来调用父类的初始化方法,因为父类中的初始化方法中封装了许多属性,在继承父类后只有通过手动调用父类的初始化方法,才能在子类的初始化方法中使用父类初始化方法封装的属性
●精灵 。封装图像image、位置rect 。提供update() 方法,根据游戏需求,更新位置rect
# 定义对象的属性
self.image = pygame.image.load(image)
# 得到图片的坐标和宽高像素
self.rect = self.image.get_rect()
●精灵组 。包含多个精灵对象 。update 方法,让精灵组中的所有精灵调用update 方法更新位置 。draw(游戏窗口对象) 方法,在游戏窗口上绘制精灵组中的所有精灵
实例:
#精灵类planeSprites:
import pygame
class planeSprites(pygame.sprite.Sprite):
def __init__(self, image, speed=1):
# 调用父类的初始化方法
super().__init__()
# 定义对象的属性
self.image = pygame.image.load(image)
# 得到图片的坐标和宽高像素
self.rect = self.image.get_rect()
# 定义图片的运行速度
self.speed = speed
def update(self, *args):
# 使飞机斜着飞行
self.rect.x += self.speed
self.rect.y += self.speed
# 杀死精灵,节约内存
self.kill()
主程序中:
import pygame
from plane_sprites import planeSprites
pygame.init()
# 创建敌机1,敌机2
enemy1 = planeSprites("./飞机大战素材/敌机.png")
enemy2 = planeSprites("./飞机大战素材/敌机.png")
# 在精灵组中加入敌机1
enemy_group = pygame.sprite.Group(enemy1, enemy2)
while True:
# 调用精灵组的update方法
enemy_group.update()
# 将精灵组中的敌机绘制到屏幕上
enemy_group.draw(screen)
# ***一定记得更新视图,否则不会显示出任何图像
pygame.display.update()
pygame.quit()
12.9 pygame中提供的两种精灵碰撞检测方法
pygame.sprite.groupcolide()
groupcollide(group1, group2, dokill1, dokill2, collided = None) -> Sprite_ dict
group1和group2 都是一个精灵组 dokill1和dokill2 都是布尔类型
当把dokill1设置为True后,精灵组1和精灵组2碰撞时精灵组1会自动销毁 当把dokill2设置为True后,精灵组1和精灵组2碰撞时精灵组2会自动销毁
pygame.sprite.spritecollide() ●判断某个精灵和指定精灵组中的精灵的碰撞
spritecollide(sprite, group, dokill, collided = None) -> Sprite_ list
●如果将dokill 设置为True,则指定精灵组中发生碰撞的精灵将被自动移除
● collided参数是用于计算碰捶的回调函数 。如果没有指定,则每个精灵必须有一一个rect 属性
●返回精灵组中跟精灵发生碰撞的精灵列表
注:这里是返回一个列表
代码实现:
enemies = pygame.sprite.spritecollide(self.hero, self.enemyGroup, True)
if len(enemies) > 0:
print("游戏结束,飞机受到损坏")
pygame.quit()
exit()
12.10 背景交替滚动
俗称跑步机模式
在飞机大战中游戏背景不断变化产生向上飞行的视觉效果
实现原理:
实际上是两张尺寸与屏幕一样大小的背景图片向下移动,等第一张图片刚移除出屏幕后立马改变坐标移动到第二张图片之后
代码实现: 精灵类
import pygame
from plane_main import SCREEN_RECT
class planeSprites(pygame.sprite.Sprite):
def __init__(self, image, speed=1):
# 调用父类的初始化方法
super().__init__()
# 定义对象的属性
self.image = pygame.image.load(image)
# 得到图片的坐标和宽高像素
self.rect = self.image.get_rect()
# 定义图片的运行速度
self.speed = speed
def update(self, *args):
# 使精灵向下移动
self.rect.y += self.speed
# 当父类update更新方法已经无法满足某一精灵类的条件则可以创建心得精灵子类
class background(planeSprites):
def __init__(self, is_single=False):
super().__init__("./飞机大战素材/游戏背景.jpg")
if is_single:
#将第二张图片放在第一张图片的上面
self.rect.y = - self.rect.height
def update(self, *args):
# 调用父类更新方法
super().update()
if self.rect.y == SCREEN_RECT.height:
self.rect.y = - self.rect.height
主方法:
import pygame
from plane_sprites import *
pygame.init()
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
class main(object):
def __init__(self):
# 创建游戏屏幕
self.screen = pygame.display.set_mode(SCREEN_RECT.size)
# 创建游戏时钟
self.clock = pygame.time.Clock()
# 创建游戏精灵组
self.__create_spirits()
# 精灵创建
def __create_spirits(self):
bg1 = background()
bg2 = background(True)
self.backGroup = pygame.sprite.Group(bg1, bg2)
# 精灵更新处理
def __update_spirits(self):
self.backGroup.update()
self.backGroup.draw(self.screen)
12.11 定时器
●在 pygame中可以使用pygame.time.set.timer() 来添加定时器
●所谓定时器,就是每隔一段时间,去执行些动作
set_timer(eventid, milliseconds) -> None
●set_timer 可以创建一个事件 ●可以在游戏循环的事件监听方法中捕获到该事件
● 第1个参数事件代号需要基于常量pygame. USEREVENT 来指定
USEREVENT 是一个整数,再增加的事件可以使用USEREVENT + 1 指定,依次类推…
●第2个参数是事件触发间隔的毫秒值
定时器事件的监听 ●通过pygame.event.get() 可以获取当前时刻所有的事件列表 ●遍历列表并且判断event.type 是否等于eventid, 如果相等,表示定时器事件发生
实现方法:
# 创建敌机的定时器,每隔1000ms发出一个事件
pygame.time.set_timer(pygameUSEREVENT, 3000)
# 事件监听
for event in pygame.event.get():
if event.type == pygameUSEREVENT:
pass
实例(敌机随机飞进屏幕中):
精灵类
import random
import pygame
# 指定屏幕格式大小
SCREEN_RECT = pygame.Rect(0, 0, 480, 700)
CREATE_EVVENTID = pygame.USEREVENT
class planeSprites(pygame.sprite.Sprite):
def __init__(self, image, speed=1):
# 调用父类的初始化方法
super().__init__()
# 定义对象的属性
self.image = pygame.image.load(image)
# 得到图片的坐标和宽高像素
self.rect = self.image.get_rect()
# 定义图片的运行速度
self.speed = speed
# 创建敌机的定时器,每隔1000ms发出一个事件
pygame.time.set_timer(CREATE_EVVENTID, 3000)
class enemy(planeSprites):
"""敌机精灵"""
def __init__(self):
super().__init__("./飞机大战素材/敌机.png")
self.rect.bottom = 0
# 飞机速度从1—3中任取
self.speed = random.randint(1, 3)
max_x = SCREEN_RECT.width - self.rect.width
# 飞机的位置从0-屏幕右边任取
self.rect.x = random.randint(0, max_x)
def update(self, *args):
super().update()
# 判断精灵飞出屏幕后杀死精灵
if self.rect.y >= SCREEN_RECT.height:
self.kill()
主方法:
import pygame
from plane_sprites import *
pygame.init()
# 精灵创建
def __create_spirits(self):
self.enemyGroup = pygame.sprite.Group()
# 事件监听处理
def __event_handler(self):
for event in pygame.event.get():
# 判断事件类型是否是定时器给出的
if event.type == CREATE_EVVENTID:
# 新增精灵
oneEnemy = enemy()
# 向精灵组中添加精灵
self.enemyGroup.add(oneEnemy)
# 精灵更新处理
def __update_spirits(self):
self.enemyGroup.update()
self.enemyGroup.draw(self.screen)
12.12 音乐导入
#导入音乐
pygame.mixer.init()
def background_music_load():
global hero_fire_music # 导入函数外部变量
pygame.mixer.music.load("./music/PlaneWarsBackgroundMusic.mp3")#游戏背景音乐
pygame.mixer.music.set_volume(0.3)#设置音量(0-1)
pygame.mixer.music.play(-1)#循环播放
hero_fire_music = pygame.mixer.Sound("./music/hero_fire.wav")#飞机开火音乐
hero_fire_music.set_volume(0.2)#设置音量(0-1)
|