第5章

“小猫顶球”游戏





前面章节编写的游戏主要使用控制台和玩家进行交互,无论从游戏界面还是游戏操作来讲,都不是很友好。从本章开始,将在Python中使用Pygame来编写带图形界面的游戏。
本章介绍的小猫顶球的游戏虽然较为简单,但读者将从这个游戏中掌握Pygame模块的基础用法,从而为后续章节复杂游戏的设计打下良好基础。


2min


5.1“小猫顶球”游戏运行示例
运行本书附带的catBall游戏工程后,会出现如图51所示的界面。
在图51所示的界面中,玩家可通过键盘上的左和右方向键控制小猫左右移动,以此来顶从空中落下的球,小猫顶到球后会发出“咚”的声音,同时分数增加,如果小猫没有顶到球,则让球落地,游戏结束,弹出如图52所示的游戏结束界面。



图51“小猫顶球”游戏开始界面






图52“小猫顶球”游戏结束


在游戏结束界面显示玩家已经获得的分数,同时提示玩家按Space键(键盘上的空格)开始新游戏,如果单击右上角的“×”按钮,则游戏结束。根据上述游戏过程,可画出如图53所示的小猫顶球的游戏流程图。



图53“小猫顶球”游戏流程图


5.2Pygame模块简介
“小猫顶球”游戏采用了Pygame模块进行编程。Pygame是一个免费并且开源的编程语言库,其可用于2D游戏制作,包含对图像、声音、视频、事件、碰撞等支持,到现在Pygame已经有了20余年的发展历史。因Pygame建立在SDL(Simple DirectMedia Layer)的基础上,SDL是一套跨平台的多媒体开发库,底层用C语言实现,故Pygame的性能非常优越。
Pygame模块在游戏开发上的易用性和跨平台的特性,使开发者不用被底层语言、游戏性能和所运行的操作系统平台所束缚,从而可以在游戏功能和逻辑上下更多功夫。
Pygame模块的开发和支持者众多,开发上碰到的大部分问题可以在官网上找到答案。当读者碰到模块使用上的问题时,可以登录http://www.pygame.org求得帮助,同时在官网上提供了各个游戏种类的示例代码,读者也可根据这些开源的游戏示例代码学习更多游戏编程的知识。
说了这么多Pygame的优点,读者可能已经迫不及待地想掌握其用法。接下来一起用Pygame来完成本章的“小猫顶球”游戏。
5.3“小猫顶球”游戏环境搭建
1. 创建“小猫顶球”游戏工程文件
在前面几章的游戏编程中,都是使用工程默认生成的main.py文件进行编码。虽然使用这个文件省事,但是不能从文件名中看出其要完成的功能,最好的办法就是文件根据功能的不同而有不同的名字。接下来一起看一下如何将新建的catBall里的main.py文件名修改为catBall.py。
运行PyCharm后单击File菜单里的New Project按钮创建catBall工程,如图54所示。



图54创建catBall工程


创建catBall工程后,在main.py文件上右击并单击Refactor按钮,在弹出的菜单中单击Rename按钮,如图55所示。




图55更改main.py文件


在弹出的对话框里将main.py修改为catBall.py,单击Refactor按钮,如图56所示。
“小猫顶球”游戏需要小猫和球的素材,本书的附带资源已经提供了此素材。打开本章的附带资源后,复制cat.png、ball.gif和dong.wav文件,在catBall工程上右击,在弹出的菜单上单击Paste按钮,如图57所示。
至此,“小猫顶球”游戏的工程便建立完毕,读者应把catBall.py文件里的内容清空,从而为后续写入游戏代码做好准备。



图56修改文件名称




图57将资源文件粘贴到catBall


2. 导入Pygame模块
Pygame模块为外置模块,在使用前必须进行导入。在PyCharm里导入Pygame模块,既可以使用Terminal方式导入,也可以在图形界面下进行导入。
1) Terminal导入
单击PyCharm下方的Terminal按钮,在弹出的窗口里输入命令,如图58所示,命令如下: 


pip3 install pygame






图58命令行导入Pygame


输入命令并按Enter键执行后,pip3包管理器会自动在Python仓库里查找最新版本的Pygame安装包,并将其安装在当前的工程环境中。
2) 图形界面导入
图形界面下安装Pygame要稍微复杂。运行PyCharm后,单击File菜单下的Settings按钮,在弹出的窗口里打开Project:catBall下拉列表后,单击Python Interpreter选项,在右侧的窗口里单击“+”按钮,如图59所示。



图59打开Pygame安装窗口


单击“+”后,在弹出的窗口里输入pygame,单击Install Package按钮,如图510所示。



图510查找pygame并安装


5.4图形界面初始化
Pygame图形界面游戏编程非常类似于画家在一块儿画布上进行画图创作,作为一个“画家”首先需要做的就是掌握画布的创建方法。接下来一起看一下如何使用Pygame创建一个白色的画布,从而为后续“创作”打下良好基础。
5.4.1无交互的图形界面创建
使用PyCharm打开5.3节创建好的catBall工程,双击catBall.py文件,输入代码如下: 


#第5章/catBall/catBall.py

import pygame

pygame.init()

SIZE = WIDTH, HEIGHT = (800, 600)

screen = pygame.display.set_mode(SIZE)

pygame.display.set_caption('小猫顶球')

