【www.shanpow.com--简历下载】
一:[Pygame]pygame菜鸟入门指南
当前位置: 火魔网 ? 程序开发 ? Python
pygame菜鸟入门指南
更新: 2010-12-28字体: 【大 中 小】点击: 110
-
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的东西。现在,写你的游戏去吧
二:[Pygame]在pygame中显示中文
在pygame中显示中文
更新: 2011-06-10字体: 【大 中 小】点击: 33
-
在pygame中显示中文最近对python产生了兴趣,进而又对pygame产生了兴趣。我本身对游戏编程就很着迷,而pygame的简单又让我的着迷程度更进了一步。 做一个堂堂的中国人,自然希望在游戏中的文字是中文了,但是在游戏中显示中文似乎没有那么简单。经过一番研究,终于搞清楚怎样在pygame中显示中文了。 下面就以pygame文档中的一段代码为例,介绍如何显示中文。 # -*- coding: gbk -*-
import pygame
from pygame.locals import *
import local2utf8
def main():
# Initialise screen
pygame.init()
screen = pygame.display.set_mode((650, 150))
pygame.display.set_caption((u"基础 pygame 程序").encode("utf-8"))
# Fill background
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((250, 250, 250))
# Display some text
#font = pygame.font.Font(None, 60)#原始代码,使用默认字体,不能显示中文
#font = pygame.font.Font("../FredGuo/simkai.ttf", 60)#正确,文件名可以包含路径
#font = pygame.font.Font("楷体", 60)#错误,name参数应该是字体的文件名
font = pygame.font.SysFont("楷体", 60)#正确,name参数应该是字体名,并且字符集要与系统的相同
text = font.render(u"Hello 我爱你", 1, (10, 10, 10))#显示内容必须转换成Unicode,否则中文不能正常显示
textpos = text.get_rect()
textpos.center = background.get_rect().center
background.blit(text, textpos)
# Blit everything to the screen
screen.blit(background, (0, 0))
pygame.display.flip()
# Event loop
while 1:
for event in pygame.event.get():
if event.type == QUIT:
return
screen.blit(background, (0, 0))
pygame.display.flip()
if __name__ == "__main__": main()代码中的注释说的已经很清楚了,在这里做一下简单的总结:首先,使窗口的标题显示中文是很简单的,只要将字符集转换成utf-8就可以;然后,我们说说稍微麻烦些的内容。在pygame中显示文字主要是用Font类,在创建类的实例时就需要指定字体。如果想使用pygame的默认字段只需将None传递Font就可以,但是这样不可以显示中文。要使用Font显示中文主要有两种方法:一个是使用pygame.font.Font(name, size)来创建新的Font实例,其中name是字体的文件名(.ttf或.ttc文件),可能包含路径,这需要你知道字体文件的存放位置,方便起见,可以将文件copy到程序目录下。另一个方法是使用pygame.font.SysFont(name, size)方法创建Font的实例,它利用系统的可用字体,如果失败会默默地使用pygame的默认字体,其中name是字体的名称(楷体、宋体等);需要注意的是name的字符集要与系统的使用的字符集尽量一致,否则可能出现问题。在使用Font的render()方法绘制中文时需要注意将字符集转换成Unicode,否则无法正常显示中文。 OK,That’s it。最后需要提醒一下,一定要注意自己文件使用的字符集,因为并不是在文件头指定什么字符集就会自动地用什么字符集保存文件。
三:[Pygame]Python写游戏: PyGame入门
Python部落(www.freelycode.com)组织翻译, 禁止转载
PyGame是一个用Python写的SDL库。SDL是一个能访问计算机多媒体硬件组件(包括声卡,视频卡,输入组件等)的跨平台库。SDL是一个非常强大的工具,擅长创建基于多媒体硬件的程序,但它是用C语言写的,可C语言很难,所以我们选择PyGame。在这篇教程中,我们会涉及到PyGame遊戲逻辑,碰撞检测,构建游戏界面和调用第三方库文件到游戏中来。注意: 本教程已认为读者对Python语法,文件结构和面向对象的有已经基本理解。安装先前往PyGame下载页面,根据你系统版本和Python版本找到适合的二进制包。如果你使用Python 3,必须确保你下载的PyGame版本是1.9.2的。新建一个工程,然后在工程项目中,新建一个Python文件,复制以下代码到新建的Python文件中:
import pygamefrom pygame.locals import *pygame.init()
正如所有Python程序一样,我们必须先导入模块,然后我们才能使用这些模块。正因为如此,在文件开头,我们先导入了pygame模块和pygame.locals模块,然后我们可以从模块中使用已编写好的常量。最后一行是初始化代码,它会初始化所有PyGame模块,你必须先使用这个方法,否则你不能用PyGame做任何事情。创建游戏组件Screen对象首先,我们必须画一些东西,所以我们将创建一个“屏幕”,这个屏幕就是我们的画布。为了能创建一个显示屏幕,我们必须使用pygame.display模块中的set_mode()方法,然后传入一个由窗体高度和窗体宽度组成的元组。(我们设置窗体尺寸为800x600)。
import pygamefrom pygame.locals import *pygame.init()screen = pygame.display.set_mode((800, 600))
如果你现在运行代码,会看见一个窗体而且它会突然消失,就像退出程序了一样。不是很爽,是吗?接下来,我们将解释我们的游戏主循环,它会确保我们只有输入正确的退出指令后,程序才会停止运行并退出。游戏主循环游戏主循环(事件)是一直在运行着的。无论是在玩游戏,更新游戏状态,渲染画面或者是采集输入数据,它是一直在不间断运行着的。当我们创建一个循环时,必须能保证,我们有方法可以跳出循环,结束程序运行。最后,我们将介绍一些在玩游戏时的基本操作。所有的操作(我们稍后会讲解一些事件内容)都会进过PyGame的事件队列中,你可以用pygame.event.get()方法来操作它。这个方法会返回一个列表,该列表包含队列中所有事件的列表。我们可以对它做个循环遍历,根据事件类型,做出相应的操作。现在我们只关心KEYDOWN(按键)和QUIT(退出)事件。
# Variable to keep our main loop runningrunning = True# Our main loop!while running: # for loop through the event queue for event in pygame.event.get(): # Check for KEYDOWN event; KEYDOWN is a constant defined in pygame.locals, which we imported earlier if event.type == KEYDOWN: # If the Esc key has been pressed set running to false to exit the main loop if event.key == K_ESCAPE: running = False # Check for QUIT event; if QUIT, set running to false elif event.type == QUIT: running = False
把以上代码加入之前写的代码中,然后运行它。你应该会看见一个空窗体。除非你看ESC键或者你触发了QUIT事件,否则它是不会关闭窗体并且停止运行的。面和方块在PyGame中,面和方块是游戏基础的组件。你可以认为面是一张白纸,你可以在上面想怎么画就怎么画。我们的screen对象其实也是一个面组件。它们可以对图片进行操作。方块代表了一个被面组件包围的矩形面积。现在开始建一个50像素*50像素的平面,然后填入一个颜色. 我们选择用白色填入平面,因为默认窗体是黑色的并且我们想让窗体明亮可见。我们会在平面上使用get_rect()方法来获得面面积和X,Y坐标。
# Create the surface and pass in a tuple with its length and widthsurf = pygame.Surface((50, 50))# Give the surface a color to differentiate it from the backgroundsurf.fill((255, 255, 255))rect = surf.get_rect()
位移和翻转
如果只在窗体中建个面组件是远远不够的,所以我们接下来要做的是,能让面组件动起来。从专业上来说,位移其实就是绘图,就是两个面之间进行位移。请不要忘记哦,我们的屏幕就是另一个面对象。下面代码将演示如何绘制屏幕。
# This line says "Draw surf onto screen at coordinates x:400, y:300"screen.blit(surf, (400, 300))pygame.display.flip()
blit()方法带有两个参数:面对象和坐标。现在我们想绘制在屏幕中央,可当你运行代码的时候,你会发现面并不是显示在屏幕中央。那是因为blit()方法是以窗口左上角为原点坐标。注意:如果小方块进行了位移,那么请记得使用pygame.display.filp()方法。该方法会更新整个屏幕,绘制出新小方块。如果你不使用该方法,你将看不到任何图像更新。精灵什么是精灵?在编程定义中,精灵表示屏幕中的2D元件。从本质上来说,精灵就是一个图像对象。PyGame提供了一个精灵类,它能扩展一个对象来表示一个或多个图形。我们会继承这个类,这样我们可以用它自带的很多方法了。我们会新建一个新对象Player。Player对象会继承精灵类,并且现在它仅有两个属性:面和小方块。我们还会给面属性一个颜色(白色)。现在Player也拥有了一个面。译者注:Player对象代表了玩家控制的小方块。
class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).__init__() self.surf = pygame.Surface((75, 25)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect()
现在把代码放在一起:
# import the pygame moduleimport pygame# import pygame.locals for easier access to key coordinatesfrom pygame.locals import *# Define our player object and call super to give it all the properties and methods of pygame.sprite.Sprite# The surface we draw on the screen is now a property of ‘player’class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).___init__() self.surf = pygame.Surface((75, 25)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect()# initialize pygamepygame.init()# create the screen object# here we pass it a size of 800x600screen = pygame.display.set_mode((800, 600))# instantiate our player; right now he’s just a rectangleplayer = Player()# Variable to keep our main loop runningrunning = True# Our main loop!while running: # for loop through the event queue for event in pygame.event.get(): # Check for KEYDOWN event; KEYDOWN is a constant defined in pygame.locals, which we imported earlier if event.type == KEYDOWN: # If the Esc key has been pressed set running to false to exit the main loop if event.key == K_ESCAPE: running = False # Check for QUIT event; if QUIT, set running to false elif event.type == QUIT: running = False # Draw the player to the screen screen.blit(player.surf, (400, 300)) # Update the display pygame.display.flip()
运行以上代码,你应该会在屏幕中央看见一个白色小方块。如果把screen.blit(player.surf,(400,300))改成screen.blit(player.surf,player.rect),你认为会发生什么事情呢?如果真想改成这样,那试着输出player.rect的值到控制台上。rect()的前两个属性分别是rect()左上角的x,y坐标值。当你移动一个小方块时,PyGame会用这些坐标值去绘制一个面。这就是我们接下来让player可以动起来的原因。用户输入现在才开始有趣起来!现在,让我们可以控制player对象。正如我们前面所讨论的一样,keydown(按键)事件pygame.event.get()会提取事件栈中最新的事件。PyGame还有另一个方法叫做pygame.event.get_pressed()。 这个get_pressed()方法会返回一个字典,该字典包含队列中所有keydown事件。我们将它放在游戏主循环中,这样可以获得我们一切想要的。
pressed_keys = pygame.event.get_pressed()
我们现在要写一个方法,该方法会从按键事件字典中提取按键事件,并且同时会判断精灵行为,然后根据按键的键值,进行相关的操作。
def update(self, pressed_keys): if pressed_keys[K_UP]: self.rect.move_ip(0, -5) if pressed_keys[K_DOWN]: self.rect.move_ip(0, 5) if pressed_keys[K_LEFT]: self.rect.move_ip(-5, 0) if pressed_keys[K_RIGHT]: self.rect.move_ip(5, 0)
K_UP, K_DOWN, K_LEFT和K_RIGHT分别代表了键盘上的方向键。如果有方向键按下被检测到,那么就会让rect()进行相关的移动。矩形自带来两个移动方法。在这里,我们使用“移动到位”方法-move_ip()。因为我们不想复制一个新的矩形对象,只是想移动现存的矩形对象。请添加以下代码到Player类中,并且把get_pressed()方法放进游戏主循环中。所以我们现在的代码应该看上去像这样子:
import pygamefrom pygame.locals.import *class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).___init__() self.surf = pygame.Surface((75, 25)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect() def update(self, pressed_keys): if pressed_keys[K_UP]: self.rect.move_ip(0, -5) if pressed_keys[K_DOWN]: self.rect.move_ip(0, 5) if pressed_keys[K_LEFT]: self.rect.move_ip(-5, 0) if pressed_keys[K_RIGHT]: self.rect.move_ip(5, 0) pygame.init() screen = pygame.display.set_mode((800, 600)) play = Player() running = True while running: for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_ESCAPE: running = False elif event.type = QUIT: running = False pressed_keys = pygame.key.get_pressed() player.update(pressed_keys) screen.blit(player.surf, (400, 300)) pygame.display.flip()
现在,你应该能用方向键来控制屏幕上小方块移动了。你可能会发现,小方块可以移出屏幕,但这不是我们想看到的。现在需要修改update()方法,让它更有逻辑性。如果小方块移出800*600窗体边界的话,它只能紧贴着边界。
def update(self, pressed_keys): if pressed_keys[K_UP]: self.rect.move_ip(0, -5) if pressed_keys[K_DOWN]: self.rect.move_ip(0, 5) if pressed_keys[K_LEFT]: self.rect.move_ip(-5, 0) if pressed_keys[K_RIGHT]: self.rect.move_ip(5, 0) # Keep player on the screen if self.rect.leftself.rect.left = 0 elif self.rect.right > 800: self.rect.right = 800 if self.rect.top => self.rect.top = 0 elif self.rect.bottom >= 600: self.rect.top = 600
如果超过了边界,我们就修改顶部、底部、左边和右边的坐标值。现在让我们添加一些障碍物。首先,让我们新建一个“Enemy”类,就如我们创建“Player”类一样,它也是继承自精灵类:
class Enemy(pygame.sprite.Sprite): def __init__(self): super(Enemy, self).__init__() self.surf = pygame.Surface((20, 10)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect(center=(820, random.randint(0, 600))) self.speed = random.randint(5, 20) def update(self): self.rect.move_ip(-self.speed, 0) if self.rect.rightself.kill()
这里有几个不同地方,我们需要说明下。首先,当我们在面上使用get_rect()方法时,我们设置了它x坐标为820,而y坐标则是用random.randint()方法生成一个随机整数。Random是一个python库,我们会在文件开头就导入random库。为什么要随机生成整数?这是非常简单的道理:我们想让这些障碍物从屏幕右边(820),但以不同的高度(0-600)飞入。我们也会用random函数来设置这些障碍物的飞行速度。这样可以造成有些障碍物飞得快有些障碍物飞得慢的效果。在enemy类中的update()方法,不带有任何参数(我们不考虑用户输入),只是简单恒速从屏幕右边飞到左边。在update()方法中,if条件会检测障碍物的左边界是否越过了屏幕的左边界或者小方块的右边界(它们不是一接触到就立马消失不见)。当它们越过以上两个边界时,我们会使用自带方法kill(),把它们从精灵组中删除掉,以防止它们继续在屏幕上显示。用这个方法并不会释放被占用或者没被引用的内存空间,但Python垃圾回收机制会帮你搞定它们。群PyGame提供了另一个非常有用的对象,它就是精灵群。它能做到犹如它名字一样 - 精灵的群。为什么我们不使用一个列表,而是使用精灵群呢?因为精灵群已经提供了很多内置方法,这些方法能帮助我们进行的冲撞和更新。让我们现在新建一个群类,把精灵类都放进一个群类中。创建之后,我们会把Player类和Enemy类分别加进一个群类中。当使用精灵类中kill()方法时,精灵类会被从群类中移除。下面是一小部分代码:
enemies = pygame.sprite.Group()all_sprites = pygame.sprite.Group()all_sprites.add(player)
现在,我们已经有了all_sprites的群对象,现在我们会改动一点代码,能促使它渲染出该群里面所有的对象。
for entity in all_sprites: screen.blit(entity.surf, entity.rect)
现在,任何放进all_sprites群里的对象,都被会渲染出来。自定义事件虽然障碍物有自己的精灵群,但事实上,还没有任何障碍物存在。问题来了,如何能使我们在屏幕上显示障碍物?。刚开始游戏时,我们可以在屏幕上新建一些障碍物,但过一会后,我们游戏就没有障碍物了。因此,我们要新建一个自定义事件,它会每几秒新建一些障碍物出来。PyGame会监听这个事件,就像它监听按键和退出事件一样。创建一个自定义事件非常容易,就像它名字一样:
ADDENEMY = paygame.USEREVENT + 1
就像这样!现在我们有了一个新事件,叫做ADDENEMY。我们在游戏主循环中监听它。需要注意的是必须使自定义事件有一个独一无二的值,并且这个值还要大于USEREVENT的值。这就是为什么我们设事件值等于USEREVENT + 1。 友情小提示:这些值都是整型常数。USEREVENT有一个数字型值,并且任何自定义事件必须是大于USEREVENT值的整数型值(因为所有小于USEREVENT的值都已经被自定义方法分配完了)。我们有了一个自定义方法。我们必须把它插入事件队列中。为了保证在整个游戏进行时,这个事件一直是在运行的,所以须要设置一个定时器。我们可以用PyGame提供的time()对象做到这点。
pygame.time.set_timer(ADDENEMY,250)
这行代码会告诉PyGame:每250毫秒触发一次ADDENEMY事件。虽然这个代码不在我们的游戏主循环中,但它仍然可以通过游戏运行。现在增加一些监听代码到我们事件中:
while running: for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_ESCAPE: running = False elif event.type == QUIT: running = False elif (event.type == ADDENEMY): new_enemy = Enemy() enemies.add(new_enemy) all_sprites.add(new_enemy)
需要注意的是set_time()方法只是负责把事件传入PyGame的事件队列中。现在,我们可以监听我们的ADDENEMY事件了,当它被触发运行时候,我们会创建一个新障碍物。新建之后,我们会把障碍物添加到精灵群中(稍后我们将用它进行碰撞环节)和all_sprites群中(这样它就可以在屏幕上显示出来).碰撞碰撞正是PyGame的魅力所在。虽然写碰撞代码非常困难,但PyGame有很多检测碰撞方法。本教程中,我们将使用spritecollideany()方法。在精灵群中,如果一个精灵对象和另一个精灵对象有碰撞,那么spritecollideany()方法会用一个精灵对象和精灵群对象进行操作。如果我们Player被一个障碍物撞到,那么我们就用Player精灵和障碍物精灵群进行操作。代码如下所示:
if pygame.sprite.spritecollideany(player, enemies): player.kill()
我们会测试在障碍物精灵群中,一个Player精灵对象被任何一个障碍物精灵组有接触。如果有,因为我们只渲染all_sprites组中的精灵,所以我们就使用Player精灵中的kill()方法。与此同时,kill()方法会从这个组中移出一个Player精灵对象。这样我们Player精灵就不会再被渲染了,这就意味着“被移出”了。现在我们把所有代码放在一起把!
# import the pygame moduleimport pygame# import random for random numbers!import random# import pygame.locals for easier access to key coordinatesfrom pygame.locals import *class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).__init__() self.surf = pygame.Surface((75, 25)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect() def update(self, pressed_keys): if pressed_keys[K_UP]: self.rect.move_ip(0, -5) if pressed_keys[K_DOWN]: self.rect.move_ip(0, 5) if pressed_keys[K_LEFT]: self.rect.move_ip(-5, 0) if pressed_keys[K_RIGHT]: self.rect.move_ip(5, 0) # Keep player on the screen if self.rect.leftself.rect.left = 0 elif self.rect.right > 800: self.rect.right = 800 if self.rect.topself.rect.top = 0 elif self.rect.bottom >= 600: self.rect.bottom = 600
见证奇迹时刻到了!图片现在我们已经做好了一个游戏,虽然是个非常难看的游戏。接下来,我们要用一些漂亮的图片来替换那些无聊的小方块。这样,这个游戏看上去就像一个真正的游戏了。在之前代码中,我们使用白色的面对象。虽然白色能让我们了解面和及其工作原理,但它使游戏变得非常丑陋。我们将为障碍物和小方块添加几张图片。我喜欢画自己的图片,所以我画了一个小飞机来表示小方块和画了一些导弹来表示障碍物。你可以下载从repo中下载到这些代码。你可以随意用我的图片,或者用你自己画的图片,又或者你可以去下载一些免费的图片。修改对象构造器我们现在Player构造器看上去是这样子的:
class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).__init__() self.surf = pygame.Surface((75, 25)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect()
我们新Player构造器看上去是这样的:
class Player(pygame.sprite.Sprite): def __init__(self): super(Player, self).__init__() self.image = pygame.image.load("jet.png").convert() self.image.set_colorkey((255, 255, 255), RLEACCEL) self.rect = self.image.get_rect()
我们想用图片来替换我们的Surface对象。我们可以通过传入图片地址到pygame.image.load()方法中。load()方法会返回一个Surface对象。当我们在Surface对象中使用convert()方法,该方法会复制一个Surface对象,这样子在屏幕上会显示流畅一点。接下来,我们会对图片使用set_colorkey()方法。set_colorkey方法会让设置的图片颜色变得更透明一些。在本教程中,我选择白色,因为它是飞机图片的背景色。RLEACCEL是个可选参数,它能帮助PyGame在非加速显示器中渲染的更快。最后,在我们图片中使用get_rect()方法,获得rect对象。请不要忘记,图片仍旧是一个surface对象;它只是现在用图片来表示。我们如法炮制对enemy构造器,进行修改:
class Enemy(pygame.sprite.Sprite): def __init__(self): super(Enemy, self).__init__() self.image = pygame.image.load("missile.png").convert() self.image.set_colorkey((255, 255, 255), RLEACCEL) self.rect = self.image.get_rect( center=(random.randint(820, 900), random.randint(0, 600)) ) self.speed = random.randint(5,20)
我们现在有了一个比以前更好看的游戏了,尽管我觉得还是缺少了点什么。现在在天空中添加一些云,我们会用相同的方法来做到让飞机表现出穿过云的效果一样。首先,我们新建一个Cloud对象,它带有一张云图片和一个能表现出云从左边移动右边的update方法。然后我们创建一个自定义事件,该事件会一定间隔内创建一个Cloud对象,并添加至all_sprites群中。下面就是我们Cloud对象代码:
class Cloud(pygame.sprite.Sprite): def __init__(self): super(Cloud, self).__init__() self.image = pygame.image.load("cloud.png").convert() self.image.set_colorkey((0, 0, 0), RLEACCEL) self.rect = self.image.get_rect( center=(random.randint(820, 900), random.randint(0, 600)) ) def update(self): self.rect.move_ip(-5, 0) if self.rect.rightself.kill()
新增代码看上去都差不多,现在我们把新增代码放在ADDENEMY自定义事件下面。
ADDCLOUD = pygame.USEREVENT + 2pygame.time.set_timer(ADDCLOUD, 1000)
现在新建一个云精灵群:
clouds = pygame.sprite.Group()
在我们游戏主循环中,我们应该把ADDCLOUD事件加进去。加入之前:
for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_ESCAPE: running = False elif event.type == QUIT: running = False elif event.type == ADDENEMY: new_enemy = Enemy() enemies.add(new_enemy) all_sprites.add(new_enemy)
加入之后:
for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_ESCAPE: running = False elif event.type == QUIT: running = False elif event.type == ADDENEMY: new_enemy = Enemy() enemies.add(new_enemy) all_sprites.add(new_enemy) elif event.type == ADDCLOUD: new_cloud = Cloud() all_sprites.add(new_cloud) clouds.add(new_cloud)
我们会同时添加Cloud精灵到all_sprites群和Cloud群中。因为我们用all_sprites方法渲染出云,然后再用clouds的update方法。你可能会问为什么我们把它们添加到障碍物群中?毕竟,它们几乎有相同的update方法。原因在于飞机只是穿过云而已,我们不想让飞机与云进行接触,剩下的就是用Cloud Group的update方法了。结论就这些!如果你测试成功的话,你应该看到以下这样子:你可以从Github上获取完整代码。我希望你喜欢这篇教程,并从中学到点东西.
英文原文: https://realpython.com/blog/python/pygame-a-primer/#.Vo0sHqOmRRI.reddit
译者: AqusJC

