Python和Pygame游戏开发指南

978-7-115-40735-1
作者: 【美】Al Sweigart(斯维加特)
译者: 李强
编辑: 陈冀康
分类: Python

图书目录:

详情

本书不仅介绍了如何运用Python中的Pygame进行编程,也详细介绍了如何运用Python语言开发游戏。通过示例展示,读者将能够清晰明了地看到一个游戏的开发和实现的全过程,并能够对Python语言编程有全面的认识,游戏开发也将不再神秘。同时,读者还能够从本书的附带资源中获得一系列源码,以帮助读者更好地掌握编程语言和游戏开发的全过程。

图书摘要

版权信息

书名:Python和Pygame游戏开发指南

ISBN:978-7-115-40735-1

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。



Al Sweigart(也可以称呼他Al)是加利福尼亚州旧金山的一名软件开发者。他很喜欢骑自行车、当志愿者、泡咖啡吧以及制作有用的软件。《Making Games with Python & Pygame》是他编写的第2本书。他的第1本书是《Invent Your Own Computer Games with Python》。

他生于德克萨斯的休斯顿。他在德克萨斯大学Austin分校读完了计算机科学学位。当看到公园里的松鼠的时候,他会大笑,这使得人们认为他是傻瓜。

● Email: al@inventwithpython.com

● Blog: http://coffeeghost.net

● Twitter: @AlSweigart

Al Sweigart是加利福尼亚州旧金山的一名软件开发者。他很喜欢骑自行车、当志愿者、泡咖啡吧以及制作有用的软件。《Making Games with Python & Pygame》是他编写的第二本书。他的第一本书是《Invent Your Own Computer Games with Python》。他生于德克萨斯的休斯顿。他在德克萨斯大学Austin分校读完了计算机科学学位。

本书特色

本书作者在国外发布英文版后,受到普遍欢迎和很好的评价。

本书通过详细分析11款示例游戏的源代码,介绍Pygame库的用法。寓教于乐,轻松有趣。

本书所涉及的11款示例游戏,是诸如Nibbles、Tetris、Simon、Bejeweled、Othello、Connect Four、Flood It等经典游戏的翻版。

本书的Web站点http://inventwithpython.com/pygame,提供源代码下载等更多资源。

本书评论

Python语言和Pygame都是开发图形化的计算机游戏的得力工具。Pygame使得开发2D图形程序变得很容易,而且它可以免费下载和安装使用。

本书教你如何用Python语言和Pygame库,来编写图形化的计算机游戏。本书通过详细分析11款示例游戏的源代码,介绍Pygame库的用法。一旦理解了Python编程基础,就可以使用Pygame库来增强自己的开发能力,制作带有图形、动画和声音的游戏。

本书所涉及的11款示例游戏,是诸如Nibbles、Tetris、Simon、Bejeweled、Othello、Connect Four、Flood It等经典游戏的翻版。

本书的Web站点http://inventwithpython.com/pygame,提供源代码下载等更多资源。


Simplified Chinese translation copyright ©2015 by Posts and Telecommunications Press

ALL RIGHTS RESERVED

Making Games with Python and Pygame ISBN-13:978-1469901732

By Al Sweigart

Copyright ©2012 by Al Sweigart

本书中文简体版由作者Al Sweigart授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。

版权所有,侵权必究。


Python语言和Pygame都是开发图形化的计算机游戏的得力工具。Pygame使得开发2D图形程序变得很容易,而且它可以免费下载和安装使用。

本书是一本中级编程图书,教读者如何用Python语言和Pygame库,来编写图形化的计算机游戏。本书共包括10章。本书首先从Python和Pygame基础知识开始,简要地介绍了Pygame库是如何工作的,以及它提供了哪些功能。然后,结合7款不同的游戏实例的开发过程,详细介绍了应用的技能和技巧。本书针对一些真实的游戏给出了完整的源代码,并且详细说明了这些代码如何工作,以便读者能够理解真正的游戏是如何使用Pygame的。

本书适合有一定Python基础知识的读者阅读和学习,进而掌握基本的游戏开发知识和技能,对于Python初学者来说,本书也可以作为通过游戏学习Python开发的实践教程。


你好!本书将教你如何使用Python语言和Pygame框架(也叫作Pygame库)来开发图形化的计算机游戏。Pygame使得开发2D图形程序变得很容易。Python和Pygame框架都可以从http://python.orghttp://pygame.org免费下载。你只需要有计算机和这本书,就可以开始开发自己的游戏了。

本书是一本中级编程图书。如果你完全是初次接触编程,你可能需要努力阅读源代码示例并搞清楚程序如何工作。然而,如果你此前学习过如何使用Python编程,这将会容易一些。《Invent Your Own Computer Games with Python》[1]这本书,将会教初学者如何编写非图形化的、基于文本的游戏,并且还有一些章节介绍如何使用Pygame库。

然而,如果你已经知道了如何用Python编程(或者甚至了解其他语言,因为Python是很容易掌握的语言)并且想要开始编写超越文本的游戏,那么,本书很适合你阅读。本书首先简短地介绍了Pygame库是如何工作的,以及它提供了哪些功能。然后,本书针对一些真实的游戏给出了完整的源代码,并且详细说明了这些代码如何工作,以便你能够理解真正的游戏是如何使用Pygame的。

当你搞明白之后,编写视频游戏程序,只不过是点亮像素,让漂亮的图片出现在屏幕上以响应键盘和鼠标输入而已。

但很少有什么事情能像编写游戏程序这么有趣。

本书教你如何用Python语言和Pygame库来编写图形化的计算机游戏。本书假设你了解一些Python知识或一般的编程知识。如果你不知道如何编程,可以通过阅读《Invent Your Own Computer Games with Python》一书来学习;或者,你也可以直接开始坚持捧读本书。

本书包含了7款不同的游戏,它们都是你曾经玩过的流行游戏的翻版。和《Invent Your Own Computer Games with Python》一书中基于文本的游戏相比,这些游戏充满了乐趣和交互性,但仍然相当短小。所有的程序都在600行以内。当你考虑一下你所下载或者从商店购买的专业游戏往往有成百上千行的代码,这些游戏真的是很小了。而那些商业化的游戏,需要程序员和美工组成的整个团队一起工作数月,甚至是数年才能开发出来。

本书共包括10章内容和一个术语表。各章内容说明如下。

第1章介绍了Python和Pygame的基础知识,主要是在不同的平台下如何正确地安装和配置Python和Pygame。这是你使用Python和Pygame开发游戏的准备工作。这一章还介绍了如何使用本书中的代码以及本书的配套站点所提供的资料。

第2章介绍了Pygame的基础知识,包括Pygame是什么以及它能够提供哪些功能。本章还介绍了像素坐标、Surface对象、颜色、动画、声音等和游戏编程相关的基础知识。

第3章完整地讲解了Memory Puzzle程序是如何工作的,并且通过介绍游戏程序,初次说明了嵌套的for循环、语法糖以及在同一程序中使用不同的坐标系统等概念。后续其他的游戏中,也会用到这些概念。

第4章详细说明了Slide Puzzle游戏的程序是如何工作的。这个游戏中所使用的众多方法和概念,在第3章中都介绍过。

第5章详细讲解了Simulate游戏的程序是如何工作的。