WHITE = (255, 255, 255)

screen.fill(WHITE)

while True:

pygame.display.update()


运行上述的代码,神奇的事情发生了,短短9行代码竟然运行出如图511所示的完整图形界面,而且这个图形界面无论在Windows上还是在Linux上都具有相同的显示效果!这就是Pygame强大之处,一次编码,多平台使用。



图511无交互的图形界面


上述代码虽然简单,但是Pygame游戏编程基本上使用这种编程方法。接下来一起看一下代码都代表什么意义。
在代码的第2行使用了pygame.init()函数,此函数用于对Pygame游戏编程进行初始化。调用此函数时,Pygame在初始化游戏编程时可能会用到的声卡、显卡、游戏手柄等硬件的调用接口,如果硬件有问题,会以元组的形式返回相关的错误代码。
screen=pygame.display.set_mode(SIZE)语句创建出了一个大小为800×600像素的画布,screen代表创建后返回的画布对象,以后需要在画布上画其他图形时,可以通过得到的screen画布对象进行创作。
pygame.display.set_caption('小猫顶球')语句用于将画布标题设置为小猫顶球。pygame.display还有很多和窗口设置相关的属性,在后续章节中将进一步说明。
screen.fill(WHITE)语句将当前画布设置为WHITE颜色填充,其中WHITE变量由RGB三原色搭配而得,Pygame将RGB三原色中每种原色的数值范围都设置为0~255,通过3个不同的数值将搭配出不同颜色。例如白色为(255,255,255),黑色为(0,0,0)。
程序的最后两行为一个while无限循环,在无限循环里不停地调用pygame.display.update()语句。while无限循环是Pygame保持窗口一直可以在屏幕上显示的关键,如果不使用无限循环,则创建的画布将一闪而过。pygame.display.update()语句用来更新画布上所有的图像,本节的例程代码不涉及画布上的图像创作,后续章节读者将看到此语句的具体使用。
5.4.2画布相关属性
在5.4.1节创建了一个大小为800×600像素的空白白色画布,并且将画布的标题设置为“小猫顶球”。为了在画布上创作出更多佳作,下面看一下和画布相关的一些属性。


图512Pygame坐标体系

1. 画布坐标系
画布大小为800×600像素,什么位置是其开始计算像素的(0,0)点?是和数学中常用的笛卡儿坐标系一样,即画布中心是(0,0)点吗?或者其他坐标体系?
Pygame使用了一种新的坐标体系,在这种坐标体系下,坐标原点(0,0)是画布的左上角,如图512所示。
从图512可知,如果画布大小为800×600像素,则从画布左上角的坐标原点水平向右,x坐标递增,最大值为800,从画布坐标原点垂直向下,y坐标递增,最大值为600,画布右下角的坐标值为(800,600)。
2. 画布属性设置
在5.4.1节使用pygame.display.set_mode(SIZE)语句将画布大小设置为SIZE大小,即800×600像素,画布还有一些其他的属性可进行设置,其属性设置如下。

pygame.display.set_mode(resolution=(0,0),flags=0,depth=0)

(1) resolution: 一个二元组参数,表示宽和高。如果不传递这个参数或使用默认值(0,0),则将设置成和当前计算机屏幕一样的分辨率。
(2) flags: 指定要显示的类型,共有如表51所示的几个属性。当要使用多个属性时可以使用“|”进行连接。例如pygame.FULLSCREEN | pygame.HWSURFACE表示同时使用了两个属性。



表51flags常见参数



参数参 数 描 述

pygame.FULLSCREEN画布全屏显示
pygame.DOUBLEBUF双缓冲模式,在HWSURFACE或OPENGL下使用
pygame.HWSURFACE硬件加速,只有全屏下才可以使用
pygame.OPENGL使用OPENGL显示接口
pygame.RESIZABLE创建的画布可以缩放
pygame.NOFRAME画布没有边框,没有右上角最小化和关闭等按钮

(3) depth: 表示要使用的颜色深度。通常情况下不要传递depth参数,Pygame会自动根据当前操作系统选择最好和最快的颜色深度。
5.5认识小猫等Surface对象
空白的画布创建好后,便可以在画布上进行艺术创作。本章介绍的是小猫顶球游戏,该如何加载小猫和球,这就必须认识一下Pygame里的Surface对象。
Pygame认为创建好的画布是Surface对象,画布上所描绘的任何图形都是Surface对象,从外部加载的图形资源也是Surface对象。可以说,Surface对象是Pygame编程的灵魂,掌握好Surface对象对于学习Pygame游戏编程会事半功倍。
1. 认识Surface对象
Surface对象是Pygame模块的一个子集,用于表示任何一张图像,其具有固定的分辨率和像素格式,只需指定尺寸,就能通过pygame.Surface()方法创建一个新的图像Surface。创建好的Surface对象可以做很多事情,例如在其上绘制图形、写入文字、填充颜色等。Surface对象支持不同对象间的叠加显示,本章介绍的小猫顶球游戏就是利用Surface对象这个特性来完成的。
Surface对象极其重要,以致Pygame为其设计了多达50余个属性和方法,覆盖了Surface对象操作的方方面面。在后续章节中,读者将接触到Surface对象的各种用法。
Surface对象创建后是一个矩形区域,其默认填充颜色为黑色,如果没有指定其他参数,则将创建出最适合当前显示分辨率的Surface对象。
2. Surface对象创建方法
在5.4.1节使用screen=pygame.display.set_mode(SIZE)语句得到了一个800×600像素的Surface对象screen,screen是一个特殊的Surface对象,它是游戏编程的主画布,是后续Surface对象显示的前提。通常来讲,使用Pygame游戏编程都要使用这一语句来创建主画布,在后续章节的游戏设计中,读者会经常看到此代码的出现。
大多数情况下,游戏编程中涉及的各种图形资源都不是由CPU或GPU即时绘制出来的,往往通过加载美工已经绘制好的图片资源来完成图形资源的显示,Pygame游戏编程也不例外。Pygame通过pygame.image.load()方法来加载图片资源,加载后的图片资源将变为Surface对象。加载cat.png图片并赋值给catSurface对象,代码如下: 


