初始pygame
首先我们知道,pygame是Python专门用来制作游戏的一个模块,那么它有那些功能呢?
• 它可以显示文字,绘制图形(比如圆形、三角形等),显示图片,实现动画效果,能够与键盘、鼠标、游戏手柄等外设交互,播放声音,支持碰撞检测。
同样,pygame模块中有很多子模块,比如:
• pygame.cursors,用来加载光标的模块;
• pygame.display,用来控制显示的窗口;
• pygame.draw,用来绘制形状、线、点等;
• pygame.event,用来管理事件(比如鼠标左键点击的事件)以及事件队列;
• pygame.image,用来加载图片;
等等……
pygame程序框架
我们接下来学习写第一个pygame程序,用pygame做游戏首先需要建立一个窗口
• 在这个程序中,我们要设定并绘制游戏的窗口,为游戏定义一个名字,先不添加游戏的内容。最后实现退出游戏的机制。
• 首先导入我们需要的模块,以便于我们的程序使用这些模块中的一些方法(函数),实现游戏设计。
#sys模块提供了一系列python程序运行环境的变量和函数(方法),比如退出程序的函数:sys.exit()
import sys
#pygame模块提供了游戏设计中用到的加载图片、声音等函数(方法)
import pygame
#pygame.locals模块包含了一些程序常用的常量,使用from modulename import *的方式导入模块,使
得后期使用模块中的函数或变量时更加方便,直接使用变量的名字就可以,不用modulename(模块名).variable(变量)这样复杂的格式。比如QUIT常量直接使用,而不是pygame.locals.QUIT。
from pygame.locals import *
在导入模块之后,需要做的一件非常重要的事情,就是初始化pygame。在导入模块之后,调用其他函数之前,一定要做初始化pygame的操作,以保证后面pygame的函数能够正常工作。
pygame.init() #初始化pygame
初始化完成以后,我们就可以大胆的开始游戏设计了。首先,我们要做的是,定义游戏的窗口大小。
#利用元组的数据格式定义表示窗口大小的变量screen_size
screen_size = width, height = 480, 700
#将元组变量screen_size传给set_mode()函数,来设定窗口的宽和高。调用该函数将返回一个pygame.surface对象,并起名字为screen。(pygame.surface将在后面的课程中讲到。)
screen = pygame.display.set_mode(screen_size)
给游戏起一个名字,定义一个标题。
#定义一个标题名字,将显示在游戏窗口的左上角。
pygame.display.set_caption("myFirstGame")
接下来,进入游戏主循环,我们利用无限循环实现,除非玩家把窗口关闭,中断游戏,方可退出。
while True:
for event in pygame.event.get(): #通过for循环遍历获取到的游戏事件。
if event.type == QUIT: #如果获取到的游戏事件是退出,则将pygame退出,将python程序环境
退出,关闭游戏软件。
pygame.quit()
sys.exit()
pygame.display.flip() #更新整个待显示的surface对象到屏幕上。
整体来看,游戏的主循环是重复获取事件(比如鼠标点击、键盘方向键),根据事件更新游戏的状态,然后将此次循环最新的状态画到窗口上。
完整的程序如下:
import sys
import pygame
from pygame.locals import *
pygame.init()
screen_size = width, height = 480, 700
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption("myFirstGame")
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
pygame.display.flip()
这样运行的结果是黑色背景,这时候可以用颜色填充背景,也可以加载图片来装饰背景
颜色填充背景:
import sys
import pygame
from pygame.locals import *
pygame.init()
screen_size = width, height = 480, 700
bg = (0, 255, 0) #定义纯色背景的颜色RGB数值(我们这里是绿色)
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption(“myFirstGame”)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
screen.fill(bg) #调用fill()函数,使用纯色填充surface对象,即“screen”让自己变成绿色
的。
pygame.display.flip()
下面我们在游戏里添加一个图片
import sys
import pygame
from pygame.locals import *
pygame.init()
screen_size = width, height = 480, 700
bg = (0, 255, 0)
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption(“myFirstGame”) #调用image子模块的load函数,返回一个对象,起名字为heroPlane.
heroPlane = pygame.image.load(“me1.png”) #获取图像所在的矩形区域rect对象(图像左上角的坐标,图像大小width*height),总是以(0,0)为起点。
position = heroPlane.get_rect() # rect是用来存储矩形坐标的pygame对象,相当于一个优盘。
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
screen.fill(bg)
#调用blit()函数,将一个图像(heroPlane)绘制到另一个图像上面(screen),位置为position
screen.blit(heroPlane, position)
pygame.display.flip()
结果就是一架小飞机出现在屏幕左上角,那怎么让他“飞起来呢”?
首先,我们先查看一下position(rect对象)的值,使用print()函数,将其打印出来。
结果如下:
#前两位是图像左上角的坐标点。
后两位是图像的大小width*height。
因此,想要移动图像,只需要修改position中坐标点的值即可,也就是让图像在不同的坐标点显示。
下面,我们可以调用rect对象的move()来移动图像,现在我们的rect对象的名字是position哦。
speed = [1, 1] #图像移动的速度,即每次移动图像的坐标在x和y方向均增加1个像素点
……[省略多行代码]
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
#调用move()函数移动图像,返回值仍为rect对象,并再次赋值给position
position = position.move(speed)
screen.fill(bg)
#使用更新后的position绘制对象
screen.blit(heroPlane, position)
pygame.display.flip()
surface对象、rect对象
前面我们用到了surface对象和rect对象,没有详细的讲,现在介绍一下
surface对象就是pygame中用来表示图像的对象,简单的说,它表示的就是图像。
怎样创建surface对象?有两种方法:
将一个图像绘制到另一个图像上是怎样实现的呢?
上节课我们通过调用blit()函数,将heroPlane图像绘制到了screen图像上,源代码如下:
screen.blit(heroPlane, position)
难道真的是画上去的吗?
首先,我们先了解一下图像是什么样的?
图像是由像素组成的,当我们将一个图像放大时,会观察到图像是由很多个方块(像素点)组成的
pygame将一个图像绘制到另一个图像上面,实际上是通过修改底图对应位置的像素点的颜色实现的,从而达到覆盖的效果。
我们上节课做的程序中,在screen图像上绘制小飞机图像,就是将小飞机所占区域像素点的绿色修改为了小飞机对应的像素颜色。
我们知道显示一个图像,是通过修改对应位置像素点的颜色实现的。那么,移动一个图像的实现原理是什么呢?
上节课的程序中,对应以上步骤的代码如下:
position = position.move(speed)
screen.fill(bg)
screen.blit(heroPlane, position)
pygame.display.flip()
rect对象是用来存储矩形坐标的pygame对象。
在上一节课的程序中,我们使用surface对象的一个方法get_rect(),创建了一个rect对象,并命名为position,用来获取heroPlane的坐标及图像大小。
position = heroPlane.get_rect() #创建heroPlane的rect对象,并命名为position
并对该rect对象进行移动的操作。
position = position.move(speed) #调用rect对象的move()方法,调整位置,并返回新的rect对象,赋值给position
rect对象表示的是一个矩形区域。
刚加载时,左上角坐标为(0,0)
上节课我们的小飞机飞走了(飞出了窗口……),现在我们要把它困在窗口中,当小飞机遇到窗口边界时,就反向飞。想一想,要实现这个功能,需要哪些步骤?
我们在上节课程序的基础上添加程序,上节课的程序已经可以让小飞机飞了
……
speed = [1, 1] #设定小飞机移动的速度
……
……
position = position.move(speed) #更新小飞机的位置,position是一个rect对象
screen.fill(bg) #填充纯色背景,覆盖之前的图像
screen.blit(heroPlane, position) #将更新后的小飞机绘制在背景图上
pygame.display.flip() #更新整个画面,双缓存机制,前面先将背景和小飞机更新但不显示,使用pygame.display.flip() 同时更新所有画面
……
screen_size = width, height = 480, 700
speed = [1, 1] #设定小飞机移动的速度
……
……
position = position.move(speed) #更新小飞机的位置,position是一个rect对象
#如果小飞机的左侧坐标小于0或者右侧坐标大于窗口的宽度width,则说明水平方向要飞出窗口了
if position.left < 0 or position.right > width:
# flip(Surface, xbool, ybool) -> Surface,水平或垂直翻转图像,返回值赋值给heroPlane
heroPlane = pygame.transform.flip(heroPlane, True, False)
speed[0] = -speed[0] # 翻转后,水平方向的移动速度反向
#如果小飞机的顶部坐标小于0或者底部坐标大于窗口的高度height,则说明垂直方向要飞出窗口了
if position.top <0 or position.bottom > height:
speed[1] = -speed[1] #垂直方向不翻转,保持飞机头向上,垂直方向的移动速度反向
飞机跑得太快的话可以延迟,需要用到时间模块
time模块在pygame中用来管理时间。
将暂停给定的毫秒数(括号中的参数应传入毫秒数)。该函数(方法)会将游戏进程休眠,可以与其他程序共享处理器,占用很少的处理器资源,但是准确度不够高。
下面,我们让小飞机每延时20ms移动一次。
……[省略代码]
screen.fill(bg)
screen.blit(heroPlane, position)
pygame.display.flip()
pygame.time.wait(20) #延时20ms再执行下面测程序
将暂停给定的毫秒数(括号中的参数应传入毫秒数)。该函数(方法)不会让游戏进程休眠,会占用处理器资源,但是时间比wait()更准确。
下面,我们让小飞机每延时20ms移动一次。
……[省略代码]
screen.fill(bg)
screen.blit(heroPlane, position)
pygame.display.flip()
pygame.time.delay(20) #延时20ms再执行下面测程序
创建一个可用于跟踪一段时间的Clock对象,该Clock对象还提供了几个函数(方法)来控制游戏的帧率。比如tick()方法可以设置游戏的帧率,也只会占用很少的处理器资源。
……[省略代码]
clock = pygame.time.Clock() #创建一个Clock对象,并命名为clock
……[省略代码]
screen.fill(bg)
screen.blit(heroPlane, position)
pygame.display.flip()
clock.tick(50) #程序以每秒不超过50帧的速度运行。
pygame中的事件
事件可以处理游戏中的各种事情。简单的说,很多程序都需要对“发生的事情”做出反应,比如说:
Pygame会接受用户的各种操作(比如按键盘,移动鼠标等)产生的事件。事件随时可能发生,而且量也可能会很大,pygame的做法是把一系列的事件存放在一个队列里,逐个处理,我们把这个队列称为事件队列。
我们在之前的程序中使用了如下代码:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
我们使用pygame.event.get()来获取事件,并用for循环遍历事件队列,只针对有用的事件作出处理,比如关闭窗口时产生的QUIT事件,该事件发生时,退出pygame及sys。这就是事件检索及处理。
下面,我们来写一个程序,实现用txt文件记录所有的事件。
import sys
import pygame
from pygame.locals import *
pygame.init()
screen_size = width, height = 480, 700
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption(“RecordEvent”)
#创建并打开一个txt文件,用来记录事件信息
f = open(“record.txt”, ‘w’)
while True:
for event in pygame.event.get(): #检索所有的事件
f.write(str(event) + ‘\n’) #将事件信息转换为字符串,并吸入文件
if event.type == QUIT:
f.close() #当关闭窗口以后,关闭文件
pygame.quit()
sys.exit()
pygame.display.flip()
结果示例:
鼠标移动的事件、鼠标所在游戏窗口中的坐标、针对上一个点的坐标差、左中右三个键是否被按下
有没有更酷的方法来显示所有发生的事件呢?能不能把事件信息显示在游戏窗口上呢?窗口背景还要是黑色的,显示的字要是绿色的,还能‘唰唰唰…’地显示在窗口上。
步骤:
先设置一个游戏窗口,并填充颜色 。Pygame不能直接在窗口打印文字。但是可以通过font对象的render()方法将文字渲染成surface对象(一个图像),然后调用blit()方法将该surface对象绘制到窗口surface对象上,就可以实现显示文字的效果啦!
pygame.font模块是pygame中加载和表示文字的模块,该模块包含一个类pygame.font.Font,用来从一个字体文件创建一个font对象。
#filename为字体文件的名字,None则表示使用系统默认字体;size为字体大小
Font(filename, size) -> Font
Font(object, size) -> Font
font对象包含很多有用的方法,比如render()方法能够创建一个新的surface对象,并在上面渲染指定的文本,该方法会返回一个新的surface对象;
get_linesize()方法可以获取字体文本的行高。
下面我们将显示文字的程序添加到基础框架程序上,创建字体对象->获取行高。
import sys
import pygame
from pygame.locals import *
pygame.init()
bg = (0, 0, 0)
screen_size = width, height = 480, 700
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption("displayRecord")
screen.fill(bg)
#文字显示
font = pygame.font.Font(None, 20) #使用Font类创建一个字体对象,并命名为font
height_per_line = font.get_linesize() #使用font对象的get_linesize()方法获取每行文字的行高(占多少像素)
lineHeight = 0 #定义行垂直坐标的递增变量。
下面我们将显示文字的程序实现出来,创建文字surface对象->绘制到窗口surface对象上。
while True:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
pygame.quit()
#调用font对象的render()方法创建文字surface对象,并命名为font_surface
font_surface = font.render(str(event), True, (0, 255, 0)) #参数分别为要显示的文字,抗锯齿,字体颜色
#将文字surface对象(图像)绘制在窗口surface对象(图像)上,从每一行的左上角的像素开始。
screen.blit(font_surface, (0, lineHeight))
lineHeight += height_per_line #每绘制完一行,行高变量自增加每一行的高度
#如果行高变量大于窗口高度时,说明窗口占满,需清零行高,以及重新用黑色覆盖所有的文字
if lineHeight > height:
lineHeight = 0
screen.fill(bg)
pygame.display.flip()
下面的表中列出了pygame中常用的事件类型,大家可以随时查阅。
游戏需要有互动,我们要能控制它来完成游戏的任务,下面我们来使用键盘的方向按键操控小飞机。
现在窗口左上角显示小飞机
下面,添加代码,我们要让小飞机动,动,动起来~
先初始化速度(包含移动方向)
pygame.init()
bg = (0, 255, 0)
#初始化移动速度,用元组数据表示,第一个元素表示左右移动,第二个元素表示上下移动
speed = [0, 0]
screen_size = width, height = 480, 700
判断KEYDOWN事件,并判断哪个键被按下,修改速度speed的值,改变小飞机移动方向和速度。
while True:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
pygame.quit()
if event.type == KEYDOWN: #判断KEYDOWN事件
#判断哪个按键被按下,第一个元素-1和1分别表示向左和向右移动一个像素点,
#第二个元素-1和1分别表示向上和向下移动一个像素点。
if event.key == K_LEFT:
speed = [-1, 0]
if event.key == K_RIGHT:
speed = [1, 0]
if event.key == K_UP:
speed = [0, -1]
if event.key == K_DOWN:
speed = [0, 1]
else:
speed = [0, 0] #判断如果按键按下的事件,则速度清零,小飞机不移动
更新position,移动小飞机。
现在小飞机可以随着我们按方向键移动了,但是它会飞出去,下面我们要解决这个问题。不能让小飞机飞出游戏窗口。
限制小飞机飞出游戏窗口。
#如果超出边界,那么小飞机的位置等于边界值,然后调用move()方法,更新小飞机的位置。
if position.left < 0:
position.left = 0
if position.right > width:
position.right = width
if position.top <0:
position.top = 0
if position.bottom > height:
position.bottom = height
position = position.move(speed)
让游戏更酷一点:
前面我们写的程序,都是通过pygame.display.set_mode()方法来指定游戏窗口的大小,并且返回一个surface对象,首先,我们先详细讲解一下set_mode()方法。
set_mode(resolution=(0,0), flags = 0, depth = 0) -> surface
# resolution是一个二元组,表示宽和高,用于指定游戏界面的大小
# flags用来指定附加选项,指定你想要显示的类型,多个附加选项用管道操作符‘|’添加,比如传入FULLSCREEN | HWSURFACE
# depth表示颜色深度,一般不设置。
酷!全屏玩游戏!我们在上节课的控制小飞机的程序基础上修改,增加全屏的功能(按F11全屏,再按F11退出全屏)。
……[省略代码]
pygame.init()
bg = (0, 255, 0)
speed = [0, 0]
fullscreen = False #设置游戏窗口全屏的标志,True表示全屏,False表示窗口
……[省略代码]
while True:
for event in pygame.event.get():
……[省略代码]
if event.type == KEYDOWN:
if event.key == K_LEFT:
speed = [-1, 0]
……[省略代码]
if event.key == K_F11: #按下F11按键是切换全屏及窗口
fullscreen = not fullscreen #全屏标志切换
if fullscreen:
#第一个参数为电脑全屏的窗口(分辨率)大小,第二个参数为全屏且启用硬件加速
screen = pygame.display.set_mode((1366,768), FULLSCREEN | HWSURFACE)
else:
screen = pygame.display.set_mode(screen_size) #切换为窗口
else:
speed = [0, 0]
但是有一个问题,如果我的电脑分辨率不是1366*768,怎么办?
import pygame
pygame.init()
modesList = pygame.display.list_modes() #获取电脑支持的分辨率列表
print(modesList)
得到的结果如下,全屏的分辨率为列表中的第一个元素:
[(1366, 768), (1360, 768), (1280, 768), (1280, 720), (1280, 600), (1024, 768), (800, 600), (640, 480), (640, 400), (512, 384), (400, 300), (320, 240), (320, 200)]
所以我们可以自动获取电脑的全屏分辨率,使得游戏适应不同的电脑
……[省略代码]
pygame.init()
modesList = pygame.display.list_modes() #获取电脑屏幕分辨率列表
fullscreen = False #设置游戏窗口全屏的标志,True表示全屏,False表示窗口
……[省略代码]
while True:
for event in pygame.event.get():
……[省略代码]
if event.type == KEYDOWN:
if event.key == K_LEFT:
speed = [-1, 0]
……[省略代码]
if event.key == K_F11: #按下F11按键是切换全屏及窗口
fullscreen = not fullscreen #全屏标志切换
if fullscreen:
#第一个参数为自动获取到的电脑全屏的窗口(分辨率)大小,第二个参数为全屏且启用硬件加速
screen = pygame.display.set_mode(modesList[0], FULLSCREEN | HWSURFACE)
else:
screen = pygame.display.set_mode(screen_size) #切换为窗口
else:
speed = [0, 0]
然而,还有一个问题,为什么全屏以后,小飞机能移动的区域还是只有原来窗口的大小呢?
if position.left < 0:
position.left = 0
#全屏时,限制宽度应该是窗口全屏的width;窗口时,限制宽度应该是窗口的width
if fullscreen == True and position.right > modesList[0][0]:
position.right = modesList[0][0]
elif fullscreen == False and position.right > width:
position.right = width
if position.top <0:
position.top = 0
#全屏时,限制高度应该是窗口全屏的height;窗口时,限制高度应该是窗口的height
if fullscreen == True and position.bottom > modesList[0][1]:
position.bottom = modesList[0][1]
elif fullscreen == False and position.bottom > height:
position.bottom = height
position = position.move(speed)
transform模块是pygame中用来改变或变换surface对象(图像)的模块。
比如我们第二节课用到的pygame.transform.flip()方法是用来水平或者垂直翻转图像的。
pygame.transform.scale()方法可以按照一定的尺寸缩放图像。
全屏和退出全屏时,对游戏背景图片的缩放操作。
if event.key == K_F11:
fullscreen = not fullscreen
if fullscreen:
screen = pygame.display.set_mode(modesList[0], FULLSCREEN | HWSURFACE)
#将obg图像放大到全屏的尺寸,并返回surface对象赋值给background
background = pygame.transform.scale(obg, modesList[0])
else:
screen = pygame.display.set_mode(screen_size)
#将obg图像缩小到窗口尺寸大小,并返回surface对象赋值给background
background = pygame.transform.scale(obg, screen_size)
将背景图绘制到游戏窗口上。
screen.blit(background, (0, 0)) #将处理过的background图像绘制到窗口上
screen.blit(airPlane,position)
pygame.display.flip()
我们在玩一些其他游戏的时候,会遇到游戏人物变大变小的效果,比如超级玛丽、球球大作战。下面我们要让我们的小飞机能够变大变小,通过按键控制变大变小。
程序实现,首先定义缩放的比率,加载小飞机的图像。
ratio = 1.0 #定义小飞机缩放的比率,初始化为1.0,即原始比例
airPlane = pygame.image.load(“me1.png”) #加载小飞机图像,缩放的时候以airPlane为原始图
newAirplane = airPlane #定义newAirplane,用于保存缩放后的图像
airPlane_rect = airPlane.get_rect() #获取小飞机图像的rect对象
#position是用于更新小飞机位置的rect对象,newAirplane_rect用来保存缩放后小飞机图像的rect对象
position = newAirplane_rect = airPlane_rect
按‘=’按键( K_EQUALS )放大,按‘-’按键( K_MINUS )缩小,按空格按键( K_SPACE )返回原始尺寸。
if event.type == KEYDOWN:
……[省略代码]
if event.key == K_EQUALS or event.key == K_MINUS or event.key == K_SPACE:
#最大放大比例不能超过2倍
if event.key == K_EQUALS and ratio < 2.0:
ratio += 0.1 #每次按下‘=’按键,放大0.1倍
#最小缩小比例不能低于0.5倍
if event.key == K_MINUS and ratio >0.5:
ratio -= 0.1 #每次按下‘-’按键,缩小0.1倍
if event.key == K_SPACE:
ratio = 1.0 #按下空格键,返回原始比例
#在原始图片airPlane基础上按一定的大小(width*height)缩放,并返回新的
#surface对象给newAirplane,缩放的大小尺寸必须用整数,所以要强制转换成int型
newAirplane = pygame.transform.smoothscale(airPlane, \
(int(airPlane_rect.width * ratio), \
int(airPlane_rect.height * ratio)))
newAirplane_rect = newAirplane.get_rect() #获取缩放后的小飞机的rect对象
#将缩放后的小飞机的左上角位置赋值给position
position.width, position.height = newAirplane_rect.width, newAirplane_rect.height
绘制缩放后的小飞机以及整个画面。
screen.blit(background, (0, 0))
screen.blit(newAirplane,position) #这里要用缩放后新的小飞机图像newAirplane绘制
pygame.display.flip()
有没有发现一个问题,添加背景图以后小飞机移动的慢了,没有纯色背景的时候移动的快。下面我们优化一下程序,使得程序效率更高,小飞机移动的会快一点。
#利用surface对象的convert_alpha()方法或convert()方法,加载图片并转换图片的像素格式
obg = pygame.image.load("background.png").convert_alpha()
airPlane = pygame.image.load("me1.png").convert_alpha()