第6章详细介绍了Wormy游戏程序的实现和工作原理。本章初次介绍了碰撞检测的方法,这在后面的游戏程序中还会用到。

第7章讲解了Tetromino游戏的实现和工作原理。本章的游戏第一次引入了关卡的概念。它通过示例深入地揭示了如何把游戏思想变成流行的、真正的程序。

第8章介绍了Squirrel Eat Squirrel游戏程序的原理和实现。这是第一款一次拥有多个敌人在游戏板上移动的游戏。本章引入了相机的概念,介绍了如何通过相机视图将游戏世界的一部分显示在屏幕上。此外,本章引入了数学正弦函数来处理真实的松鼠跳动。你会发现,利用一些数学知识进行游戏编程,可以使得游戏效果更为生动逼真。

第9章介绍了一个更为复杂的Star Pusher游戏的原理和实现。该游戏拥有使用贴片图形独特设计的关卡。我们学习了如何修改关卡文件,从而改变众多游戏对象在游戏世界中的位置。

第10章给出了4款其他的游戏的源代码。这一章并没有详细介绍4款游戏的工作原理。但是,这些不同类型的游戏程序,能够帮助读者思考自己想要制作什么样的游戏以及如何编写其代码。本章最后还列出了进一步学习Python和Pygame编程的更多资源。

术语表列出了本书中介绍的一些术语及其概念说明,以方便读者在阅读过程中查阅参考。

这本书针对中级程序员,他们已经学习过什么是变量和循环,但是现在想要知道真正的游戏程序看起来是什么样子。在我初次学习编程之后,还并不是真正地知道如何使用这些技能来开发一些很酷的东西,这之间还有很长的距离。我希望本书中的游戏能够使你对于程序如何工作有足够的了解,从而为你实现自己的游戏打下坚实的基础。

配套Web站点

本书的Web站点是http://inventwithpython.com/pygame。本书所提及的所有程序和文件,都可以从这个Web下载。如果你对于本书以及其中的程序有任何的问题,请通过al@inventwithpython.com给我写邮件。

[1]该书中文版将由人民邮电出版社出版。


如果在阅读本书之前,你了解一点Python编程的话(或者知道如何使用Python以外的另一种语言编程),可能会有所帮助。然而,即便你还不了解,仍然可以阅读本书。编程并不像人们所想象得那么难。如果你遇到一些困难,可以读我的另一本书《Invent Your Own Computer Games with Python》,查找令你感到混淆的某一个主题。

在阅读本书之前,你不需要知道如何使用Pygame库。本书第2章是介绍Pygame的主要功能和函数的一个简短教程。考虑到你有可能没有读过《Invent Your Own Computer Games with Python》这本书,并且可能还没有在自己的计算机上安装Python和Pygame,本章将介绍安装过程。如果你自己已经安装了Python和Pygame,那么,你可以直接跳过本章。

在开始编写程序之前,需要先在计算机上安装叫作Python解释器的软件(你可能需要请别人帮忙)。解释器(interpreter)是能够理解你用Python编写(或者说录入)的指令的一个程序。没有解释器,计算机就不能够运行Python程序。从现在开始,我们将“Python解释器”称为“Python”。

可以从Python编程语言的官方站点http://www.python.org下载Python解释器软件。你可能需要某些人的帮助才能够下载和安装Python软件。根据你的计算机使用的是Windows、Mac OS X或Ubuntu这样的Linux OS,安装过程会有些不同。你可以从http://invpy.com/installing找到人们在自己的计算机上安装Python的在线视频。

当你访问http://python.org的时候,应该会在左边看到链接的一个列表(如“About”、“News”、“Documentation”、“Download”等)。在Download链接上点击,以打开下载页面,然后,找到名为“Python 3.2 Windows Installer(Windows binary -- does not include source)”的一个文件,并且点击该链接,以下载针对Windows的Python。

在刚刚下载得到的python-3.2.msi文件上双击,以启动Python安装程序(如果它没有启动,尝试用鼠标右键点击该文件并且选择Install)。一旦安装程序启动,只要不断地点击Next按钮并随着步骤接受安装程序中的选项(不需要做任何修改)就可以了。当安装完成之后,点击Finish按钮。

Mac OS X 10.5带有Apple预安装的Python 2.5.1。在编写本书的时候,Pygame只支持Python 2,而不支持Python 3。然而,本书中的程序在Python 2和Python 3下都能工作。

Python Web站点还有一些关于在Mac上使用Python的额外信息:http://docs.python.org/dev/using/mac.html

Pygame for Linux只支持Python2,而不支持Python 3。如果你的操作系统是Ubuntu,可以这样来安装Python:打开一个终端窗口(从桌面点击Applications > Accessories > Terminal),并输入“sudo apt-get install python2.7”,然后按下回车键。你将需要输入根密码才能安装Python,如果你不知道这个密码的话,向计算机的所有者询问并输入它。

你还需要安装IDLE软件。从终端中录入“sudo apt-get install idle”。要安装IDLE,也需要根密码(请计算机的所有者为你输入该密码)。

我们将使用IDLE软件来输入程序并运行它。IDLE英文全称为Interactive DeveLopment Environment,中文含义为交互式开发环境。这个开发环境是使得编写Python程序更为容易的一个软件,就像是字处理器软件使得写书很容易一样。如果你的操作系统是Windows XP,你应该能够通过点击Start按钮,然后选择Programs,Python 3.1,IDLE(Python GUI)来运行Python。对于Windows Vista或Window 7来说,只要点击左下角的Windows按钮,输入“IDLE”并选择“IDLE(Python GUI)”就可以了。

如果你的操作系统是Max OS X,通过打开Finder窗口并在Applications上点击,然后点击Python 3.2,点击IDLE图标,以启动IDLE。

如果你的操作系统是Ubuntu或Linux,通过打开一个终端窗口,然后输入“idle3”并按下回车键,来启动IDLE。也可以点击屏幕顶部的Applications,然后选择Programming,然后选择IDLE 3。

当你初次运行IDLE时,会出现叫作交互式shell(interactive shell)的窗口。shell就是一个程序,它允许你向计算机输入指令。Python shell允许你输入Python指令,然后,shell会将这些指令发送给Python解释器去执行,如图1-1所示。

图1-1

Pygame不是Python所附带的。和Python一样,Pygame也是可以免费使用的。你必须下载并安装Pygame,这与下载和安装Python解释器一样容易。在Web浏览器中,访问http://pygame.org并且在该Web站点左边的“Downloads”上点击。本书假设你使用的是Windows操作系统,但是Pygame在每种操作系统上的工作方式都是相同的。你需要针对自己的操作系统以及所安装的Python版本,下载相应的Pygame安装程序。

你不需要下载Pygame的“源代码”,而是要下载针对你的操作系统的“二进制程序”。对于Windows,下载pygame-1.9.1.win32-py3.2.msi文件(这是针对Windows上的Python 3.2的Pygame)。如果你安装的是不同的Python版本(例如,Python 2.7或Python 2.6),那么,要下载针对你的Python版本的.msi文件。在编写本书的时候,Pygame当前的版本是1.9.1。如果你在Web站点上看到了更新的一个版本,请下载并安装较新的Pygame版本。

对于Mac OS X,针对你所拥有的Python版本,下载.zip文件或.dmg文件并运行它们。对于Linux,打开一个终端并运行“sudo apt-get install python-pygame”。