catSurface=pygame.image.load("cat.png")

上述代码对cat.png图片进行了加载,需要说明的是,Pygame支持大多数图片格式,但是图片格式如果过于小众,则存在加载不出来的情况。Pygame支持的图片格式有JPG、PNG、GIF、PCX、TGA、TIF、LBM、PBM、PGM、PPM和XPM。
读者在编程中可能碰到这样的问题,图片资源的分辨率太大了,在Pygame的主画布里如果按照1∶1加载,则图片会过大,不符合编程中需要的图片大小。最容易解决这个问题的办法是让美工按照需求调整图片的分辨率,但图片资源往往要在各个游戏场景里复用,其分辨率也不尽相同,让美工按照各个场景需求调整图片分辨率似乎不大现实。幸运的是,Pygame提供了transform方法来满足游戏场景里缩放图片资源的Surface对象,从而解决图片分辨率的问题。将小猫Surface对象缩小成94×153像素的Surface对象,并赋值给catSurface,代码如下: 


catSurface=pygame.image.load("cat.png")

catSurface=pygame.transform.scale(catSurface,(94,153))


3. Surface对象属性获取
Pygame游戏编程的实质就是对多个Surface对象的灵活运用。对于各个Surface对象,游戏编程中经常需要获取其位置、高度等信息,从而进行控制,为此,Pygame提供了众多的Surface对象属性获取方法来满足游戏开发者的需求。Surface对象常见属性如表52所示。


表52Surface对象常见属性



方法方 法 描 述

Surface.get_width()获取Surface宽度,单位为像素
Surface.get_height()获取Surface高度,单位为像素
Surface.get_size()获取Surface的(width,height)尺寸
Surface.get_rect()获取Surface的Rect矩形对象,Rect矩形对象的值为(0,0,width,height)

加载cat.png图片,并使用Surface的属性及方法进行输出,代码如下: 


#第5章/catBall/catShow.py

import pygame

pygame.init()

SIZE = WIDTH, HEIGHT = (800, 600)

screen = pygame.display.set_mode(SIZE)

pygame.display.set_caption('小猫顶球')

WHITE = (255, 255, 255)

catSurface=pygame.image.load("cat.png")

print(catSurface.get_width())

print(catSurface.get_height())

print(catSurface.get_size())

print(catSurface.get_rect())

catSurface=pygame.transform.scale(catSurface,(100,200))

print(catSurface.get_width())

print(catSurface.get_height())

print(catSurface.get_size())

print(catSurface.get_rect())

screen.fill(WHITE)

while True:

pygame.display.update()


上述代码的运行结果如图513所示。



图513Surface对象属性获取


从图513可知,直接用pygame.image.load()方法加载图片后,得到的是图片的原始宽度、高度、尺寸、Rect等属性。使用pygame.transform.scale()方法对Surface对象进行调整后,获取的就是调整后的各个属性。



4min


5.6显示小猫等Surface对象
在5.5节使用pygame.image.load()方法加载了小猫等图片资源,并使用pygame.transform.scale()方法对加载的图片资源根据需求进行了缩放。加载好的图片资源该如何显示到画布上,并且其在画布上的位置怎么确定?
在5.5节的最后使用Surface.get_rect()方法得到了Surface对象的Rect对象,Pygame使用Rect对象的位置来定位Surface的坐标位置。换句话说,如果改变Rect对象的位置,则代表Surface对象的图片位置也将随之跟着改变。Rect对象是图像显示的关键,下面看一下Rect对象的相关知识。
5.6.1创建Rect对象
Rect对象是一个四元组,其由4个数字来表示一个矩形区域,例如(100,100,200,300)表示的Rect对象如图514所示。


图514(100,100,200,300)Rect对象图示


从图514可知,在Rect对象的四元组中,前两个数字表示Rect对象的左上角坐标为(100,100),第3个数字表示Rect对象的宽度为300像素,第4个数字表示Rect对象的高度为200像素。Pygame还支持用户自己创建Rect对象,其语法如下: 

Rect(left,top,width,height)

(1) left: 从左上角坐标原点开始向右计算的x坐标。
(2) top: 从左上角坐标原点开始向下计算的y坐标。
(3) width: Rect对象的宽度。
(4) height: Rect对象的高度。
5.5节加载图片资源为Surface对象后,通过Surface对象的get_rect()方法得到图片的Rect对象,此Rect对象将继承图片的宽度和高度等信息,其四元组信息为(0,0,图片宽度,图片高度),如果改变Rect对象的位置信息,则图片的位置将随之改变。
5.6.2Rect对象位置属性
从5.6.1节可知,改变Rect对象的位置后,得到Rect对象的Surface对象的位置也随之改变,那么Rect对象都有哪些位置属性?其位置属性如图515所示。



