分享

pygame菜鸟入门指南

 昵称QAb6ICvc 2013-01-25
当前位置: 火魔网 ? 程序开发 ? Python

pygame菜鸟入门指南

-
pygame是一个由Pete Shinners编写的SDL的Python封装。这就意味着,使用pygame,你可以写出可以不作修改就可以在任何支持SDL的平台(Windows, Unix, Mac, BeOS,等等)上运行的游戏或者多媒体程序。

Pygame不难学,但是图形编程的世界却会让新手糊涂。我撰写此文的目的,在于抽取出过去几年来在pygame和它的先驱,pySDL上的工作经验中获得的实用知识。我已经尝试着要把这些建议按照它们的重要程度来分等级,但是它(指“重要程度”)却又与你的背景知识和你的工作细节相关。

1.熟练使用Python。

最重要的事情是要有信心使用Python。如果你不熟悉你所使用的语言,那在学习实际上很复杂的东西比如说图形编程,这将会成为一个累赘。用python写几个有一定规模的非图形程序——解析某些文本文件,写一个猜数游戏或者一个流水分录程序,或者别的什么。习惯于字符串和列表操作——学会怎么样分割,分片,以及合并字符串或者列表。知道import是怎么样工作的——试着一个由数个源文件组成的程序。编写你自己的函数,并且练习操作数字和字符;知道怎么样在它俩之间转换。直到使用列表和字典已经成为第二本能——你不用每次分片一个列表或者排序一个关键值集合的时候都求助于文档。当你遇到挫折时,抵制向邮件列表,comp.lang.python,或者irc求救的诱惑。打开解释器,坐下来跟问题干上几个小时。把《Python 2.0 快速指南》打印出来,并且放到电脑边上。

听起来难以置信的无聊,但是在你熟悉python的过程中所获得的益处却会在编写你自己的游戏时体现出来。跟你以后编写实战代码时省下的时候相比,熟悉python所花的时候不值一提。

2.识别pygame的哪些部分是你所需要的。

看着pygame文档顶部那些杂乱的类要让人眼花。重要的是认识到只利用这些功能的一个小子集就可以帮助你完成许多事。许多类你压根就不会用到——在一年里,我没有接触到过 Channel,Joystick,cursors,Userrect,surfarray或者版本函数。

3.知道什么是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()就够用了。

4.使用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')

Do:用:

surface = pygame.image.load('foo.png').convert()

相当简单,你只需要当从硬盘中加载图像的时候,对每个surface调用它一次。你会对结果满意的;我发现使用convert()时,blit的速度有了6倍的提高。

你不需要使用convert()的唯一情况,就是当你真的想对图像的内部格式有绝对的控制权的时候——比如说你正在写一个图像转换程序或者其它的,但是你要保证输出文件和输入文件有相同的像素格式。如果你在写一个游戏,你需要速度,就用convert()。

5.脏矩形动画

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中写这种游戏。而详细的答案是一个步骤里滚动数个像素,试要企图做出绝对平滑的滚动。玩你游戏的人会喜欢一个滚动得快的游戏,而不太会注意到背景的跳跃。

最后一点——不是所有的游戏都需要高帧率。一个策略战争游戏在一秒钟内只需要更新几次就足够了——在这种情况下,脏矩形动画带来的复杂性是多余的。

6 . 硬件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。

7.不要纠缠于细枝末节。

有时候,游戏编程新人在某些对游戏的成功并非紧要的地方花了太多时间。把将要的要素做“对”是可理解的,但是在创作游戏的早期,你并不知道哪些是重要的问题,更不要说你应该选择的答案了。结果就是带来一堆的借口。

举例说,思考一下怎么样组织你的图形文件的问题。是每一帧有它自己的图像文件好呢,还是每个精灵都有?或者把所有的图像都打包成一个压缩包?许多项目的许多时间被浪费在在邮件列表提问,争论问题的答案,比较,等等等等。这些都是次要矛盾;花在争论上的时间原本都应该用到编码实战游戏中去。

这里的主旨就是说,一个已经实现了的“恰当地好”的解决方案,要远远优于一个没有开始动手的完美的解决方案。

 8.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的地方,而我从来没想到我也要在这里用上它们。

9.不要对像素级的碰撞检测费心

至此你已经让你的精灵动了起来,你需要知道他们到底会不会撞上别人。像下面这样做是很有诱惑性的:

  • 看看矩形是不是碰撞了,如果不是,忽略它们。

  • 对于重叠区域的每一个像素,检查它在每个精灵对应的像素是不是不透明的,如果的确是这样,则它们碰撞了。

有很多方法实现这个想法,对精灵进行“与运算”等,但无论如何,它都会很慢。甚至对多数游戏来,更恰当的是做“子矩形碰撞”——对每个精灵做一个比其真实图像略小的矩形,用它来进行碰撞。这样会快得多,并且大数多情况下玩家不会注意到这个不精确的做法。

10.管理好事件子系统

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()来选择你只需要哪些事件类型——你的队列看起来会更好管理。

11.色键 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通道。很复杂——不要用它了。

12.用Python的方法去干活。

最后一个要点(这并不是最不重要的一个,只是它恰好应该放在最一个说)了。Pygame是SDL的一个优秀的轻量级封装,同时也是你本地操作系统图形调用的一个完美的轻量级封装。如果你的代码依然很慢,而你已经做完了我上面提到的事情,那么大好机会来了,你所面对的问题就是你怎么样组合Python代码造成的。某些运行得很慢的惯用语跟你所做的事情没什么关系。很幸运,python是一个很纯净的语言——如果一段代码看起来很笨拙不实用,那么就有机会改善它的运行速度。阅读关于提高Python性能的技巧,从中获取提高你的代码速度的建议。有人说,过早的优化是万恶之源。如果它只是不够快,不要改动代码来尝试让它更快。某些东西并不一定要那样。

就这样吧。现在你知道了所有我所知道的关于的pygame的东西。现在,写你的游戏去吧

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多