在Windows上,双击所下载的文件以安装Pygame。要检查Pygame是否安装正确,在交互式shell中输入如下内容:

如果按下回车键后什么也没有出现,那么我们知道已经成功地安装了Pygame。如果出现了ImportError: No module named pygame错误,那么,尝试再次安装Pygame,并且确保正确地输入import pygame。

本章有5个小程序,它们展示了如何使用Pygame所提供的不同功能。在本书的最后一章中,我们将使用这些不同的功能,用Python和Pygame编写一个完整游戏。本书的Web站点上提供了安装Pygame的一个视频教程:http://invpy.com/videos

这本书和其他的编程图书不同,因为它专注于几个游戏程序的完整的源代码。本书不会教授编程概念,然后让你自己去搞清楚如何使用这些概念来编写程序,而是向你展示一些程序,然后,说明它们是如何组织起来的。通常,你应该按照顺序阅读本书中的各章。这些游戏中还有很多概念会重复地用到,并且只是会在它们所出现的第一个游戏中加以介绍。但是,如果你认为这个游戏很有趣,可以向前跳到那一章阅读。如果你觉得有些概念不了解,稍后再回头来阅读之前的几章。

每一章都关注一个游戏程序,并且说明代码的不同部分是如何工作的。一行一行地录入本书中的代码,将有助于复制这些游戏。然而,你也可以从本书的Web站点下载源代码。在Web浏览器中,访问http://invpy.com/并且按照说明来下载源文件。但是,自己输入代码真地能够帮助你更好地了解代码。

尽管可以在阅读本书的时候输入代码,但你还是需要从http://invpy.com/downloads下载书中的游戏所用到的图形文件和声音文件。确保将这些图像文件和声音文件放在与.py Python文件相同的目录中,否则的话,你的Python程序可能会无法找到这些文件。

当你自己编写代码的时候,不要输入在每一行开头处出现的行号。例如,如果你在本书中看到如下代码:

不需要输入左边的“1”以及紧接其后的空格。只要输入如下内容就可以了。

这些编号只是为了便于在书中引用特定的代码行。它们并非实际程序的一部分。

除了行号之外,确保按照代码的样子进行输入。注意,其中一些代码行从页面的最左端开始,但是却缩进了4个或8个空格。确保在每一行的开始处放置合适数目的空格(因为IDLE中的每个字符具有相同的宽度,你可以通过统计所看到的代码行之上或之下的字符数,来计算应该使用的空格数)。

例如,在如下的代码中可以看到,第二行缩进了4个空格,因为上面一行的4个字符(“whil”)超出了缩进位置。第3行又缩进了4个空格(4个字符,第三行之上的一行的“if n”超出了缩进位置)。

一些代码行太长了,无法在图书页面中放到一行之中,并且,代码的文本会换到新的一行中。当你在文本编辑器中输入这些代码行的时候,将所有代码输入到同一行而不要按下回车键。

你可以查看代码左边的行号,从而分辨出新的一行开始了。例如,如下的代码只有两行,即便第一行换行了。

本书中的一些程序有点长,尽管通过录入这些程序的源代码对于学习Python是很有帮助的,但你可能偶尔会有些录入错误,从而导致程序崩溃。而这些录入错误可能并不是很明显。

你可以将自己的源代码的文本复制并粘贴到本书Web站点上的在线比较工具中。这个比较工具将会显示出你输入的源代码和书中的源代码的任何不同之处。这是找出程序中的录入错误的一种很容易的方法。

复制和粘贴文本是很有用的计算机技能,特别是对于计算机编程来说。本书的Web站点上有一个视频,介绍如何进行复制和粘贴:http://invpy.com/copypaste

这个在线比较工具的Web页面是http://invpy.com/diff/pygame。本书的Web站点上也有一个视频介绍如何使用该工具。

有很多编程知识需要学习。但是,你现在还不需要学习所有这些内容。在本书中有几处地方,你可能会想要学习这些额外的内容,但是,如果我将这些内容都包含在本书中的话,本书的篇幅会增加不少。这样一来,这本又大又厚的图书会令你崩溃,会烦死你。相反,我把这些内容放在本书的Web站点上一个叫作“more info”的链接之中。要理解本书中的内容,你并不需要阅读这些额外信息,但是,如果你感到好奇的话,可以去看看。这些链接都很短,并且以http://invpy.com打头。

这个“more info”中的所有信息,也都可以通过http://invpy.com/pygamemoreinfo来下载。


Python带有诸如random、math或time这样的几个模块,它们为程序提供额外的功能,同样,Pygame框架也包含了几个模块,其功能包括绘制图形、播放声音、处理鼠标输入等。

本章将介绍Pygame所提供的基本模块和功能,并且假设你已经了解Python编程的基础知识。如果你对理解一些编程概念感到困难,可以阅读我的《Invent Your Own Computer Games with Python》一书,这本书的目标读者是程序设计的完全初学者。

《Invent Your Own Computer Games with Python》这本书中有几章会介绍Pygame。一旦你了解了Pygame,可以通过在线文档http://pygame.org/docs看看Pygame所提供的其他模块。

使用Python的内建函数编写的Python程序,只能够通过print()和input()函数来处理文本。程序可以在屏幕上显示文本,并且让用户通过键盘来输入文本。这类程序有一个命令行界面(command line interface,CLI)。这些程序多少有些局限性,因为它们不能显示图形,没有颜色,并且不能使用鼠标。这种CLI程序只是使用input()函数从键盘获取输入,甚至用户必须按下回车键,然后程序才能够响应输入。这意味着不可能制作实时(也就是说,持续运行代码而不需要等待用户)动作的游戏。

Pygame提供了使用图形化用户界面(graphical user interface,GUI)来创建游戏的功能。使用基于图形的GUI的程序可以显示带有图像和颜色的窗口,而不再是一个基于文本的CLI。

我们用Pygame开发的第一个程序,是在屏幕上显示一个带有“Hello World”的窗口的小程序。通过点击IDLE的File菜单,然后选择New Window,打开一个新的文件编辑器。在IDLE的文件编辑器中,输入如下的代码并将其保存为blankpygame.py 。然后,按下F5键或者从文件编辑器顶部的菜单选择Run > Run Module,运行该程序。

记住,不要输入每一行开始处的行号和句点(那些只是为了方便在本书中引用)。

当运行这个程序的时候,将会出现一个黑色的窗口,如图2-1所示。

图2-1

是的,你刚刚创建了世界上最无趣的视屏游戏。它只是一个空白的窗口,在窗口的顶部显示了一个“Hello World!”(在所谓的窗口的标题栏中,标题栏会保存标题文本)。

但是,创建一个窗口只是制作图形化游戏的第一步。当你点击窗口右上角的X按钮的时候,程序会终止并且窗口会消失。

调用print()函数来让文本出现在窗口中的方法无效,因为print()是一个用于CLI程序的函数。对于使用input()获取来自用户的键盘输入,也是一样的。Pygame使用其他的函数进行输入和输出,我们将在本章稍后介绍它们。现在,我们来详细看一下“Hello World”程序中的每一行代码。

Hello World的前几行,几乎在你使用Pygame编写的每一个程序中都会用作开头的几行。