图515Rect对象位置属性



图515为加载图片资源产生的Surface对象在更改位置后得到的Rect对象。虽然Rect对象有很多位置属性,但掌握了如图515所示的Rect对象的位置属性已经足够读者进行游戏编程,其每个位置的含义如下。
(1) top: Rect对象的左上角y坐标,也可以使用y代替。
(2) left: Rect对象的左上角x坐标,也可以使用x代替。
(3) width: Rect对象的宽度,也可以使用w代替。
(4) height: Rect对象的高度,也可以使用h代替。
(5) right: Rect对象右下角x坐标。
(6) bottom: Rect对象右下角y坐标。
(7) centerx: Rect对象中心点的x坐标。
(8) centery: Rect对象中心点的y坐标。
Rect对象的属性在代码中都可以改变。用PyCharm打开catBall工程后,新建catSleep.py文件,加载catSleep.jpg图片,并将其Rect对象的top移动到200像素,并且将left移动到300像素,代码如下: 


#第5章/catBall/catSleep.py

import pygame

pygame.init()

SIZE = WIDTH, HEIGHT = (800, 600)

screen = pygame.display.set_mode(SIZE)

pygame.display.set_caption('小猫顶球')

WHITE = (255, 255, 255)

catSleepSurface=pygame.image.load("catSleep.jpg")

catSleepRect=catSleepSurface.get_rect()

catSleepRect.top=200

catSleepRect.left=300



screen.fill(WHITE)

while True:

screen.blit(catSleepSurface,catSleepRect)

pygame.display.update()


上述代码的运行结果如图516所示。



图516更改位置后的Rect对象


读者在输入代码并运行时,可能已经发现,当修改了Rect对象属性值后,其他的属性也会随之联动。如果代码中修改了Rect对象的top和left,则此时centerx和centery的坐标会变成什么?读者可以编写代码加以验证。
5.6.3Rect对象进行移动
小猫顶球游戏中要控制小猫来顶球,如何让小猫和球运动起来?Rect对象提供了移动的方法,又从前述章节可知,小猫和球都是Surface对象,都有Rect对象属性,因此可以通过Rect对象的移动来使Surface对象移动,Rect对象的移动方法如表53所示。


表53Rect对象移动方法



方法方 法 描 述

move(x,y)向x和y坐标移动Rect对象。如果x为正值,则向右移动,如果x为负值,则向左移动; 如果y为正值,则向下移动,如果y为负值,则向上移动; x和y值必须为整数; 方法会返回一个新的Rect对象,原对象不变
move_ip(x,y)向x和y坐标移动Rect对象。如果x为正值,则向右移动,如果x为负值,则向左移动; 如果y为正值,则向下移动,如果y为负值,则向上移动; x和y值必须为整数

万事开头难,先编写代码尝试着让一个Surface对象移动起来吧。
接下来将通过Rect对象的move()方法实现一个在屏幕上跳动的小球。在catBall工程里新建movingBall.py文件,输入的代码如下: 


#第5章/catBall/movingBall.py

import pygame,sys

pygame.init()

SIZE = WIDTH, HEIGHT = (800, 600)

screen = pygame.display.set_mode(SIZE)

pygame.display.set_caption('跳动的小球')

WHITE = (255, 255, 255)

x,y=3,3

ballSurface=pygame.image.load("ball.gif")

ballRect=ballSurface.get_rect()

screen.fill(WHITE)

tick = pygame.time.Clock()#创建时钟对象(可以控制游戏的循环频率)

while True:

tick.tick(60)#每秒循环60次

ballRect = ballRect.move(x, y)

screen.fill(WHITE)

if ballRect.left >screen.get_width()-ballRect.width or ballRect.left<0:

x=-x

if ballRect.top<0 or ballRect.top>screen.get_height()-ballRect.height:

y=-y

screen.blit(ballSurface,ballRect)

pygame.display.update()


运行上边的代码后,会出现如图517所示的场景: 小球在屏幕里跳动,碰到边界后自动改变跳动方向。



图517跳动的小球


