博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python学习笔记——Pygame之基础知识
阅读量:4218 次
发布时间:2019-05-26

本文共 21575 字,大约阅读时间需要 71 分钟。

Pygame基础知识

GUI vs. CLI

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

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

使用Pygame的Hello World程序源代码

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

# coding:utf-8import pygame, sysfrom pygame.locals import *pygame.init()DISPLAYSURF = pygame.display.set_mode((400, 300))pygame.display.set_caption('Hello World!')while True:    for event in pygame.event.get():        if event.type == QUIT:            pygame.quit()            sys.exit()                pygame.display.update()

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

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

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

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

建立一个Pygame程序

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

import pygame, sys

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

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

from pygame.locals 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更为详细地介绍我们想要这么做的原因)。

pygame.init()

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

DISPLAYSURF = pygame.display.set_mode((400, 300))

第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的变量中。

pygame.display.set_caption('Hello World!')

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

游戏循环和游戏状态

while True:    for event in pygame.event.get():

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

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

1.处理事件。

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

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

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

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

pygame.event.Event对象

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

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

while True:    for event in pygame.event.get():

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

QUIT事件和pygame.quit()函数

if event.type == QUIT:            pygame.quit()            sys.exit()

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行开始执行。

pygame.display.update()

第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所示的样子。

我们可以使用一个笛卡尔坐标系统(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 withPython》一书的第12章详细地介绍了笛卡尔坐标)。

关于函数、方法、构造函数和模块中的函数(及其差别)的一些提示

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

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

foo()

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

dukie.foo()

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

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

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

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

import whammy fizzy)egg=Wombat()egg. bluhbluh()whammy. spam()

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

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

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

Surface对象和窗口

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),我们将在本章后面介绍它。

颜色

光线有3种主要的颜色:红色、绿色和蓝色(红色、蓝色和黄色是绘画和颜料的主要颜色,但是计算机显示器使用光,而不是颜料)。通过将这3种颜色的不同的量组合起来,可以形成任何其他的颜色。在Pygame中,我们使用3个整数的元组来表示颜色。元组中的

第1个值,表示颜色中有多少红色。为0的整数值表示该颜色中没有红色,而255表示该颜色中的红色达到最大值。第2个值表示绿色,而第3个值表示蓝色。这些用来表示一种颜色的3个整数的元组,通常称为RGB值(RGB value)。

由于我们可以针对3种主要的颜色使用0~255的任何组合,这就意味着Pygame可以绘制16777 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值。

颜色的透明度

当你通过一个带有红色色调的玻璃窗口看过去,其背后的所有颜色都会增加一个红色的阴影。你可以通过给颜色值添加第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对象。

anotherSurface=DISPLAYSURF.convert_alpha()

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

pygame.Color对象

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

>>>import pygame>>>pygame. Color(255,0,0)(255,0,0,255)>>>myColor=pygame. Color(255,0,0,128)>>>myColor==(255,0,0,128)True>>>

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

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

Rect对象

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

1.左上角的X 坐标。

2.左上角的Y 坐标。
3.矩形的宽度(以像素为单位)。
4.矩形的高度(以像素为单位)。

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

>>>import pygame>>>spamRect=pygame. Rect(10,20,200,300)>>>spamRect==(10,20,200,300)True

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

>>>spamRect.right210

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

>>>spamRect.right=350>>>spamRect.left150

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

基本的绘制函数

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

# -*- coding: utf-8 -*-import pygame, syspygame.init()DISPLAYSURF = pygame.display.set_mode((500, 400), 0, 32)pygame.display.set_caption("Drawing")BLACK = (0, 0, 0)WHITE = (255, 255, 255)RED = (255, 0, 0)GREEN = (0, 255, 0)BLUE = (0, 0, 255)DISPLAYSURF.fill(WHITE)pygame.draw.polygon(DISPLAYSURF, GREEN, ((146, 0), (291, 106), (236, 277), (56, 277), (0, 106)))pygame.draw.line(DISPLAYSURF,BLUE,(60,60),(120,60),4)pygame.draw.line(DISPLAYSURF,BLUE,(120,60),(60,120))pygame.draw.line(DISPLAYSURF,BLUE,(60,120),(120,120),4)pygame.draw.circle(DISPLAYSURF,BLUE,(300,50),20,0)pygame.draw.ellipse(DISPLAYSURF,RED,(300,250,40,80),1)pygame.draw.rect(DISPLAYSURF,RED,(200,150,100,50))pixObj = pygame.PixelArray(DISPLAYSURF)pixObj[480][380] = BLACKpixObj[482][382] = BLACKpixObj[484][384] = BLACKpixObj[486][386] = BLACKpixObj[488][388] = BLACKdel pixObjwhile True:    for event in pygame.event.get():        if event.type == pygame.locals.QUIT:            pygame.quit()            sys.exit()    pygame.display.update()

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

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

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

  • fill(color) ——fill()方法并不是一个函数,而是pygame.Surface对象的一个方法。它将会使用传递给color参数的任何颜色来填充整个Surface对象。
  • pygame.draw.polygon(surface, color, pointlist, width) ——多边形是由多个扁平的边所组成的形状。Surface和color参数告诉函数,将多边形绘制到哪一个Surface上,以及用什么颜色绘制。

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

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

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

  • pygame.draw.line(surface, color, start_point, end_point, width) ——这个函数在start_point和end_point参数之间绘制一条直线。
  • pygame.draw.lines(surface, color, closed, pointlist, width) ——这个函数绘制了从一个点到下一个点的一系列的线条,这和pygame.draw.polygon()函数非常相似。唯一的区别在于如果你给closed参数传递了False,将不会有从pointlist中的最后一个点到第一个点的那条直线了。如果你传递了True,将会绘制从最后一个点到第一个点的直线。
  • pygame.draw.circle(surface, color, center_point, radius, width) ——该函数绘制一个圆。center_point参数指定了圆的圆心。传递给radius参数的整数,确定了圆的大小。

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

  • pygame.draw.ellipse(surface, color, bounding_rectangle, width) ——该函数绘制一个椭圆形(就像是一个压扁了或伸长了的圆)。该函数的参数都是常用的参数,但它们会告诉你绘制多大的椭圆,以及在何处绘制。必须指定椭圆的边界矩形。边界矩形(bounding rectangle)是围绕这一个形状所能绘制的最小的矩形。一个椭圆及其边界矩形的例子如图2-7所示。

  • pygame.draw.rect(surface, color, rectangle_tuple, width) ——该函数绘制一个矩形。rectangle_tuple是4个整数的一个元组(分别表示左上角的XY 坐标,以及宽度和高度),或者可以传递一个pygame.Rect对象来替代。如果rectangle_ tuple的宽度和高度的大小相同,那就会绘制一个正方形。