第1行是一条简单的import语句,它导入pygame和sys模块,以便我们可以在程序中使用这些模块中的函数。Pygame所提供的所有那些处理图形、声音以及其他功能的Pygame函数,都位于pygame模块中。

当导入pygame模块的时候要注意,你也会自动地导入位于pygame模块之中的所有模块,如pygame.images和pygame.mixer.music。不需要再用其他的import语句来导入这些位于该模块之中的模块。

第2行也是一条import语句。然而,它使用了from modulename import *的格式,而不是import modulename的格式。通常,如果你想要调用模块中的一个函数,必须在导入该模块之后,使用modulename.functionname()的格式。然而,通过使用from modulename import *,你可以省略掉modulename.部分,而直接使用functionname()来调用(就像是调用Python的内建函数一样)。

针对pygame.locals使用这种形式的import语句,是因为pygame.locals包含了几个常量变量,它们前面不需要pygame.locals,也可以很容易地识别出是pygame.locals模块中的变量。对于所有其他的模块,通常会使用常规的import modulename格式(http://invpy.com/namespaces更为详细地介绍我们想要这么做的原因)。

第4行是pygame.init()函数调用,在导入了pygame之后并且在调用任何其他的Pygame函数之前,总是需要调用该函数。现在不需要知道这个函数到底做些什么,只需要知道,要让众多的Pygame函数能够工作,我们需要先调用这个函数。如果你看到诸如pygame.error: font not initialized的一个错误,检查看看是否在程序的开始处忘记调用pygame.init()了。

第5行调用了pygame.display.set_mode()函数,它返回了用于该窗口的pygame. Surface对象(本章后面将会介绍Surface对象)。注意,我们给该函数传入了两个整数的一个元组值:(400, 300)。这个元组告诉set_mode()函数创建一个宽度和高度分别为多少个像素的窗口。(400, 300)将会创建一个宽400像素、高300像素的窗口。

记住给set_mode()传递两个整数的一个元组,而不是两个整数自身。调用该函数的正确方式是这样的: pygame.display.set_mode((400, 300))。诸如pygame.display. set_mode (400, 300)的一个函数调用,将会导致TypeError: argument 1 must be 2-item sequence, not int这样的一个错误。

返回的pygame.Surface对象(为了简便起见,我们将其称为Surface对象),存储在一个名为DISPLAYSURF的变量中。

第6行通过调用pygame.display.set_caption()函数,设置了将要在窗口的顶部显示的标题文本。在这个函数调用中,传入了字符串值'Hello World!',以使得该文本作为标题出现,如图2-2所示。

图2-2

第7行是一个while循环,它有一个直接为True值的条件。这意味着它不会因为该条件求得False而退出。程序执行退出的唯一方式是执行一条break语句(该语句将执行移动到循环之后的第一行代码)或者sys.exit()(它会终止程序)。如果像这样的一个循环位于一个函数中,一条return语句也可以使得执行退出循环(同时退出函数的执行)。

本书中的游戏,其中都带有这样的一些while True循环,并且带有一条将该循环称为“main game loop”的注释。游戏循环(game loop,也叫作主循环,main loop)中的代码做如下3件事情。

1.处理事件。

2.更新游戏状态。

3.在屏幕上绘制游戏状态。

游戏状态(game state)只不过是针对游戏程序中用到的所有变量的一组值的一种叫法。在很多游戏中,游戏状态包括了记录玩家的生命值和位置、敌人的生命值和位置、在游戏板做出了什么标记、分数值或者轮到谁在玩等信息的变量的值。任何时候,如玩家受到伤害(这会减少其生命值),敌人移动到某个地方,或者游戏世界中发生某些事情的时候,我们就说,游戏的状态发生了变化。

如果你已经玩过那种允许你存盘的游戏,“保存状态(save state)”就是你在某个时刻所存储的游戏状态。在大多数游戏中,暂停游戏将会阻止游戏状态发生变化。

由于游戏状态通常是作为对事件的响应而更新(例如,点击鼠标或者按下键盘),或者是随着时间的流逝而更新,游戏循环在一秒钟之内会不断地、多次检查和重复检查是否发生了任何新的事件。在主循环中,就有用来查看创建了哪个事件的代码(使用Pygame,这通过调用pygame.event.get()函数来做到)。在主循环中,还有根据创建了哪个事件而更新游戏状态的代码。这通常叫作事件处理(event handling),如图2-3所示。

图2-3

任何时候,当用户做了诸如按下一个按键或者把鼠标移动到程序的窗口之上等几个动作之一(在本章后面会列出这些动作),Pygame库就会创建一个pygame.event.Event对象来记录这个动作,也就是“事件”(这种叫作Event的对象,存在于event模块中,该模块本身位于pygame模块之中)。我们可以调用pygame.event.get()函数来搞清楚发生了什么事件,该函数返回pygame.event.Event对象(为了简单起见,我们直接称之为Event对象)的一个列表。

这个Event对象的列表,包含了自上次调用pygame.event.get()函数之后所发生的所有事件(或者,如果从来没有调用过pygame.event.get(),会包括自程序启动以来所发生的所有事件)。

第8行是一个for循环,它会遍历pygame.event.get()所返回的Event对象的列表。在这个for循环的每一次迭代中,一个名为event的变量将会被赋值为列表中的下一个事件对象。pygame.event.get()所返回的Event对象的列表,将会按照事件发生的顺序来排序。如果用户点击鼠标并按下键盘按键,鼠标点击的Event对象将会是列表的第一项,键盘按键的Event对象将会是第二项。如果没有事件发生,那么pygame.event.get()将返回一个空白的列表。

Event对象有一个名为type的成员变量(member variable,也叫作属性,attributes或properties),它告诉我们对象表示何种事件。针对pygame.locals模块中的每一种可能的类型,Pygame都有一个常量变量。第9行检查Event对象的type是否等于常量QUIT。记住,由于我们使用了from pygame.locals import *形式的import语句,主要输入QUIT就可以了,而不必输入pygame.locals.QUIT。

如果Event对象是一个停止事件,就会调用pygame.quit()和sys.exit()函数。pygame. quit()是pygame.init()函数的一种相反的函数,它运行的代码会使得Pygame库停止工作。在调用sys.exit()终止程序之前,总是应该先调用pygame.quit()。通常,由于程序退出之前,Python总是会关闭pygame,这不会真的有什么问题。但是,在IDLE中有一个bug,如果一个Pygame程序在调用pygame.quit()之前就终止了,将会导致IDLE挂起。

我们没有if语句来针对其他的Event对象类型运行代码,因此,当用户点击鼠标、按下键盘按键或者导致创建任何其他类型的Event对象的时候,没有事件处理代码。用户可能会做一些事情来创建这些Event对象,但是,这并不会对程序有任何改变,因为程序不会有任何针对这些类型的Event对象的事件处理代码。在第8行中的for循环执行完后,就处理完了pygame.event.get()所返回的所有Event对象,程序继续从第12行开始执行。

第12行调用了pygame.display.update()函数,它把pygame.display.set_ mode()所返回的Surface对象绘制到屏幕上(记住,我们将这个对象存储在了DISPLAYSURF变量中)。由于Surface对象没有变化(例如,没有被本章稍后将会介绍的某些绘制函数修改),每次调用pygame.display.update()的时候,将会重新绘制相同的黑色图像。

这就是整个程序。在第12行代码执行之后,无限的while循环再次从头开始。这个程序只是让一个黑色的窗口出现在屏幕上,不断地检查QUIT事件,然后重复地将这个没有变化的黑色窗口重新绘制到屏幕上,除此之外,什么也不做。接下来,我们来学习像素、Surface对象、Color对象、Rect对象和Pygame绘制函数,以了解如何让一些有趣的内容出现在这个窗口中,而不只是一片黑压压的颜色。

“Hello World”程序所创建的窗口,只不过是屏幕上叫作像素(pixel)的小方点的组合。每个像素最初都是黑色的,但是可以设置为一种不同的颜色。假设我们只有一个8像素8像素的Surface对象,而不是一个400像素宽和300像素高的Surface对象,并且,为X 轴和Y 轴添加了数字,然后,就可以很好地将其表示为如图2-4所示的样子。

图2-4

我们可以使用一个笛卡尔坐标系统(Cartesian Coordinate system)来表示一个特定的点。X 轴上的每一列和Y 轴的每一行都有一个地址,也就说从0~7的一个整数,我们可以通过指定X 轴和Y 轴的整数来定位任何的像素。

例如,在上面的88图像中,我们可以看到XY 坐标为(4, 0)、(2, 2)、(0, 5)和(5, 6)的像素显示为黑色,而坐标为(2, 4)的像素显示为灰色。XY 坐标也叫作点(point)。如果你已经上过数学课或者学习过笛卡尔坐标,你可能会注意到,Y 坐标从最顶部的0开始,然后向下增加,而不是向上增加。这就是为什么在Pygame中(以及几乎每一种编程语言中),笛卡尔坐标是有效的。

Pygame框架通常将笛卡尔坐标表示为两个整数的一个元组,例如,(4, 0)或(2, 2)。第1个整数是X 坐标,而第2个整数是Y 坐标(我在《Invent Your Own Computer Games with Python》一书的第12章详细地介绍了笛卡尔坐标)。

函数和方法几乎是相同的东西。二者都可以通过调用来执行其中的代码。一个函数和一个方法之间的区别在于方法总是要附加给一个对象。通常,方法修改和特定对象相关的某些内容(你可以将附加的对象当作传递给方法的一种永久性的参数)。

如下是调用名为foo()的一个函数。

如下是对同样名为foo()的一个方法的调用,该方法附加给了一个对象,这个对象存储在一个名为duckie的变量中。

对于模块中的一个函数的调用,看上去可能像是一个方法调用。为了对二者加以区分,你需要先看一下第一个名称,看看它是一个模块的名称,还是包含了对象的一个变量的名称。你可以分清楚,sys.exit()是对于模块中的一个函数的调用,因为程序的开始处有一条类似import sys的import语句。

构造函数(constructor function)是可以和常规函数一样调用的函数,只不过其返回值是一个新的对象。单看源代码,函数和构造函数是一样的。构造函数(也可以简单地称为constructor,或者有时候称为ctor)只是对于那些返回一个新的对象的函数的叫法。通常,构造函数是以一个大写字母开头的。这就是为什么当你编写自己的程序的时候,函数名应该总是以小写字母开头。

例如,pygame.Rect()和pygame.Surface()都是pygame模块中的构造函数,它们分别返回一个新的Rect对象和Surface对象(稍后将会介绍这些对象)。

如下是函数调用、方法调用以及调用模块中的一个函数的示例。

即便这些名称都放到一起,你也可以分辨出哪一个是函数调用,哪一个是方法调用,而哪一个是对方法中的一个函数的调用。名称whammy引用一个模块,因为你可以看到,在第一行已经导入了它。名称fizzy的前面以及其后面的圆括号中都没有任何内容,因此,我们知道这是一个函数调用。

Wombat()也是一个函数调用,只不过它是一个构造函数(开头的大写字母并不能保证它是一个构造函数,而不是一个常规的函数,但这是一个额外的标志),会返回一个对象。该对象存储到名为egg的一个变量中。egg.bluhbluh()调用是一个方法调用,之所以能够分辨出来,是因为bluhbluh附加给了一个变量,而该变量中存储了一个对象。

同时,whammy.spam()是一个函数调用,而不是一个方法调用。你可以看出它不是一个方法,因为名称whammy在前面是作为一个模块导入的。

Surface对象是表示一个矩形的2D图像的对象。可以通过调用Pygame绘制函数,来改变Surface对象的像素,然后再显示到屏幕上。窗口的边框、标题栏和按钮并不是Surface对象的一部分。

特别是pygame.display.set_mode()返回的Surface对象叫作显示Surface(display Surface)。绘制到显示Surface对象上的任何内容,当调用pygame.display.update()函数的时候,都会显示到窗口上。在一个Surface对象上绘制(该对象只存在于计算机内存之中),比把一个Surface对象绘制到计算机屏幕上要快很多。这是因为修改计算机内存比修改显示器上的像素要快很多。

程序经常要把几个不同的内容绘制到一个Surface对象中。在游戏循环的本次迭代中,一旦将一个Surface对象上的所有内容都绘制到了显示Surface对象上(这叫作一帧,就像是暂停的DVD上的一幅静止的画面),这个显示Surface对象就会绘制到屏幕上。计算机可以很快地绘制帧,并且我们的程序通常会每秒运行30帧,即30 FPS。这叫作帧速率(frame rate),我们将在本章后面介绍它。

本章后面的第2.14节和2.19节将会介绍在Surface对象上绘制。

光线有3种主要的颜色:红色、绿色和蓝色(红色、蓝色和黄色是绘画和颜料的主要颜色,但是计算机显示器使用光,而不是颜料)。通过将这3种颜色的不同的量组合起来,可以形成任何其他的颜色。在Pygame中,我们使用3个整数的元组来表示颜色。元组中的第1个值,表示颜色中有多少红色。为0的整数值表示该颜色中没有红色,而255表示该颜色中的红色达到最大值。第2个值表示绿色,而第3个值表示蓝色。这些用来表示一种颜色的3个整数的元组,通常称为RGB值(RGB value)。

由于我们可以针对3种主要的颜色使用0~255的任何组合,这就意味着Pygame可以绘制16 777 216 种不同的颜色,即256×256×256种颜色。然而,如果试图使用大于255的值或者负值,将会得到类似“ValueError: invalid color argument”的一个错误。

例如,我们创建元组(0, 0, 0)并且将其存储到一个名为BLACK的变量中。没有红色、绿色和蓝色的颜色量,最终的颜色是完全的黑色。黑色就是任何颜色都没有。元组(255, 255, 255)表示红色、绿色和蓝色都达到最大量,这最终得到白色。白色是红色、绿色和蓝色的完全的组合。元组(255, 0, 0)表示红色达到最大量,而没有绿色和蓝色,因此,最终的颜色是红色。类似的,(0, 255, 0)是绿色,而(0, 0, 255)是蓝色。

可以组合红色、绿色和蓝色的量来形成其他的颜色。表2-1列出了几种常见的颜色的RGB值。

表2-1

颜色

RGB值

颜色

RGB值

湖绿色

( 0, 255, 255)

海军蓝

( 0,  0, 128)

黑色

( 0,  0,  0)

橄榄色

(128, 128,  0)

蓝色

( 0,  0, 255)

紫色

(128,  0, 128)

紫红色

(255,  0, 255)

红色

(255,  0,  0)

灰色

(128, 128, 128)

银色

(192, 192, 192)

绿色

( 0, 128,  0)

青色

( 0, 128, 128)

浅绿色

( 0, 255,  0)

白色

(255, 255, 255)

褐红色

(128,  0,  0)

黄色

(255, 255,  0)

当你通过一个带有红色色调的玻璃窗口看过去,其背后的所有颜色都会增加一个红色的阴影。你可以通过给颜色值添加第4个0~255的整数值来模仿这种效果。这个值叫作alpha值(alpha value)。这是表示一种颜色有多么不透明的一个度量值。通常,当你在一个Surface对象上绘制一个像素的时候,新的颜色完全替代了那里已经存在的任何颜色。但是,使用带有一个alpha值的颜色,可以只是给已经存在的颜色之上添加一个带有颜色的色调。

例如,表示绿色的3个整数值的一个元组是(0, 255, 0)。但是,如果添加了第4个整数作为alpha值,我们可以使其成为一个半透明的绿色(0, 255, 0, 128)。255的alpha值是完全不透明的(也就是说,根本没有透明度)。颜色(0, 255, 0)和(0, 255, 0, 255)看上去完全相同。alpha值为0,表示该颜色是完全透明的。如果将alpha值为0的任何一个颜色绘制到一个Surface对象上,它没有任何效果,因为这个颜色完全是透明的,且不可见。

为了使用透明颜色来进行绘制,必须使用convert_alpha()方法创建一个Surface对象。例如,如下代码创建了一个可以在其上绘制透明颜色的Surface对象。

一旦在该Surface对象上绘制的内容存储到了anotherSurface中,随后another Surface可以“复制”(blit,也就是copy)到DISPLAYSURF中,以便它可以显示在屏幕上(参见本章稍后的2.19节)。在那些并非从一个convert_alpha()返回的Surface对象上,你不能够使用透明颜色,这也包括从pygame.display. set_mode()返回的显示Surface,注意这一点是很重要的。

如果我们创建了一个颜色元组来绘制著名的隐形粉红独角兽,应该会使用(255, 192, 192, 0),它最终看上去是完全不可见的,就像是alpha值为0的任何其他颜色一样。毕竟,它是隐形的。图2-5是绘制隐形粉红独角兽的屏幕截图。

图2-5

你需要知道如何表示一种颜色,因为Pygame的绘制函数需要一种方式来知道你想要使用何种颜色进行绘制。3个整数或4个整数的一个元组是一种方式。另一种方式是一个pygame.Color对象。你可以调用pygame.Color()构造函数,并且传入3个整数或4个整数,来创建Color对象。可以将这个Color对象存储到变量中,就像可以将元组存储到变量中一样。尝试在交互式shell中输入如下的内容。

Pygame中的任何绘制函数(我们将会学习一些),如果有一个针对颜色的参数的话,那么可以为其传递元组形式或者Color对象形式的颜色。即便二者是不同的数据类型,但如果它们都表示相同的颜色的话,一个Color对象等同于4个整数的一个元组(就像42 == 42.0将会为True一样)。

既然你知道了如何表示颜色(作为pygame.Color对象,或者是3个整数或4个整数的一个元组,3个整数的话,分别表示红色、绿色和蓝色,4个整数的话,还包括一个可选的alpha值)和坐标(作为表示XY 的两个整数的一个元组),让我们来了解一下pygame.Rect对象,以便可以开始使用Pygame的绘制函数。

Pygame有两种方法来表示矩形区域(就像有两种方法表示颜色一样)。第一种是4个整数的元组。

1.左上角的X 坐标。

2.左上角的Y 坐标。

3.矩形的宽度(以像素为单位)。

4.矩形的高度(以像素为单位)。

第二种方法是作为一个pygame.Rect对象,我们后面将其简称为Rect对象。例如,如下的代码创建了一个Rect对象,它的左上角位于(10, 20),宽度为200像素,高度为300像素。

这种表示的方便之处在于Rect对象自动计算矩形的其他部分的坐标。例如,如果你需要知道变量spamRect中所存储的pygame.Rect对象的右边的X 坐标,只需要访问Rect对象的right属性。

如果左边的X 坐标为10并且矩形的宽度为200像素,Rect对象的Pygame代码会自动计算出矩形的右边的X 坐标必须位于210。如果重新设置right属性,所有其他的属性也会自动计算求得。

表2-2列出了pygame.Rect对象所提供的所有属性(在我们的示例中,Rect对象存储在名为myRect的一个变量中)。

表2-2

属性名称

说明

myRect.left

矩形的左边的X 坐标的int值

myRect.right

矩形的右边的X 坐标的int值

myRect.top

矩形的顶部的Y 坐标的int值

myRect.bottom

矩形的底部的Y 坐标的int值

myRect.centerx

矩形的中央的X 坐标的int值

myRect.centery

矩形的中央的Y 坐标的int值

myRect.width

矩形的宽度的int值

myRect.height

矩形的高度的int值

myRect.size

两个整数的一个元组:(width, height)

myRect.topleft

两个整数的一个元组:(left, top)

myRect.topright

两个整数的一个元组:(right, top)

myRect.bottomleft

两个整数的一个元组:(left, bottom)

myRect.bottomright

两个整数的一个元组:(right, bottom)

myRect.midleft

两个整数的一个元组:(left, centery)

myRect.midright

两个整数的一个元组:(right, centery)

myRect.midtop

两个整数的一个元组:(centerx, top)

myRect.midbottom

两个整数的一个元组:(centerx, bottom)

Pygame提供了几个不同的函数,用于在一个Surface对象上绘制不同的形状。这些形状包括矩形、圆形、椭圆形、线条或单个的像素,通常都称为绘制图元(drawing primitives)。打开IDLE的文件编辑器并且输入如下的程序,将其保存为drawing.py

当这个程序运行的时候,会显示如图2-6所示的窗口,直到用户关闭该窗口。

图2-6

注意我们是如何为每种颜色设置常量变量的。这么做使得代码更具有可读性,因为在源代码中看到GREEN,很容易理解这是表示绿色,比使用(0, 255, 0)要清晰很多。

绘制函数是根据它们所绘制的形状来命名的。传递给这些函数的参数,则告诉它们在哪一个Surface对象上绘制,将形状绘制到何处(以及形状的大小是多少),用什么颜色绘制,以及使用的线条的宽度是多少。在drawing.py程序中,可以看到这些函数是如何调用的,如下是针对每个函数的一个简短介绍。

pointlist参数是一个元组或者点的列表(也就是说,用于XY 坐标的元组,或者两个整数的元组的列表)。多边形是通过这样的方式来绘制的,即在每个点以及元组中其后续的点之间绘制线条,然后,从最后的点到第一个点绘制一个线条。你也可以传递点的列表,而不是点的元组。

width参数是可选的。如果你漏掉了这个参数,多边形将会绘制为填充的,就像是屏幕上的绿色多边形那样,会用颜色来填充它。如果确实给width参数传递了一个整数值,则只是绘制出多边形的边框。这个整数表示多边形的边框会有多少个像素那么宽。给width参数传递1,将会绘制一个边框细瘦的多边形,而传递4、10或者20,将会绘制一个边框较粗一些的多边形。如果给width参数传入0,多边形将会是填充的(这和完全漏掉了width参数的效果一样)。

所有的pygame.draw绘制函数的最后都有可选的width参数,并且,它们的工作方式和pygame.draw.polygon()的width参数相同。可能width参数的一个更好的名称应该是thickness,因为这个参数控制了你绘制的线条有多粗。

圆的半径是从圆心到边的距离(圆的半径总是其直径的一半)。给radius参数传递20,将会绘制半径为20个像素的一个圆。

图2-7

bounding_rectangle参数可以是一个pygame.Rect对象或者是4个整数的一个元组。注意,不必像在pygame.draw.circle()函数中那样来指定椭圆形的圆心。

遗憾的是没有单个的函数可以调用来设置一个单个像素的颜色(除非你使用相同的起点和终点来调用pygame.draw.line())[1]。在向一个Surface对象绘制之前和之后,Pygame框架需要在幕后运行一些代码。如果它必须针对想要设置的每一个像素来做这些事情,程序将会运行得慢很多(根据我的快速测试,用这种方式绘制像素,所需时间会是原来的两到三倍)。

相反,应该创建一个Surface对象的pygame.PixelArray对象(我们后面将其简称为PixelArray对象)。创建一个Surface对象的PixelArray对象,将会“锁定”该Surface对象。而当一个Surface对象锁定的时候,仍然能够在其上调用绘制函数,但是,不能够使用blit()方法在其上绘制诸如PNG或JPG这样的图像(本章稍后将会介绍blit()方法)。

如果想要查看一个Surface对象是否锁定了,使用get_locked()方法,如果它锁定了,Surface的get_locked()方法将会返回True,否则的话,返回False。

由pygame.PixelArray()返回的PixelArray对象,可以通过两个索引来访问,从而设置单个的像素。例如,第28行的pixObj[480][380] = BLACK将会把X 坐标为480、Y 坐标为380的点设置为黑色(别忘了,BLACK变量存储的颜色元组是(0, 0, 0))。

要告诉Pygame已经完成了单个像素的绘制,用一条del语句删除掉PixelArray对象,这就是第33行所做的事情。删除PixelArray对象,将会“解锁”Surface对象,以便你可以再次在其上绘制图像。如果忘记了删除PixelArray对象,下一次尝试将一幅图像复制,即绘制到Surface上的时候,程序会导致一条如下所示的错误“pygame.error: Surfaces must not be locked during blit”。

在调用了绘制函数以便让显示Surface对象看上去是你想要的方式之后,必须调用pygame.display.update()让显示Surface真正地出现在用户的显示器上。

必须记住的一件事情是pygame.display.update()将使得显示Surface,即通过调用pygame.display.set_mode()而返回的Surface对象,出现在屏幕上。如果想要让其他Surface对象上的图像出现在屏幕上,必须使用blit()方法(我们将在2.19节中介绍)将其复制到显示Surface对象上。

既然知道了如何让Pygame框架绘制到屏幕上,让我们来学习一下如何制作动画。只有静止的、不能移动的图像的游戏将会相当无聊(我的游戏“Look At This Rock”的销售情况就很令人失望)。动画的图像是做如下的事情所产生的结果:在屏幕上绘制图像,然后隔几秒后在屏幕上绘制一幅略为不同的图像。想象一下程序的窗口有6个像素宽和1个像素高,所有的像素都是白色,而只有位于4,0的一个像素是黑色,看上去如图2-8所示。

图2-8

如果修改了窗口使得3,0的像素成为黑色,而4,0成为白色,看上去将会如图2-9所示。

图2-9

对于用户来说,看上去好像黑色的像素向左边“移动”了。如果重新绘制窗口,使得2,0的像素成为黑色,那么,看上去好像是黑色的像素继续向左移动了,如图2-10所示。

图2-10

看上去好像是黑色的像素在移动,但这只是错觉。对于计算机来说,它只是显示了3幅不同的图像,而每一幅图像上恰好都有一个黑色的像素。考虑一下,如果如图2-11所示的3幅图像快速出现在屏幕上。

图2-11

对于用户来说,看上去像是猫在朝着松鼠的方向移动。但是,对于计算机来说,它们只是一堆的像素。制作看上去逼真的动画的技巧在于让程序将一幅图片绘制到窗口上,等待数秒钟,然后绘制一幅略微不同的图片。

如下的示例程序展示了一个简单的动画。在IDLE的文件编辑器中输入这段代码,并且将其保存为catanimation.py 。还需要将图像文件cat.png放在与catanimation.py 文件相同的目录下。可以从http://invpy.com/cat.png下载这个图像。从http://invpy.com/catanimation.py可以找到这段代码。

看一下,动画的猫实现了。这个程序比我的游戏“Look At This Rock 2: A Different Rock”在商业上要成功很多。

帧速率(frame rate)或刷新速率(refresh rate)是程序每秒钟绘制的图像的数目,用FPS或帧/秒来度量(在计算机显示器上,FPS常见的名称是赫兹。很多显示器的帧速率是60Hz,或者说每秒60帧)。视频游戏中,较低的帧速率会使得游戏看上去抖动或卡顿。如果游戏包含的代码太多了,以至于无法运行来频繁地绘制到屏幕上,那么,FPS会下降。但是,本书中的游戏都足够简单,甚至在较旧的计算机上也不会有问题。

pygame.time.Clock对象可以帮助我们确保程序以某一个最大的FPS运行。Clock对象将会在游戏循环的每一次迭代上都设置一个小小的暂停,从而确保游戏程序不会运行得太快。如果没有这些暂停,游戏程序可能会按照计算机所能够运行的速度去运行。这对玩家来说往往太快了,并且计算机越快,它们运行游戏也就越快。在游戏循环中调用一个Clock对象的tick()方法,可以确保不管计算机有多快,游戏都按照相同的速度运行。catanimation.py程序的第7行创建了Clock对象。

每次游戏循环的最后,在调用了pygame.display.update()之后,应该调用Clock对象的tick()方法。根据前一次调用tick()(这在游戏循环的前一次迭代的末尾进行)之后经过了多长时间,来计算需要暂停多长时间(第一次调用tick()方法的时候,根本没有暂停)。在动画程序中,调用tick()是在第47行进行的,作为游戏循环中的最后一条指令。

你需要知道的是在游戏循环的每一次迭代中,应该在循环的末尾调用tick()方法一次。通常,这刚好在调用了pygame.display.update()之后进行。

尝试修改FPS常量变量,以不同的帧速率来运行相同的游戏。将其设置为一个较低的值,就会使得程序运行得较慢。将其设置为一个较高的值,就会让程序运行得较快。

如果你想要在屏幕上绘制简单的形状,绘制函数已经很好用了,但是,很多游戏都有图像(也叫作精灵,sprite)。Pygame能够从PNG、JPG、GIF和BMP图像文件中,将图像加载到Surface对象上。这些图像文件格式的区别参见http://invpy.com/formats

猫的图像存储在一个名为cat.png 的文件中。要加载这个文件的图像,将字符串'cat.png'传递给pygame.image.load()函数。pygame.image.load()函数调用将会返回一个Surface对象,图像已经绘制于其上。这个Surface对象将会是和显示Surface对象不同的另一个Surface对象,因此,我们必须将图像的Surface对象复制到显示Surface对象上。位图复制(Blitting)就是将一个Surface的内容绘制到另一个Surface之上。这通过blit() Surface对象方法来完成。

如果在调用pygame.image.load()的时候得到了一条错误消息,如“pygame. error: Couldn't open cat.png”,那么,在运行程序之前,请确保cat.png文件和catanimation.py位于同一文件夹之中。

动画程序的第39行使用blit()方法把catImg复制到了DISPLAYSURF。blit()方法有两个参数。第一个参数是源Surface对象,这是将要复制到DISPLAYSURF Surface对象上的内容。第2个参数是两个整数的一个元组,这两个整数表示图像应该复制到的位置的左上角的XY 坐标。

如果catx和caty设置为100和200,catImg的宽度为125,高度为79,这个blit()调用将会把该图像复制到DISPLAYSURF上,以使得catImg的左上角的XY 坐标为(100, 200),而其右下角的XY 坐标为(225, 279)。

注意,不能复制当前“锁定”的一个Surface(例如,通过其生成了一个PixelArray对象并且还没有删除该对象)。

游戏循环剩下的部分只是修改catx、caty和direction变量,以使得猫在窗口中移动。还有一个pygame.event.get()调用负责处理QUIT事件。

如果想要将文本绘制到屏幕上,也可以编写几个pygame.draw.line()调用,来绘制出每个字母的线条。然而,录入所有那些pygame.draw.line()调用并计算出所有的XY 坐标,这将会是一件令人头疼的事情,并且看上去效果也不会很好,如图2-12所示。

图2-12

上面的这条消息,可能需要调用pygame.draw. line()函数41次才能产生。相反,Pygame提供了一些非常简单的函数用于字体和文本创建。如下是使用Pygame的字体函数的一个较小的Hello World程序。在IDLE文件编辑器中输入代码,并且将其保存为fonttext.py

让文本显示到屏幕上,一共有6个步骤。

1.创建一个pygame.font.Font对象(如第12行所示)。

2.创建一个Surface对象,通过调用Font对象的render()方法,将文本绘制于其上(如第13行所示)。

3.通过调用Surface对象的get_rect()方法,从Surface对象创建一个Rect对象(如第14行所示)。这个Rect对象将具有为文本而设置的正确的宽度和高度,但是,其top和left属性将为0。

4.通过修改Rect对象的属性之一,来设置其位置。在第15行,我们将Rect对象的中心设置为200, 150。

5.将带有文本的Surface对象复制到pygame.display.set_mode()所返回的Surface对象上(如第19行所示)。

6.调用pygame.display.update(),使显示Surface出现在屏幕上(如第24行所示)。

pygame.font.Font()构造函数的参数是表示所要使用的字体文件的一个字符串,以及表示字体大小的一个整数(以点为单位,这和字处理程序度量字体大小的单位相同)。在第12行,我们传入了'freesansbold.ttf'(这是Pygame自带的一种字体)和整数32(字体大小为32点)。

参见http://invpy.com/usingotherfonts了解使用其他字体的相关信息。

render()方法调用的参数是所要显示的文本的一个字符串,指定是否想要抗锯齿(本章稍后介绍)的一个Boolean值、文本的颜色,以及背景的颜色。如果想要一个透明的背景,那么,直接在方法调用中漏掉背景颜色参数即可。

抗锯齿(Anti-aliasing)是一种图形技术,通过给文本和图形的边缘添加一些模糊效果,使其看上去不那么块状化。带有抗锯齿效果的绘制需要多花一些计算时间,因此,尽管图形看上去更好,但程序可能会运行得较慢(但只是略微慢一点)。

如果放大一条带有锯齿的线条和一条抗锯齿的线条,它们的样子如图2-13所示。

图2-13

要对Pygame的文本使用抗锯齿效果,只需要给render()方法的第二个参数传入True。pygame. draw.aaline()和pygame.draw.aalines()函数,分别和pygame.draw.line()和pygame.draw. lines()函数具有相同的参数,只不过,它们绘制抗锯齿(平滑的)线条,而不是带锯齿(块状化的)线条。

播放存储在声音文件中的声音,甚至比显示图像文件中的图像还要简单。首先,必须通过调用pygame.mixer.Sound()构造函数,来创建一个pygame.mixer.Sound对象(后面我们将其简称为Sound对象)。它接受一个字符串参数,这是声音文件的文件名。Pygame可以加载WAV、MP3或OGG文件。http://invpy.com/formats介绍了这些声音文件格式的区别。

要播放声音,调用Sound对象的play()方法。如果想要立即停止Sound对象播放,调用stop()方法。stop()方法没有参数。如下是一些示例代码。

可以从http://invpy.com/beeps.wav下载beeps.wav 文件。

在调用play()之后,程序会立即继续执行;在移动到下一行代码之前,它不会等待声音播放完成。

当玩家受到伤害、挥动一次剑,或收到一个硬币的时候,用Sound对象播放声音效果,这对游戏来说是很不错的。但是,如果不管游戏中发生了什么,都有一个背景音乐在播放,你的游戏可能会更好。Pygame一次只能加载一个作为背景音乐播放的声音文件。要加载一个背景声音文件,调用pygame.mixer.music.load()函数并且将要加载的声音文件作为一个字符串参数传递。这个文件可以是WAV、MP3或MIDI格式。

要开始把加载的声音文件作为背景音乐播放,调用pygame.mixer.music.play(−1, 0.0)函数。当到达了声音文件的末尾的时候,−1参数会使得背景音乐永远循环。如果将其设置为一个整数0或者更大,那么,音乐只能循环指定的那么多次数,而不是永远循环。0.0意味着从头开始播放声音文件。如果这是一个较大的整数值或浮点值,音乐会开始播放直到声音文件中指定的那么多秒。例如,如果传入13.5作为第二个参数,声音文件会从开始处播放到第13.5秒的地方。

要立即停止背景音乐,调用pygame.mixer.music.stop()函数。该函数没有参数。

如下是声音方法和函数的一些示例代码。

本章介绍了使用Pygame框架编写图形化游戏的基础知识。当然,只是了解这些函数,可能并不足以帮助你学习如何使用这些函数来编写游戏。本书中其他的各章,每一章都关注一个较小的、完整的游戏的源代码。这会帮助你认识到完整的游戏程序看上去是什么样的,以便随后你可以对如何编写自己的游戏程序有更多的思路。

和《Invent Your Own Computer Games with Python》一书不同,本书假设你了解基本的Python编程。如果你想不起来变量、函数、循环、if-else语句和条件是如何工作的,你可能能够通过查看代码中的内容以及程序如何工作而搞清楚这些。但是,如果你仍然有困难,可以阅读《Invent Your Own Computer Games with Python》这本书(这本书针对完全的编程新手)。

[1]作者注:实际上,Surface对象有set_at( )和get_at( )方法。这里最初的表述不严谨。


相关图书

深度学习的数学——使用Python语言
深度学习的数学——使用Python语言
动手学自然语言处理
动手学自然语言处理
Web应用安全
Web应用安全
Python高性能编程(第2版)
Python高性能编程(第2版)
图像处理与计算机视觉实践——基于OpenCV和Python
图像处理与计算机视觉实践——基于OpenCV和Python
Python数据科学实战
Python数据科学实战

相关文章

相关课程