代码中将x和y的值设为3,每次小球的Surface对象调用move(x,y)方法时,小球将向x坐标和y坐标移动3像素。
pygame.time.Clock()方法可以帮助程序确定要以最大多少帧的速率运行,这样在游戏的每一次while循环后会设置一个暂停,以防程序运行过快。因为计算机的配置不同,所以编程的时候需要使用这种方法来让计算机以一个固定的速度运行。tick.tick(60)方法设置计算机程序每秒运行60次,这个值设置得越高,程序运行得越快,反之则越慢。
if ballRect.left >screen.get_width()ballRect.width or ballRect.left<0语句对小球的Rect对象的left值进行判断,如果小球的Rect对象的left值大于画布右边界或小于左边界,则改变小球的x轴移动方向。
if ballRect.top<0 or ballRect.top>screen.get_height()ballRect.height语句对小球的Rect对象的top值进行判断,如果小球的Rect对象的top值大于画布下边界或小于上边界,则改变小球的y轴移动方向。
screen.blit(ballSurface,ballRect)语句为Surface上的绘制语句。screen为游戏的主画布,本行代码将小球的Surface对象以小球的Rect位置为基准绘制到screen主画布上。
pygame.display.update()语句为更新屏幕语句,每次while循环都必须运行此语句,否则屏幕上将不会显示任何Surface对象。
读者可能好奇,小球为什么能移动?它的移动机制到底是什么?其实小球的移动非常类似于读者看到的动画片。大家知道,动画片通过一秒播放30张或以上的连续的图片来让眼睛误认为是动画,从而形成了移动动画效果。Pygame不断地把Surface对象以非常小的坐标改变绘制到屏幕上,当绘制的画面频率大于30画面/s时,人的眼睛就认为Surface对象进行了移动。需要注意的是,每次绘制前需要把画布用底色填充,从而覆盖上次的绘制结果。
每次绘制前,如果不用底色填充画布会发生什么情况?读者可以尝试将while循环里的screen.fill(WHITE)语句注释掉,其运行结果如图518所示。



图518每次循环时不填充画布的结果




3min


5.7键盘和鼠标事件响应
读者可能已经发现,本章前面章节的游戏代码运行后无法关闭,必须通过停止程序任务的方式结束游戏,这个问题该如何解决?Pygame提供了事件处理模块来帮助用户对游戏程序进行控制,Pygame提供的事件处理模块为pygame.event、pygame.key、pygame.mouse。
1. pygame.event
操作系统采取消息驱动机制来对键盘输入、鼠标移动等进行响应,每当按下键盘和移动鼠标时都会产生响应的Event事件,操作系统会自动将产生的Event事件存储到消息队列中。
Pygame使用pygame.event.get()方法从消息队列中获取操作系统的event事件,得到event事件后,可根据event事件的类型而对键盘、鼠标等进行响应。pygame.event.get()方法得到的event事件共有两类属性,分别是type和dict。需要注意的是,消息队列严重依赖pygame.display模块,假如pygame.display模块没有正确初始化,消息队列则可能工作不正常。消息队列最多只能容纳128个event事件,当消息队列容量到达128像素后,新的事件将不会得到存储。
常用的event事件的type和dict属性如表54所示,在游戏编程中可以通过type和dict值来处理event事件。


表54常用的event事件的type和dict属性



type属性dict属性


QUITNone
ACTIVEEVENTgain、state
KEYDOWNkey、mod、unicode、scancode
KEYUPkey、mod
MOUSEMOTTONpos、rel、buttons
MOUSEBUTTONUPpos、button
MOUSEBUTTONDOWNpos、button
VIDEORESIZEsize、w、h
VIDEOEXPOSENone
USEREVENTcode

表54对常用的event事件的type和dict属性进行了描述,有了这个表格,就可以响应玩家的程序关闭事件了。
以下代码将保证用户单击主画布右上方的“×”按钮时,程序会终止运行,代码如下: 


#第5章/catBall/catBall.py

import pygame

import sys

pygame.init()

SIZE = WIDTH, HEIGHT = (800, 600)

screen = pygame.display.set_mode(SIZE)

pygame.display.set_caption('小猫顶球')

WHITE = (255, 255, 255)

screen.fill(WHITE)

while True:

for event in pygame.event.get():

if event.type == pygame.QUIT:

sys.exit()

pygame.display.update()


在上述代码中引入了Python的sys模块,sys模块有很多重要的方法,读者用到的时候可以去查阅相关文献,此处使用sys.exit()方法来退出程序。在程序中使用for循环得到每条消息队列的事件也是以后游戏编程中常用的一种方法,读者需掌握。
2. pygame.key模块
pygame.key模块是Pygame关于键盘操作的响应模块,其常见的key事件和含义如表55所示。


表55常见的key事件和含义



事件含义



get_focused()如果有键盘输入,则事件返回值为True
get_pressed()得到键盘按键的所有状态
get_modes()检测是否有组合键被按下
set_mods()将某些组合键设置为被按下状态
set_repeat()控制重复响应持续按下按键的时间
get_repeat()得到重复响应按键的参数

小猫顶球游戏要使用左右方向键控制小猫的左右移动,有了pygame.key模块知识就可以完成控制小猫左右移动的编码,代码如下: 


import pygame

import sys

pygame.init()

SIZE = WIDTH, HEIGHT = (800, 600)

screen = pygame.display.set_mode(SIZE)

pygame.display.set_caption('小猫顶球')

#加载小猫图片

catSurface=pygame.image.load("cat.png")

#将小猫图片缩小为94像素×153像素

catSurface=pygame.transform.scale(catSurface,(94,153))

catRect=catSurface.get_rect()

#将小猫的Rect对象放置到画布下方的中心

catRect.left=WIDTH//2-catRect.width//2

catRect.top=HEIGHT-catRect.height

WHITE = (255, 255, 255)

screen.fill(WHITE)

tick = pygame.time.Clock()

while True:

tick.tick(60)

for event in pygame.event.get():

if event.type == pygame.QUIT:

sys.exit()

screen.fill(WHITE)

keyPressed=pygame.key.get_pressed()

#小猫向左移动,当移到最左边界时,不能继续移动

if keyPressed[pygame.K_LEFT]:

catRect.left-=2



