转载自: http://www.pythontik.com/blog/article.asp?id=294
Pygame是SDL的一个python封装,由Pete Shinners编写。使用pygame,你可以用Python写游戏或其它的多媒体应用程序,它们将稳定地运行在SDL支持的任何平台上(Windows,Unix,Mac,beOS和其它等等)。
Pygame容易学习,但是图形编程的世界对于新来者来说,可能是相当混乱的。我写这篇文章的目的是试图提炼出我过去那些年来所获得的或使用pygame的得到的那些有用的知识。Pygame的前身是pySDL。我是按照这些问题的重要性的次序来排列的,但是这些心得对你的帮助程序取决于你自己的知识背景和你的项目的情况。
1、熟练使用python
如果你使用你不熟悉的语言进行图形编程的话,学起来会比较复杂的。用Python写一些量不大的非图形化程序——分析一些文本文件,写一些竞猜类游戏或记帐类程序或其它等等。轻松地处理字符串和列表——知道如何去分离(split),切片(slice)和合并字符串和列表。知道如何导入(import)——试着写一个程序,该程序的代码分散在几个源文件中。写你自己的函数,并练习处理数字和字符;知道如何在二者之间作转换。要记住使用列表和字典的语法——你不会想每次需要对一个列表进行切片或对一套关键字分类时再去查看相应的文档。当你遇到麻烦时,尽量不要去访问邮件列表或聊天室。相反,应该运行python的解释器并对问题进行几个小时的调试。经常翻阅相应Python版的手册。
当然这些听起来可能相当的枯燥,但是通过你对python的熟悉,当你再用它写你的游戏时,你会感到非常的有把握。并可大大提高工作效率。
2、明白自己真正需要pygame的哪些部分。
面对着pygame文档索引顶部的那些混杂的类,可能让人感到糊涂。但是你应该认识到,我们只需要这些功能的一小部分就可以完成大量的处理任务了。有许多的类,你可能几乎都不会用到。
3、知道surface是什么
pygame的最重要的部分就是surface。只需要把一个surface看作一页白纸就行了。你可以使用一个surface做很多的事情——你可以在其上绘制线条,在它上面的部分区域填充颜色,拷贝图像到它上面和拷贝它上面图像,设置或读取它上面单个像素的颜色。一个surface可以是任意大小的尺寸(合理的),并且你想要多少surface,就可以创建多少个surface(同样,要合理)。有一个surface是比较特殊的——你只能使用pygame.display.set_mode()创建仅一个。这个“display surface”代表屏幕;你对它所做的一切都将显示在用户的屏幕上。
那么如何创建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()就可以了。
如何使用convert()?只需要在使用image.load()函数创建了一个surface后调用它就可以了。使用surface = pygame.image.load('foo.png').convert()来替换surface = pygame.image.load('foo.png')。
这很容易。当你从磁盘载入一个图像时,你只需要对每个surface调用convert()一次。你会得到满意的结果;blit的速度得到大大的提高。
只有在你真正需要绝对控制一个图像的内在的格式时,你才不想使用convert()——比如说你正在写一个图像转换程序或什么,并且你需要确保所输出的文件与输入的文件有相同的像素格式。如果你是在写一个游戏,那么你需要的是速度。请使用convert()。
5、绘制rect(矩形区域)动画
在pygame程序中,产生不恰当的帧频率的原因是对pygame.display.update()函数的不理解。使用pygame,仅仅绘制一些东西到“display surface”不会导致立即显示到屏幕上——你需要调用pygame.display.update()。调用该函数有三种方法:
* pygame.display.update() ——这将更新整个窗口(对于全屏显示来说,将更新整个屏幕)。
* pygame.display.flip() ——这个做相同的事情,并且如果你在使用双缓冲硬件加速的话,它也会做正确的处理。
* pygame.display.update(矩形或矩形的列表)——这只更新你指定的屏幕区域。
多数图形编程的新手都使用第一个方案——它们对于每帧都更新整个屏幕。缓慢的速度这一问题就产生了,大多数人对于这一速度是不可接受的。在我的机器上,调用update()要花35毫秒,听起来也没花多少时间呀,除非你认识到1000/35=28,就是说最快每秒28帧。这不符合游戏的逻辑,难道就不blit,不输入,不进行音频输入?我其它什么也不做,就在哪里更新屏幕,这样才能达到最大的帧频28。啊...晕!
解决之道就是所谓的“绘制区域动画”。代替每帧更新整个屏幕,我们只对帧所导致的部分区域的改变作更新。我通过跟踪一个列表中的那些矩形来实现这个,然后在结束时调用update。对于移动子画面(sprite)的具体描述如下:
1、blit(传送)一块背景到子画面(sprite)对象的当前位置上,使用这块背景来抹去(覆盖)当前位置的子画面。
2、把该子画面的当前位置矩形添加到一个所谓“待更新矩形”的列表中。
3、移动子画面对象。
4、将子画面绘制到它的新的位置
5、将子画面的新位置添加到“待更新矩形”的列表中。
6、调用display.update(待更新矩形列表)。
这与以前在速度上的区别是令人惊讶的。
有两种情况,你不会使用这种技术。首先就是每帧都要更新整个窗口和屏幕的情况下——考虑面对像一个实时空中战略类的游戏或滚动条这类的平滑过度,你怎么做呢?简短的回答是:不用pygame写这类游戏。长点答案是我们可以一次移动几个像素的距离;用不着使过度十分地平滑。这不会影响到玩家的游戏感觉。
最后要说明的是——不是每个游戏都要求高的帧频。一个战略类的战争游戏可能通过每秒仅作少量的更新就可容易地得到——在这种情况下,局部的更新所带来的复杂性是没有必要的。
6、硬件surface的麻烦性多于它们的使用价值
如果你已经注意到了使用pygame.display.set_mode()可用到的各种标记,你可能会有这样的想法:“嘿,HWSURFACE!嗯,我喜欢——谁不喜欢硬件加速。哦...双缓冲;嗯,听起来感觉一定很快,我想我也想要!”这不是你的错误;我也曾经在多年的三维游戏制作中相信硬件加速比较好,软件表现较慢。
不幸的是,硬件表现带来了一系列的缺点:
* 它仅在某些平台上工作。Windows的机器通常可以得到硬件surfaces,如果你需要的话。其它大多数的平台则不行。例如Linux,如果X4被安装了,如果DGA2工作正常,如果可能能够提供硬件surface,如果moons正确地对齐了,那么有可能能够提供硬件surface。如果一个硬件surface是无效的,那么sdl将默默地使用一个软件surface来代替。
* 它只能工作在全屏状态下。
* 它使得每个像素的访问复杂化了。如果你有一个硬件surface,那么在其上写或读单个像素值之前,你需要锁住这个surface。如果你不这样做,将发生坏的情况。之后,在操作系统整个混乱之前,你又需要迅速地解锁。在pygame中这个过程大部是自动的,但还需要考虑剩下的部分。
* 你失去了鼠标指针。如果你批定HWSURFACE(并且实际的得到了它),你的鼠标指针通常将立即消失(或时隐时现的状态)。你将需要创建一个子画面来作为手工的鼠标指针,并且你必须考虑指针的加速和灵敏度。活受罪。
* 它可能会更慢。许多的驱动程序是没有被加速的,并且由于所有的东西必须通过视频总线传送(除非你能够将你的源surface放入视频储存器),这可能导致比软件访问更慢。
硬件表现有它的地方。在Windows下它工作的十分可靠,所以如果你对跨平台不感兴趣,那么它可能给你带来速度上的巨大的提高。然而,它也更加的令人头痛和复杂。最好是坚持使用可靠的SWSURFACE,除非你确信知道你正在做什么。
7、不要因枝节问题而分心。
有时候,新的游戏程序员在对他们的游戏的成功方面不是真正关键的问题上花费了太多的时间。想要解决这些问题的心态是可理解的,但在一个游戏的创建过程中,还为之尚早,你甚至无法知道重要问题是什么,更何谈你应该做何选择。结果可能是白费工夫。
例如,考虑如何组织你的图形文件的问题。 每个帧或子画面都应该考虑它自己的图形文件吗?或许所有的图形都应该被压缩到一个文件?大量的时间被浪费在向邮件列表询问问题,辨论答案上了。这是次要的问题,花在这些问题上的时间应该用在编写游戏代码上。
8、Rect是你的朋友
虽然Pete Shinners的包(Pygame)有酷的alpha效果和快速的blit速度,但是我必须得承认,我所喜欢的是它的Rect类(一个较低级的部分)。rect是一个简单的矩形——仅由它的左上角和它的宽高所定义。许多pygame的函数都要求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访问rect的实用的功能。这包括移动、缩小或增大rect(矩形)、发现两个rect(矩形)相交、和各种碰撞检测功能。
例如,假设我想得到所有包含点(x,y)的子画面(sprite)的一个列表——或许玩家在此点敲击了,或许该点是一个子弹的当前位置。如果每个子画面(sprite)都有一个.rect成员的话,就简单了,我可以就这么实现:
sprites_clicked = [sprite for sprite in all_my_sprites_list if sprite.rect.collidepoint(x, y)]
实际上除了你可以使用rect作为surface或图形函数的参数以外,rect与surface或图形函数没有其它的关系。在某些与图形无关的地方,你也可以使用rect,但是它们仍然需要被定义为矩形。对于rect用处很少的项目,我不会考虑使用它们。
9、不要考虑对全部的像素做碰撞检测。
如果你已经实现了子画面的移动,你需要知道它们是否碰撞到了一起。那么你很有可能像下面这样做:
1、检查rect是否相撞。如果没有就忽略。
2、对于在重叠区域中的每个像素,检查两个子画面中的对应像素是不是不透明的。如果是,则产生了碰撞。
也有其它的检测方法,可以对子画面的遮罩作与运算等等,但是在pygame中你所使用的这些方法,处理起来可能太慢。对于大多数游戏,或许只作一个子区域碰撞会更好——为每个子画面(sprite)创建一个比实际图像更小的rect(矩形),以用来作碰撞。这样检测会更快,虽然不太精确。
10、管理事件子系统
Pygame的事件系统是机智的。有两种不同的方法用来发现哪个输入设备(键盘、鼠标或操纵杆)正在动作。第一种是通过直接检查设备的状态。你可以通过调用pygame.mouse.get_pos()或pygame.key.get_pressed()来实现。这将告诉你相关设备在函数(如pygame.mouse.get_pos()或pygame.key.get_pressed())调用时的状态。
第二种方法是使用SDL事件队列。该队列是事件的一个列表——当事件被检测到时事件被添加到该列表,当事件被从队列中读取后,该事件将被删除。
每个系统都有优点和缺点。状态检查(system1)让你可以准确地知道给定输入设备的状态——如果mouse.get_pressed([0])是1,意思就是此刻鼠标左按键被按下了。事件队列只报告鼠标在过去某时被按下;如果你经常均等机会地检查事件队列,那么还可以,但是使用额外的代码检查会有延迟。状态检查系统的另一个好处是容易检测“chording”,也就是同时产生的几个状态。如果你想知道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() 与event. wait()的比较——poll()可能更好一些,因为它在等待输入时不会阻塞你的程序去做另外的事情——而wait()会将程序挂起直到一个事件被接受。然而poll()在运行时将用掉全部可用的cpu时间,并且它将使用NOEVENTS来填充事件队列。使用set_blocked()来只选择你感兴趣的事件类型——你的队列将容易维护的多。
11、Colorkey与Alpha的比较
围绕着这两种技术,有很多不太清楚的地方,大多是因为术语的关系。
'Colorkey blitting'意味着告诉pygame,在某个图像中有着某种颜色的所有像素都是透明,取代了它们将是什么颜色。当图像的其余部分被blit时,这些透明的像素不被blit,并且因些不掩盖背景。这就是我们如何使子画面(sprite)不是矩形的方法。简单地调用surface.set_colorkey(color),这里的color是一个rgb元组——比如(0,0,0)。这将使用源图像的黑色的像素变成透明的。
'Alpha'不同,它有两种。'Image alpha'适用于整个图像,并且大概也是你想要的。它用于设置不透明度。如果你设置一个surface的alpha为192,然后把它blit到一个背景上,那么每个像素颜色的3/4来自于源图像,1/4取自背景。Alpha的值的范围是255~0,0是完全透明,255是完全不透明。注意,colorkey和alpha在blit时可以被合并——这导致一个图像在一些点是完全透明的,而别处是半透明的。
'Per-pixel alpha'是alpha的另一种,它更复杂。源图像中的每个像素都有它自己的alpha值,从0到255。因此,当每个像素被blit到一个背景上时可以有不同的不透明度。这种alpha不能与colorkey合并blit,并且它覆盖单位图像的alpha。'Per-pixel alpha'很少用在游戏中,要使用它,你必须在你的一个图形编辑器中使用一个特殊的alpha channel来保存你的源图像。它比较复杂——根本不要使用它。
12、工作的方式。
Pygame是一个优秀的轻量级的SDL包。如果你已经按我上面提及的方法做了,但是你的代码的速度仍然慢,那么问题就在于你用python处理数据的方法。某些惯用的作法只会降低你的速度,不管你做什么。所以我们也可以作一些改变,虽然代码可能看上去很笨拙,但有可能会提高速度。至于如何提高代码的速度,你可以看一下相关的主题。过早的优化是所有不好的结果的根源;如果代码只是不足够快,那么就不要再费力地想让它更快了。
现在,我对pygame所知道的东西,你都知道了。那么现在开始写游戏吧!