Python功能众多,你已尝试使用了几个,现在该大干一场了。在本项目中,你将学习如何使用Pygame,这个扩展让你能够使用Python编写功能齐备的全屏街机游戏。Pygame虽然易于使用,功能却非常强大。它由多个组件组成,Pygame文档(参见Pygame官网http://pygame.org)做了详尽的介绍。本项目将介绍一些主要的Pygame概念,但鉴于本项目的目标是让你起步,因此不会介绍诸如声音和视频处理等有趣的功能。建议你掌握基本知识后再去探索其他功能。你可能还想参阅Will McGugan和Harrison Kinsley的著作Beginning Python Games Development或Paul Craven的著作Program Arcade Games with Python and Pygame

1.问题描述

那么,如何编写计算机游戏呢?游戏的基本设计过程与其它程序类似,但开发对象模型前,必须先设计游戏本身,如游戏包含的角色、所处的环境以及要实现的目标。

为避免打乱有关Pygame基本概念的介绍,这里创建的游戏比较简单。如果你愿意,完全可以创建更复杂的游戏。

这里将创建的游戏是从巨蟒剧团推出的著名短剧“Self-Defense Against Fresh Fruit”改编而来的。在这个短剧中,军士长John Cleese指挥士兵使用防守战术抵御入侵者使用新鲜水果(如石榴、糖水芒果、青梅和香蕉)发起的进攻。防守战术包括使用枪支、放老虎以及敌人头顶扔下重达16吨的铅锤。在这个游戏中,我们将反过来,让玩家控制一只香蕉。这只香蕉要躲开从天而降的16吨铅锤,尽力在防御战中活下来。我想将这个游戏命名为Squish比较合适。


注意    阅读本项目时,如果你想尝试编写自己的游戏,去做就是了。如果你只是想修改这个游戏的外观,只需替换其中的图形(几幅GIF或PNG图像)和一些描述性文本即可。


这个项目的目标是围绕着游戏设计展开的。这款游戏必须像设计的那样:香蕉能够移动,16吨的铅锤从天而降。另外,与往常一样,代码必须是模块化的,且易于扩展。一个重要的需求是。设计应包含一些游戏状态(如游戏简介、关卡和“游戏结束”状态),同时可轻松地添加新状态。

2.有用的工具

这个项目需要的工具只有一个,那就是Pygame,可从其官网(http://pygame.org)下载。要在UNIX中使用Pygame,可能还需要安装其他一些软件,安装Pygame的最简单方式是使用pip。

Pygame发布版包含多个模块,但在这个项目中大都用不到。接下来几小节将描述需要用到的模块(只讨论需要用到的具体函数或类)。除了接下来将描述的函数外,将用到的各种对象(如Surface、Group和Sprite)还包含一些很有用的方法,我们会在实现部分用到时对其进行讨论。

2.1.pygame

模块pygame自动导入其他所有的Pygame模块,因此只要在程序开头包含语句import pygame,就能使用其他模块,如pygame.display和pygame.font。

模块pygame包含类Surface,它返回一个新的Surface对象。Surface对象其实就是一个指定尺寸的空图像,可用来绘画和传送。传送(调用Surface对象的方法blit)意味着在Surface之间传输内容。【传送的英文单词blit是从技术术语块传输(block transfer)的简写BLT衍生而来的。】

函数init是Pygame游戏的核心,必须在游戏进入主事件循环前调用。这个函数自动化其他所有模块(如font和image)。

想要捕获Pygame特有的错误,就需要使用error类。

2.2.pygame.locals

模块pygame.locals包含你可能在自定义模块的作用域内使用的名称(变量),如事件类型、键、视频模式等名称。可导入这个模块的所有内容(from pygame.locals import*),但如果知道需要哪些名称,应该做更具体的导入,如from pygame.locals import FULLSCREEN。

2.3.pygame.display

模块pygame.display包含处理内容显示的函数,这些内容可显示在普通窗口中,也可占据整个屏幕。在这个项目中,需要用到如下函数。

  • flip:更新显示。一般而言,分两步来修改屏幕。首先,对函数get_surface返回的Surface对象做必要的修改,然后调用pygame.display.flip来更新显示,反映出所做的修改。
  • update:只想更新屏幕的一部分时,使用这个函数而不是flip。调用这个函数时,可只提供一个参数,即RenderUpdates类的方法draw返回的矩形列表(这个方法将在接下来讨论模块pygame.sprite时介绍)。
  • set_mode:设置显示的尺寸和类型。显示模式有多种,但这里只是用全拼模式和默认模式“在窗口中显示”。
  • set_caption:设置Pygame程序的标题。函数set_caption主要用于游戏在窗口中运行(而不是以全屏模式运行)时,因为标题将用作窗口的标题。
  • get_surface:返回一个Surface对象,你可在其中绘制图形,再调用pygame.display.flip或pygame.display.blit。这个项目只使用了Surface对象的一个方法来绘画,这就是blit,它将一个Surface对象中的图形传输到另一个Surface对象的指定位置。另外,还将使用Group对象的方法draw在Surface上绘制Sprite对象。

2.4.pygame.font

模块pygame.font将包含类Font。字体对象可用于表示不同的字体,可用于将文本渲染为可在Pygame中作为普通图形使用的图像。

2.5.pygame.sprite

模块pygame.sprite包含两个非常重要的类:Sprite和Group。

Sprite类是所有可见游戏对象(在这个项目中,是香蕉和重16吨的铅锤)的基类。要实现自定义的游戏对象,可从Sprite派生出子类,并重写构造函数以设置其属性image和rect(这些属性决定了Sprite的外观和位置),同时重写在Sprite可能需要更新时调用的方法update。

Group及其子类的实例用作Sprite对象的容器。一般而言,使用Group是个不错的注意。在简单的游戏(如本项目)中,只需创建一个名为sprites或allsprites之类的Group,并将所有Sprite都添加到其中。这样,当你调用Group对象的方法update时,将自动调用所有Sprite对象的方法update。另外,Group对象的方法clear用于清除它包含的所有Sprite对象(实际的清理工作是使用一个回调函数完成的),而方法draw可用于绘制所有的Sprite对象。

在这个项目中,将使用Group的子类RenderUpdates,其方法draw返回列表,其中包含所有受到影响的矩形。可将这个列表传递给pygame.display.update,以后只更新需要更新的部分。通过这样做,有可能极大地改善游戏的性能。

2.6.pygame.mouse

在即将开发的游戏Squish中,只是用模块pygame.mouse来做两件事情:隐藏鼠标以及获取鼠标的位置。这两件事分别是使用pygame.mouse.set_visible(False)和pygame.mouse.get_pos()来完成的。

2.7.pygame.event

模块pygame.event跟踪各种事件,如鼠标单击、鼠标移动、按下或松开键等。要获取最近发生的事件列表,可使用函数pygame.event.get。


注意    如果只需要状态信息,如pygame.mouse.get_pos返回的鼠标位置,就无需使用pygame.event.get。然而,你需要确保Pygame同步地更新,为此可定期调用函数pygame.event。pump。


2.8.pygame.image

模块pygame.image用于处理图像,如以GIF、PNG、JPEG和其他几种文件格式存储的图像。在这个项目中,只需要这个模块的函数load,它读取图像文件并创建一个包含该图像的Surface对象。

3.准备工作

对一些Pygame模块的功能进行粗略了解后,改动手编写这个游戏的第一个原型了。然而,这样做之前,需要做几项准备工作。首先必须确保安装了Pygame,包括模块image和font。(要核实是否安装了这些模块,可在交互式Python解释器中导入它们。)

你还需准备几幅图像。如果要按本项目说的那样呈现游戏的主题,就需要两幅图像,分别表示重16吨的铅锤和香蕉,如图所示。这些图像的尺寸无关紧要,但最好在100像素x100像素~200像素x200像素之间。这两幅图像还应使用常见的图像文件格式,如GIF、PNG或JPEG。

自制街机游戏(1):初次实现_子类自制街机游戏(1):初次实现_python_02


注意    你可能还想提供一张启动屏幕(像游戏用户问候的第一个屏幕)图像。在这个项目中,我直接使用了表示铅锤的图像。


4.初次实现

使用诸如Pygame等新工具开发程序时,应让第一个原型尽可能简单,并将重点放在学习新工具的基本知识,而不是程序本身的细节上。这样做通常大有裨益。因此,在游戏Squish的第一个版本中,我们只创建重16吨的铅锤从天而降的动画。制作这个动画需要步骤如下。

(1)使用pygame.init、pygame.display.set_mode和pygame.mouse.set_visible初始化Pygame。使用pygame.display.get_surface获取屏幕表面,使用方法fill以白色填充屏幕表面,再调用pygame.display.flip现实所做的修改。

(2)加载铅锤图像。

(3)使用这幅图像创建自定义类Weight(Sprite的子类)的一个实例。将这个对象添加到RenderUpdates编组sprites中。(处理多个Sprite对象时,这样做很有帮助。)

(4)使用pygame.event.get获取最近发生的所有事件,并依次检查这些事件。如果发现事件QUIT或因按下Escape键(K_ESCAPE)而触发的KEYDOWN事件,就退出程序。(事件类型和键分别存储在事件对象的属性type和key中。诸如QUIT、KEYDOWN和K_ESCAPE等常量可从模块pygame.locals导入。)

(5)调用编组sprites的方法clear和update。方法clear使用毁掉函数来清除所有的Sprite对象(这里是铅锤),而方法update调用Weight实例的方法update(你必须在Weight类中实现方法update)。

(6)调用sprites.draw并将屏幕表面作为参数,以便在当前位置绘制铅锤(每次调用Weight实例的update方法后,位置都将发生变化)。

(7)调用pygame.display.update,并将sprites.draw返回的矩形列表作为参数,只需更新需要更新的部分。(如果你不在乎性能,可使用pygame.display.flip来更新整个屏幕。)

(8)重复第4~7步。

下图列出了实现这些步骤的代码。在你退出游戏,如关闭窗口时,将发生QUIT事件。

自制街机游戏(1):初次实现_子类_03

自制街机游戏(1):初次实现_子类_04

自制街机游戏(1):初次实现_自定义_05

要运行这个程序,可使用下面的命令:

python weights.py

执行这个命令时,必须确保weights.py和weight.png(铅锤图像)都在当前目录中。下图显示了执行这个程序时的屏幕截图。

自制街机游戏(1):初次实现_子类_06

这些代码大都是不言自明的,但有几点需要解释一下。

  • 所有的Sprite对象都有属性image和rect,其中前者应是一个Surface对象(图像)而后者应是一个矩形对象(只需使用self.image.get_rect()初始化它即可)。绘制Sprite对象时,将用到这两个属性。通过修改self.rect,可移动Sprite对象。
  • Surface对象包含方法convert,可用于创建使用不同颜色模式的副本。你无需关心细节,只需在调用convert时不提供任何参数即可。这将根据当前显示量身定制一个Surface对象,从而最大限度的提高其显示速度。
  • 颜色是使用RGB元组(红-绿-蓝,每个值的取值范围都是0~255)指定的,因此元组(255, 255, 255)表示白色。

要修改矩形(如这里的self.rect),可设置其属性(top、bottom、left、right、topleft、topright、bottomleft、bottomright、size、width、height、center、centerx、centery、midleft、midright、midtop和midbottom),也可调用诸如inflate、move等方法。有关这些属性和方法的描述,请参阅Pygame文档(http://pygame.org/docs/ref/rect.html)。

Pygame技术就位后,该稍微扩展和重构游戏的逻辑了。

今天先到这里,扩展和重构游戏的逻辑明天再讲,这篇文章有不懂的加一下群(群号:822163725,备注:小陈学Python,不备注可是会被拒绝的哦~!),在群里和大神交流交流!欢迎大家进群交流!

最后欢迎大家扫码关注

自制街机游戏(1):初次实现_子类_07