if catRect.left<=0:

catRect.left=0

#小猫向右移动,当移到最右边界时,不能继续移动

if keyPressed[pygame.K_RIGHT]:

catRect.left+=2

if catRect.left>=WIDTH-catRect.width:

catRect.left=WIDTH-catRect.width

screen.blit(catSurface,catRect)

pygame.display.update()


运行上述代码,结果如图519所示,当玩家按键盘的左右方向键时,小猫也将左右移动。



图519可左右移动的小猫


图像加载生成的Surface对象默认的Rect位置在画布的左上角。但根据游戏设计,Surface对象的初始位置都应该在一个特定的地方,例如小猫顶球游戏就要求小猫的初始位置在画布下方的中心。游戏编码时可以通过Surface对象的Rect位置来初始化Surface的位置,例如代码中使用catRect.left=WIDTH//2catRect.width//2和catRect.top=HEIGHTcatRect.height来使位置下方居中。
代码中使用keyPressed[pygame.K_LEFT]和keyPressed[pygame.K_RIGHT]来判断按键是否是左和右方向键,pygame.K_LEFT和pygame.K_RIGHT是Pygame里提供的左和右方向键的键定义,Pygame给键盘的每个键都提供了键定义,游戏编码时可以通过键定义来判断所按的键。常见的键定义如表56所示。


表56常见的键定义



键定义含义键定义含义


K_BACKSPACE空格K_Aa键
K_TABTab键K_F1F1键
K_RETURN回车键K_NUMLOCKNum Lock键
K_0数字0键K_LCTRL左Ctrl键
K_LEFT方向左键K_RCTRL右Ctrl键
K_RIGHT方向右键K_POWERPower键

当小猫左右移动时,需要对画布的边界情况进行判断,防止小猫不停地移动,以至移到画布外部。
5.8小猫和球类碰撞检测
小猫顶球游戏必须对小猫和球的碰撞加以检测,一种可行的方法是当小猫的Rect对象和球的Rect对象有重叠的时候,认为小猫顶到了球。使用这种方法需要时时对两个Rect对象的矩形区域进行位置判断,如果加入更多的球,则数学计算将较为烦琐。幸运的是,Pygame提供了更好的机制进行碰撞检测,这就是接下来要学习的Sprite。
5.8.1类与类的继承
Sprite又称为“精灵”,指游戏中经常出现的可视化对象,小猫顶球游戏中的小猫和球都可称为Sprite对象,Pygame对Sprite类的碰撞检测提供了调用方法,那么什么是类?
类是对具有共同特性对象的一种抽象。例如,小猫顶球游戏目前只顶一个球,后期如果想顶多个球,则在采用传统方法时,每个球都要进行单独定义,代码将变得冗余不堪。如果找出各个要顶的球的共有特征,并将其抽象为球这个类,则以后想生成更多球时,可直接生成球这个类的对象,代码将简洁且易维护。
类有很多抽象术语,了解这些抽象术语可以帮助读者更好地掌握类的用法。
(1) 类属性: 类中所定义的属性,有共有的类属性和私有的类属性两种。
(2) 构造函数: 生成类对象时,负责初始化类的各个参数。
(3) 方法: 类的方法就是类中定义的函数,可以完成相应的功能,如改变类的状态,进行某个计算等。
(4) 继承: 一个派生类继承一个或多个基类,派生类可以继承父类的属性和方法。
(5) 实例化: 创建一个类的实例对象。
(6) 封装: 将类变成一个黑匣子,外部无法看清内部的工作细节。
1. 类的定义和实例化对象
类定义的语法格式如下: 


class 类名:

类体


上述语法格式中class是关键字,定义类必须以class开头,类名为类的名字,类名也必须符合Python变量命名规则,一般来讲类名的首字母用大写。当然,读者如果喜欢用中文命名类名也可以,Python完美支持中文类名。
有了类名后,可以通过“对象名=类名([参数列表])”的语法来实例化类的对象。
以下代码将创建Ball类,并实例化两个Ball对象,代码如下: 


#第5章/catBall/classBall.py

class Ball:

def __init__(self,pos,color):

self.pos=pos



self.color=color

def print(self):

print(self.color+"球的位置在"+str(self.pos))



redBall=Ball((30,30),"red")

blueBall=Ball((100,100),"blue")

redBall.print()

blueBall.print()



图520Ball类与其对象

运行上述代码,结果如图520所示。
在上述代码中,首先定义了一个Ball类,类中有一个构造函数和一个类方法,其中构造函数负责Ball类中pos和color两个变量的初始化,类方法负责显示类的属性。
类的构造函数必须以__init__作为开始,括号里的self不可省略,其表明此函数属于类。代码中类的pos属性和color属性需要在类构造时初始化,需要注意的是,所有类中的变量必须以self作为前缀。
类定义完成后,代码通过redBall=Ball((30,30),"red")和blueBall=Ball((100,100),"blue")生成两个Ball对象,每个Ball对象都有自己的pos和color属性,这一点从两个类对象的print()方法调用也可以看出。
读者需要牢记,类方法中的参数里必须有self关键字,这也是和前边所学的函数的最大区别。
并不是所有类都必须有构造函数和方法,没有构造函数的类,生成对象时也不必带参数。以下代码就是一个不含有构造函数的类: 


class Ball:

