Pygame 致力于 2D 游戏的开发
参考pygame菜鸟入门指南
文章目录
一、下载安装 Pygame
安装过程中出现了两个问题:
-
You are using pip version 9.0.1, however version 10.0.1 is available.
You should consider upgrading via the ‘python -m pip install --upgrade pip’ command.原因:这个问题主要是版本没有更新的意思,更新一下就好了
解决方法:输入python -m pip install --upgrade pip
命令更新一下就好了 -
pygame-1.9.3-cp36-cp36m-win_amd64.whl is not a supported wheel on this platform.
原因:版本不对应
解决方案:检查是否下载了正确的对应:python版本,32还是64位
二、Pygame 常用模块
模块名 | 功能 |
---|---|
pygame.cdrom | 访问光驱 |
pygame.cursors | 加载光标 |
pygame.display | 访问显示设备 |
pygame.draw | 绘制形状、线和点 |
pygame.event | 管理事件 |
pygame.font | 使用字体 |
pygame.image | 加载和存储图片 |
pygame.joystick | 使用游戏手柄或者类似的东西 |
pygame.key | 读取键盘按键 |
pygame.mixer | 声音 |
pygame.mouse | 鼠标 |
pygame.movie | 播放视频 |
pygame.music | 播放音频 |
pygame.overlay | 访问高级视频叠加 |
pygame.rect | 管理矩形区域 |
pygame.scrap | 本地剪贴板访问 |
pygame.sndarray | 操作声音数据 |
pygame.sprite | 操作移动图像 |
pygame.surface | 管理图像和屏幕 |
pygame.surfarray | 管理点阵图像数据 |
pygame.time | 管理时间和帧信息 |
pygame.transform | 缩放和移动图像 |
三、常用模块的常用方法
Display 模块的常用方法:
方法名 | 功能 |
---|---|
pygame.display.init() | 初始化display模块 |
pygame.display.quit() | 结束display模块 |
pygame.display.get_init() | 如果display模块已经被初始化,则返回True |
pygame.display.set_mode() | 初始化一个准备显示的界面 |
pygame.display.get_surface() | 获取当前的Surface对象 |
pygame.display.flip() | 更新整个待显示的Surface对象到屏幕上 |
pygame.display.update() | 更新部分内容显示到屏幕上,如果没有参数,则与flip功能相同(上一条) |
Surface 对象的常用方法:
四、知道什么是surface
Pygame的最重要部分是surface。就把surface 想当成一张白纸吧。你要用对一个surface做许多的事——你可以在它上面画线,给它的部分填充颜色,把图像拷进去或者拷出来,设置或者读取它上面的某个单独的像素的颜色值。一个surface可以是任何大小(可以理解)并且你要多少就有多少(也可以理解)。有一个surface是特别的——你用 pygame.display.set_mode()创建的那一个。这个display surface代表了屏幕;你对它做的任何事情都会呈现在用户屏幕上。你只能有一个这玩意——这是SDL的一个限制,而不是pygame的。
但是你怎么创建surface呢?正如上所说,可以用 pygame.display_set_mode()来创建特殊的display surface。你可以用image.load()来创建一个包含了图像的surface,或者你可以用font.render()来创建一个包含了文字的surface。甚至你可以用一个Surface()来创建一个什么都没有的surface。
大部分的surface函数都不是至关紧要的。只要学习blit(),fill(),set_at()和get_at()就够用了。
五、使用surface.convert()
当我第一次阅读surface.convert()的文档时,我并没有意识到这是我要注意的。“我只使用png格式,既然我用的都是同一个格式,所以我不需要convert()”。它证明了我是非常,非常错的。
Convert()所指的“格式”并非指文件格式工(如 png,jpeg,gif),它是所谓的“像素格式”。它代表了一个surface记录一个特定像素的颜色的方法。如果surface格式跟显示格式不一样,那SDL就要在每次blit的时候去转化它——这是个相当费时的过程。不用关心解释,只要注意到如果想在blit之外获得速度,那你就需要 convert()。
那怎么转换呢?只需在用image.load()函数创建了一个surface后调用它。
不使用: surface = pygame.image.load('foo.png')
用:
surface = pygame.image.load('foo.png').convert()
相当简单,你只需要当从硬盘中加载图像的时候,对每个surface调用它一次。你会对结果满意的;我发现使用convert()时,blit的速度有了6倍的提高。
你不需要使用convert()的唯一情况,就是当你真的想对图像的内部格式有绝对的控制权的时候——比如说你正在写一个图像转换程序或者其它的,但是你要保证输出文件和输入文件有相同的像素格式。如果你在写一个游戏,你需要速度(界面显示速度也就是游戏的流畅度),就用convert()。
六、脏矩形动画
Pygame程序中导致帧率不足的的最常见原因就是误用了 pygame.display.update()
函数。在pygame中,仅仅把东西画到display surface中并不会让它显示在屏幕上——你要调用pygame.display.update()。有三种方法去调用它:
pygame.display.update()
——更新整个窗口(或者在全屏显示下是整个屏幕)。
Pygame.display.flip()
——这个干了相同的活,只是如果你同时使用了双缓冲硬件加速时它也会帮你该做的事,但如果你没有,那当什么都没发生过……
Pygame.display.update(一个矩形或者矩形列表)
——这个只更新屏幕上你指定矩形区域。
很多图形编程的新丁使用第一个选择——他们在每一帧里更新整个屏幕。问题是这样做对大多数人来说慢得不能忍受。在我的电脑上调用update()花费35毫秒,听起来不多,除非你看到一秒最多有1000/35=28 帧,并且还是没有任何游戏逻辑,没有blit,没有输入,没有人工智能,什么都没有。我只是坐在凳子上去更新屏幕,而28fps就是我的最高帧率。啊!
解决方法叫做“脏矩形动画”。替换每帧更新整个屏幕,而只更新自上一帧已经改变过的部分。我是通过用一个列表来跟踪这些矩形,然后在帧结束时调用update(the_dirty_rectangles)来实现的。
比如说对于一个移动中的精灵,我:在背景上blit精灵所在的位置,擦掉它。
把精灵的当前区域矩形加到一个叫dirty_rects的列表中去。
移动这个精灵。
在新的区域画这个精灵。
把精灵的新区域加到我的dirty_rects中去。
调用display.update(dirty_rects)
想想Solarwolf有大量持续移动的精灵亦平滑更新,并且还有时间去显示一个视差粒子效果,并且也被更新。
有两种情况是用不上这种技术的。其一是当整个窗口或者屏幕的确需要在每一帧被更新——考虑一下一个平滑滚动的引擎,像一个实时战略游戏或者一个边滚动条。那在这种情况下你应该用什么?呃,简单的答案就是不要在 pygame中写这种游戏。而详细的答案是一个步骤里滚动数个像素,试要企图做出绝对平滑的滚动。玩你游戏的人会喜欢一个滚动得快的游戏,而不太会注意到背景的跳跃。
最后一点——不是所有的游戏都需要高帧率。一个策略战争游戏在一秒钟内只需要更新几次就足够了——在这种情况下,脏矩形动画带来的复杂性是多余的。
七、硬件surface弊大于利
如果你已经看过可以用在pygame.display.set_mode()中可以使用的众多标志值时,你可能会这样想:“嘿,HWSURFACE!嗯,我需要它——谁不喜欢硬件加速?噢……DOUBLEBUF;嗯,听起来挺快的,我看我也需要它!”这不是你的错,我们经受过多年的3D游戏训练,已经默认了把硬件加速是优秀的,而软件加速是缓慢的。很不幸,硬件渲染天生就有一长串的缺陷:
它只在能在某些平台上运行。在Windows上,你通常可以得到硬件surface。大多数的其它平台却不能。比如说Linux,它也许会提供硬件 surface,如果安装了X4,如果DGA2正常运行起来,如果moons也被正确对齐了。如果不能给你硬件surface,SDL会悄悄地给你一个软件surface。
它只能在全屏模式下工作.
它复杂化了每个像素的访问。如果你有一个硬件surface,你必须在读写单独的像素值的时候锁定屏幕。如果你不这样做,就会坏了大事。接着你又要赶在系统被搞蒙并且开始抱怨之前马上解锁。这些过程都是pygame自动为你做的,但是它值得注意。
你没有了屏幕光标。如果你指定要HWSURFACE(并且真的拿到手了),你的光标通常会消失掉(更严重的是只剩下半个在一闪一闪的)。你只有创建一个精灵来扮演鼠标光标的角色,并且还要承担光标加速和敏感度的责任。真烦人。
它有可能变得更慢。许多驱动程序并不会加速我们作图类型,并且所有东西都必须通过视频总线来blit(除非你能用源surface来充满显存),结果就是比软件访问更慢。
硬件渲染有它存在的理由。在Windows下它运行地相当可靠。所以如果你不关于跨平台的性能时,它可以给你带来看得见的速度提升。不过,它也有代价——更多的头疼和复杂性。除非你知道你在干什么,否则最好就是坚持使用较好可信赖的的SWSURFACE。
八、不要纠缠于细枝末节
有时候,游戏编程新人在某些对游戏的成功并非紧要的地方花了太多时间。把将要的要素做“对”是可理解的,但是在创作游戏的早期,你并不知道哪些是重要的问题,更不要说你应该选择的答案了。结果就是带来一堆的借口。
举例说,思考一下怎么样组织你的图形文件的问题。是每一帧有它自己的图像文件好呢,还是每个精灵都有?或者把所有的图像都打包成一个压缩包?许多项目的许多时间被浪费在在邮件列表提问,争论问题的答案,比较,等等等等。这些都是次要矛盾;花在争论上的时间原本都应该用到编码实战游戏中去。
这里的主旨就是说,一个已经实现了的“恰当地好”的解决方案,要远远优于一个没有开始动手的完美的解决方案。
九、Rect是你的好朋友
Pete Shinners的封闭可能有很酷的alpha效果,和快速的blit速度,但是我不得不说我最喜欢pygame部分是底层的Rect类。一个rect就是一个矩形——由它左上角的位置,它的宽度,它的高度定义。Pygame的许多函数都用rect作参数,也接受“矩形形式”,一个跟rect有相同值的序列。因此,如果我需要一个位于10, 20和40, 50之间的区域时,我可以做以下几个中的一个:
rect = pygame.Rect(10, 20, 30, 30) rect = pygame.Rect((10, 20, 30, 30)) rect = pygame.Rect((10, 20), (30, 30)) rect = (10, 20, 30, 30) rect = ((10, 20, 30, 30))
如果你使用开头三个中的任何一个,你就可以使用rect的实用函数。它们包括移动,收缩和膨胀矩形,找出两个矩形的并集,和一堆的碰撞检测函数。
例如,假设我想得到包含了点(x, y)——可能用户点击了这里,也可能是子弹的当前位置——的所有精灵。如果每个精灵都有一个.rect成员,事情就会很简单——我只消:
sprites_clicked = [sprite for sprite in all_my_sprites_list if sprite.rect.collidepoint(x, y)]
除了能用rect作为参数,rect 跟surface和图像函数没别的瓜葛。你也可以把它们用到跟图形没啥关系,却又需要矩形的地方去。我几乎在每个工程里都发现几个需要使用rect的地方,而我从来没想到我也要在这里用上它们。
十、不要对像素级的碰撞检测费心
至此你已经让你的精灵动了起来,你需要知道他们到底会不会撞上别人。像下面这样做是很有诱惑性的:
看看矩形是不是碰撞了,如果不是,忽略它们。
对于重叠区域的每一个像素,检查它在每个精灵对应的像素是不是不透明的,如果的确是这样,则它们碰撞了。
有很多方法实现这个想法,对精灵进行“与运算”等,但无论如何,它都会很慢。甚至对多数游戏来,更恰当的是做“子矩形碰撞”——对每个精灵做一个比其真实图像略小的矩形,用它来进行碰撞。这样会快得多,并且大数多情况下玩家不会注意到这个不精确的做法。
十一、管理好事件子系统
Pygame的事件系统很巧妙。有两种不同的方法找出输入设备(键盘,鼠标或游戏控制杆)在做什么。
第一种方法是直接检查设备的状态。通过调用叫pygame.mouse.get_pos()或pygame.key.get_press()的方法来实现。它们会告诉你当你调用这函数时设备的状态。
第二种方法是使用SDL的事件队列。这个队列是一个事件的列表——当事件发生时就会被加到列表中,如果被读过了,它们就会被删除掉。
这两套系统各有利弊。状态检查(第一套系统)很精确——你准确知道一个输入什么时候发生——如果mouse.get_pressed([0])的值是1,说明那时的鼠标左键点下了。而事件队列仅仅告诉我们在过去某个时间鼠标被点下了;如果你很频繁地检查队列,那没事,但如果你延迟了检查,潜在的输入就会越来越多。状态检查系统的另一个优势就是可以很方便地检测“和音 ”,也就是说,在同一时间里同时生了多种状态。如果你要检查t和f键是不是被同时按下了,只要检查:
if (key.get_pressed[K_t] and key.get_pressed[K_f]):
print “Yup!”
在队列系统中,每个到达队列的按键都作为一个单独的事件,所以你必须记得,在你检查f键的时候,t键被按下了但没有弹起。有点复杂。
但状态系统有一个巨大的缺陷。它只报告被调用的那一刻的设备状态。如果鼠标被按下了,并且在释放之前调用了mouse.get_pressed(),鼠标会返回0——get_pressed()完全错过了鼠标按下事件。这两个事件,MOUSEBUTTONDOWN和MOUSEBUTTONUP,仍然被放到事件队列中去,等待读取和处理。
教训就是:选择最适合你的需要的系统。如果不需要在循环中做太多事——也就是说你只需坐在板凳上,在一个’while 1’循环中待输入,用get_pressed()或者其它状态函数,延迟会比较短。相反,如果每一个按键都很关键,但是延迟不那么重要——比如你的用户在一个编辑框里输入某些东西,就使用事件队列。某些按键会有点延迟,但至少你会一个不缺。
关于event.poll()和wait()对比的注记—— poll()看起来会好点,因为它在等待输入的时候不会阻塞你的程序做事。Wait()则挂起程序,直到接收到一个事件。不过,poll()运行时会花掉所有可用的CPU时间,并且它会用NOEVENTS来塞满事件队列。用set_blocked()来选择你只需要哪些事件类型——你的队列看起来会更好管理。
十二、色键 vs Alpha
这两种技术常常混淆,并且大多来自于对术语的误用。
“色键”告诉pygame,特定图像的具有某种特定颜色的所有像素都被当成透明的。当其它部分的图像被blit的时候,这些透明像素不会被blit,所以不会破坏背景。这就是我们怎么样使得精灵的形状不是矩形的。简单调用surface.set_colorkey(color),其中color是一个RGB元组——像(0, 0, 0)。它会把图像中黑色的像素当作透明的。
“Alpha”则不同,它有两种形式。“图像Alpha”应用于整个图像,这通常是你需要的。如大家所知的“半透明”,alpha让源图像的每个像素只有部分不透明。比如说,你设置了一个surface的alpha为 192,然后把它blit到一个背景上去时,每个像素的3/4颜色值会来自源图像,而1/4来自背景图像。
Alpha用255到0的一个数来衡量,其中0 表示完全透明,255表示完全不透明。注意,颜色键和alpha可以混合——产品就是一部分完全透明另一部分半透明的图像。
“按像素alpha”是alpha的另一种表现,它更加复杂。简单说,源图像上每个像素都有它自己的alpha值,从0到255。
在blit到背景上的时候,每个像素都有一个不同的不透明度。这种类型的alpha不能跟色键混使用,并且要覆盖整个图像alpha。游戏中很少会用到按像素alpha的,如果要用它,你要先用一个图像编辑器让它保留下一个alpha通道。很复杂——不要用它了。
十三、简单示例
import pygame
import sys
# 初始化 pygame
pygame.init()
size = width,height = 800,600
speed = [-2,1]
bg = (255,255,255) # 背景设置为白色
# 创建指定大小的窗口 Surface
screen = pygame.display.set_mode(size)
# 设置窗口的标题
pygame.display.set_caption("初次见面,请大家多多关照")
# 加载图片
chicken = pygame.image.load("chicken.png")
# 获得图像的位置矩形
position = chicken.get_rect()
# 设置为死循环,确保窗口一直显示
while True:
# 遍历所有的事件
for event in pygame.event.get():
# 如果单击关闭窗口,则退出
if event.type == pygame.QUIT:
sys.exit()
# 移动图像
position = position.move(speed)
# 反转图像
if position.left < 0 or position.right > width:
chicken = pygame.transform.flip(chicken,True,False)
# 反方向移动
speed[0] = -speed[0]
if position.top < 0 or position.bottom > height:
speed[1] = -speed[1]
# 填充背景
screen.fill(bg)
# 更新图像
screen.blit(chicken,position)
# 更新界面
pygame.display.flip()
# 延迟 10 毫秒
pygame.time.delay(10)
效果图:
十四、补充解释
set_mode:返回一个 Surface 对象,代表了在桌面上出现的那个窗口,三个参数第一个为元祖,代表分辨率(不可省略);
第二个是标志位如下,如果不用指定什么特性默认为0
第三个是色深,通常最好不要传递深度参数。它将默认为系统的最佳和最快颜色深度。
blit:将一个图像画到另一图像上,作为背景的图像会被模糊化,以使突出显示目标图像的效果