pygame.PixelArray对象

遗憾的是没有单个的函数可以调用来设置一个单个像素的颜色(除非你使用相同的起点和终点来调用pygame.draw.line()) 。在向一个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 belocked during blit”。

pygame.display.update()函数

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

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

动画

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

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

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

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

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

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

# -*- coding: utf-8 -*-"""Created on Fri Sep  7 18:48:15 2018@author: Administrator"""import pygame, syspygame.init()FPS = 30fspClock = pygame.time.Clock()DISPLAYSURF = pygame.display.set_mode((400, 300), 0, 32)pygame.display.set_caption("Animation")WHITE = (255, 255, 255)catImg = pygame.image.load("cat.png")catx = 10caty = 10direction = 'right'while True:    DISPLAYSURF.fill(WHITE)    if direction == 'right':        catx +=5        if catx == 280:            direction = 'down'    elif direction == 'down':        caty += 5        if caty ==220:            direction = 'left'    elif direction == 'left':        catx -= 5        if catx == 10:            direction == 'up'    elif direction == 'up':        caty -= 5        if caty == 10:            direction = 'right'                DISPLAYSURF.blit(catImg, (catx, caty))        for event in pygame.event.get():        if event.type == pygame.locals.QUIT:            pygame.quit()            sys.exit()                pygame.display.update()    fspClock.tick(FPS)

看一下,动画的猫实现了。

帧速率和pygame.time.Clock对象

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

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

fspClock = pygame.time.Clock()

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

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

fspClock.tick(FPS)

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

用pygame.image.load()和blit()绘制图像

如果你想要在屏幕上绘制简单的形状,绘制函数已经很好用了,但是,很多游戏都有图像(也叫作精灵,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'topen cat.png”,那么,在运行程序之前,请确保cat.png文件和catanimation.py位于同一文件夹之中。

DISPLAYSURF.blit(catImg, (catx, caty))

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

如果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所示。

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

# -*- coding: utf-8 -*-"""Created on Fri Sep  7 19:16:36 2018@author: Administrator"""import pygame, syspygame.init()DISPLAYSURF = pygame.display.set_mode((400, 300))pygame.display.set_caption("Hello World")WHITE = (255, 255, 255)GREEN = (0, 255, 0)BLUE = (0, 0, 255)fontObj = pygame.font.Font('freesansbold.ttf', 32)textSurfaceObj = fontObj.render("Hello World!", True, GREEN, BLUE)textRectObj = textSurfaceObj.get_rect()textRectObj.center = (200, 150)while True:    DISPLAYSURF.fill(WHITE)    DISPLAYSURF.blit(textSurfaceObj, textRectObj)        for event in pygame.event.get():        if event.type == pygame.locals.QUIT:            pygame.quit()            sys.exit()                pygame.display.update()

让文本显示到屏幕上,一共有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所示。

要对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()方法没有参数。如下是一些示例代码。

soundObj=pygame. mixer. Sound(' beeps. wav')soundObj. play()import time time. sleep(1)# wait and let the sound play for 1 second soundObj. 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()函数。该函数没有参数。

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

# Loading and playing a sound effect: soundObj=pygame. mixer. Sound(' beepingsound. wav')soundObj. play()# Loading and playing background music: pygame. mixer. music.1oad(backgroundmusic. mp3')pygame. mixer. music. play(-1,0.0)#... some more of your code goes here...pygame. mixer. music. stop()

 

你可能感兴趣的文章
HTML条件注释
查看>>
内核态与用户态
查看>>
趣链 BitXHub跨链平台 (4)跨链网关“初介绍”
查看>>
C++ 字符串string操作
查看>>
MySQL必知必会 -- 了解SQL和MySQL
查看>>
MySQL必知必会 -- 数据检索
查看>>
MySQL必知必会 -- 排序检索数据 ORDER BY
查看>>
POJ 3087 解题报告
查看>>
POJ 2536 解题报告
查看>>
POJ 1154 解题报告
查看>>
POJ 1661 解题报告
查看>>
POJ 1101 解题报告
查看>>
ACM POJ catalogues[转载]
查看>>
常见的排序算法
查看>>
hdu 3460 Ancient Printer(trie tree)
查看>>
KMP
查看>>
poj 3863Business Center
查看>>
Android编译系统简要介绍和学习计划
查看>>
Android编译系统环境初始化过程分析
查看>>
user2eng 笔记
查看>>