def print(self):

print("我是一个小球")

redBall=Ball()

redBall.print()


2. 类的继承
类的重要的特点就是继承。通过类的继承,开发者可以在已有类的基础上加上自己需要的部分,从而使类的使用更加灵活。在继承关系中,已有的类称为基类,新设计的类称为派生类。派生类可以继承父类的公有属性和方法,派生类可以同时继承多个基类。
设计Animal基类和Cat子类,并实例化Cat对象,代码如下: 


#第5章/catBall/animal.py

class Animal:

def __init__(self):

self.head=True #具有大脑

def speak(self):

print("动物可以叫")



class Cat(Animal):

def speak(self):

print("猫咪喵喵叫")

def jump(self):

print("猫咪跳得高")



blackCat=Cat()

print(blackCat.head)

blackCat.speak()

blackCat.jump()



图521类继承运行结果

运行上述代码,结果如图521所示。


从运行结果可知,Cat类继承了Animal基类后,基类里的head变量被Cat类继承下来。基类里的speak()方法,如果Cat类有同名方法,则Cat类的同名方法将覆盖基类里的方法。Cat类除了继承基类的方法,也可以有自己的方法。
5.8.2小猫和球类
有了5.8.1节类的知识,可以创建小猫和球两个类并实现对象,代码如下: 


#第5章/catBall/catBall.py

#创建Cat类

class Cat(pygame.sprite.Sprite):

def __init__(self, image):

#图片加载

self.image = pygame.image.load(image)

#缩小图片

self.image = pygame.transform.scale(self.image, (94, 153))

#得到Rect对象

self.rect = self.image.get_rect()

self.rect.left = WIDTH //2 - self.rect.width //2

self.rect.top = HEIGHT - self.rect.height



#向左移动

def moveLeft(self):

self.rect.left -= 2

if self.rect.left <= 0:

self.rect.left = 0



#向右移动

def moveRight(self):

self.rect.left += 2

if self.rect.left >= WIDTH - self.rect.width:

self.rect.left = WIDTH - self.rect.width



#显示Cat



def display(self):

screen.blit(self.image, self.rect)





#创建Ball类

class Ball(pygame.sprite.Sprite):

def __init__(self, image):

self.image = pygame.image.load(image)

self.rect = self.image.get_rect()



def move(self, moveStep):

self.rect = self.rect.move(moveStep)



def display(self):

screen.blit(self.image, self.rect)





#实例化Cat和Ball的对象

boy = Cat('cat.png')

ball = Ball('ball.gif')


从代码可知,Cat类和Ball类都继承自Sprite,类都有image和rect变量,通过这两个个变量来存储图片的Surface和Surface产生的Rect对象,Cat类通过moveLeft()和moveRight()方法左右移动,Ball类通过move()方法进行移动,两个类都通过display()方法在画布上显示。
5.8.3使用碰撞函数进行碰撞检测
Cat类和Ball类都继承自Sprite类,Pygame对Spirte类有专门的碰撞检测函数,其函数为collide_rect(sprite1,sprite2),当sprite1和sprite2的Rect对象有重合时,函数的返回值为True,当无重合时,返回值为False。
根据上述知识,可以使用下边的代码对小猫和球进行碰撞检测,代码如下: 


b = pygame.sprite.collide_rect(boy, ball)


当b为True时,小猫顶到了球;当b为False时,小猫没有顶到球。
5.9信息显示和音效播放
小猫顶球游戏的一个重要的环节就是显示游戏分数,当一局游戏结束后,需提示玩家按照操作进行下一局的游戏,这就需要掌握Pygame显示文字的方法了。
从前边的小猫和球的显示可知,Pygame采用的都是Surface对象和Surface对象对应的Rect对象相结合的方法来显示图片,显示文字也没有什么更好的办法,也只能采取这样的方法。
5.9.1字体显示
1. 生成字体对象

Pygame提供了字体类pygame.font.Font,其构造函数有两个参数,第1个参数是字体文件名称,第2个参数为字体大小。每一台计算机所拥有的字体也不尽相同,那么该如何保证任一台计算机都可以正确地显示?使用Pygame提供的get_default_font()方法可以得到操作系统的默认字体。以下代码得到了ff字体对象,其中font_size由主程序传递,代码如下: 


ff = pygame.font.Font(pygame.font.get_default_font(), font_size)


2. 字体对象生成Surface
字体对象的render()方法可以生成Surface对象,其方法参数为

render(text,antialias,color,background=None)。

(1) text: 要显示的文字信息,仅支持一行文本。
(2) antialias: 是否打开抗锯齿功能,如果打开抗锯齿功能,则文字将更平滑。
(3) color: 文字颜色,支持RGB三元组。
(4) background: 背景颜色,如果为None,则文字背景将是透明的。
以下代码将通过render()方法生成Surface对象,其中text和font_color由主程序传递,代码如下: 


textSurface = ff.render(text, True, font_color)


3. Surface生成Rect并居中显示
有了Surface对象,生成Rect对象并居中显示就简单了,代码如下: 


textRect = textSurface.get_rect()

textRect.center = (xPos, yPos)


5.9.2字体显示函数
字体显示在小猫顶球游戏中要频繁地用到,可以将其封装成字体显示函数,其具体的代码如下: 


def draw_text(text, xPos, yPos, font_color, font_size):

""" 绘制文本,xPos和yPos为坐标,font_color为字体颜色,font_size为字体大小"""

ff = pygame.font.Font(pygame.font.get_default_font(), font_size)

textSurface = ff.render(text, True, font_color)

textRect = textSurface.get_rect()

textRect.center = (xPos, yPos)

screen.blit(textSurface, textRect)


5.9.3音效播放
当小猫顶到球时,播放出相应音效会大大提高游戏的趣味性,Pygame通过mixer模块来支持音效播放,其调用非常简单,代码如下: 


sound=pygame.mixer.Sound("dong.wav")

sound.play()


sound为Pygame产生的Sound类的对象,当需要播放dong.wav这个音效时,调用sound对象的play()方法即可。
5.10“小猫顶球”游戏主程序完善
至此,“小猫顶球”游戏各个主要功能都已经得到实现。接下来完成主程序,从而把各个功能串联起来,完整代码如下: 


#第5章/catBall/catBall.py

import pygame, sys

pygame.init()

SIZE = WIDTH, HEIGHT = (800, 600)

screen = pygame.display.set_mode(SIZE)

pygame.display.set_caption('小猫顶球')

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

RED=(255,0,0)

score=0

scoreFont=pygame.font.Font(pygame.font.get_default_font(), 20)

scoreDis=scoreFont.render(str(score),1,BLACK)

scorePos=[WIDTH//2,0]

gameOver=False





def draw_text(text, xPos, yPos, font_color, font_size):

""" 绘制文本,xPos和yPos为坐标,font_color为字体颜色,font_size为字体大小"""

ff = pygame.font.Font(pygame.font.get_default_font(), font_size)

textSurface = ff.render(text, True, font_color)

textRect = textSurface.get_rect()

textRect.center = (xPos, yPos)

screen.blit(textSurface, textRect)





class Cat():

def __init__(self, image):

self.image = pygame.image.load(image)

#缩放图片

self.image = pygame.transform.scale(self.image, (94, 153))

self.rect = self.image.get_rect()




self.rect.left = WIDTH //2 - self.rect.width //2

self.rect.top = HEIGHT - self.rect.height



def moveLeft(self):

self.rect.left -= 2

if self.rect.left <= 0:

self.rect.left = 0



def moveRight(self):

self.rect.left += 2

if self.rect.left >= WIDTH - self.rect.width:

self.rect.left = WIDTH - self.rect.width



def display(self):

screen.blit(self.image, self.rect)





class Ball():


def __init__(self, image):

self.image = pygame.image.load(image)

self.rect = self.image.get_rect()



def move(self, moveStep):

self.rect = self.rect.move(moveStep)



def display(self):

screen.blit(self.image, self.rect)





boy = Cat('cat.png')

ball = Ball('ball.gif')

sound=pygame.mixer.Sound("dong.wav")

moveStep = [2, 2]

angle = 0

tick = pygame.time.Clock()#创建时钟对象(可以控制游戏循环的频率)



while True:

tick.tick(120)#每秒循环120次

for event in pygame.event.get():

if event.type == pygame.QUIT:

sys.exit()

screen.fill(WHITE)

keyPressed = pygame.key.get_pressed()

if keyPressed[pygame.K_SPACE]:

gameOver=False

score=0

ball.rect.left,ball.rect.top=0,0

boy.rect.left=WIDTH //2 - boy.rect.width //2

boy.rect.top=HEIGHT - boy.rect.height



if not gameOver:

if keyPressed[pygame.K_LEFT]:

boy.moveLeft()

if keyPressed[pygame.K_RIGHT]:

boy.moveRight()

ball.move(moveStep)

if ball.rect.left <= 0 or ball.rect.right >= WIDTH:

moveStep[0] = -moveStep[0]

if ball.rect.top <= 0:

moveStep[1] = -moveStep[1]

if ball.rect.bottom >= HEIGHT:

gameOver=True

b = pygame.sprite.collide_rect(boy, ball)

if b:

sound.play()

moveStep[1] = -moveStep[1]

score += 10

boy.display()

ball.display()

draw_text(str(score), WIDTH //2, 15, BLACK, 25)

else:

draw_text("Your Score is:"+str(score),WIDTH//2,HEIGHT//2-150,BLACK,50)

draw_text("GAME OVER",WIDTH//2,HEIGHT//2,BLACK,100)

draw_text("Press Space Begin New Game",WIDTH//2,HEIGHT//2+150,RED,30)





pygame.display.update()


主程序中通过判断gameOver变量是否为真来判断游戏是否结束,当游戏结束的时候,监测用户的按键是否为空格键,如果是空格键,则游戏分数清零,小猫重新回到初始位置。
笔者将小猫顶到球的每次得分设置为10,读者可以通过更改score += 10语句设置每次顶球的分数。
5.11小结
本章主要介绍了小猫顶球游戏的具体实现,同时对本章涉及的Pygame里的图形界面初始化、Surface对象显示、键盘和鼠标事件响应、碰撞检测、声音显示、音效播放等知识点进行了简要介绍。
学习本章后,读者应能掌握Pygame图形界面编程思想、Surface对象的相关知识以及Pygame的Event事件处理方法等。
读者可以用本章介绍的知识完成接红包等常见游戏。
本章知识可为后续章节的学习打下良好基础。