趣味Python游戏编程:第1章 神奇的游戏循环:弹跳小球

趣味Python游戏编程:第1章 神奇的游戏循环:弹跳小球

准备好了吗?我们即将开始激动人心的游戏编程之旅。或许你之前学习过一些编程知识,但若是从没接触过游戏编程,那么你仍然会对游戏程序的运行感到不解。游戏程序不像计算一个公式或谜题,得到答案之后程序就结束了,游戏程序一直是处于运行中的,只要你不主动退出,那么你可以永远待在游戏之中。这就是游戏循环的神奇魔力。

本章将深入介绍游戏循环的运作方式,以及如何运用游戏循环来编写你自己的第一个游戏——弹跳小球。我们将生成一个游戏窗口,然后在里面添加很多小球,让它们在窗口中自由移动,当小球碰到窗口四周时则发生反弹。是不是很有趣呢?

本章主要涉及如下知识点:

设置游戏开发环境

创建游戏场景

创建游戏角色

实现角色的移动

游戏循环的原理

管理多个角色

1.1 准备工作

1.1.1 选择合适的开发工具

“工欲善其事,必先利其器”,编写游戏之前需挑选一款合适的工具,这样可以大大地简化程序编写工作。Python语言的很多第三方库都提供游戏编程功能,最有名的要属Pygame库了,它提供丰富的API来实现游戏的各种效果。但是,对初学者来说,Pygame库还是显得有些复杂,这里希望采用更加简洁高效的工具,使得可以把注意力集中在游戏算法的实现上,而不需要花费太多精力去学习游戏开发库的使用。

于是本书打算采用Pgzero库来编写游戏。Pgzero的完整名称是Pygame Zero,不难看出,它是从Pygame库衍生而来的。可以说Pgzero就是Pygame的一个精简版本,能够实现Pygame库的主要功能,但是屏蔽了一些复杂的细节,使得初学者能够快速上手。

1.1.2 设置开发环境

由于Pgzero是Python的第三方库,它不能独立工作,必须在Python代码中来使用,因此首先需要安装Python开发环境。可以去Python官网下载最新的安装包进行安装(关于Python的详细安装步骤请参考附录)。现在已经准备好了游戏编程的基本环境,可以使用Python提供的IDLE编辑器来编写代码了。

且慢,你是否觉得使用IDLE编辑器来编写程序不是那么方便呢?对于简单的小程序当然无所谓了,但是游戏程序相对来说还是比较复杂的,而且游戏中需要调用一些图片或声音资源,还要对所有的游戏资源进行统一管理。因此还得寻找一个更加灵活方便的游戏编写工具,在这里我采用的是Mu编辑器。Mu编辑器是专门为Python学习者设计的一个开发工具,它的编辑器非常友好,提供了很多的便捷操作,例如代码自动提示、代码缩进标示、语法检查等功能。更重要的是,它已经集成了Pgzero库,而且提供对游戏资源的管理,这正是我们所需要的,不是吗?关于Mu编辑器的安装在附录A中有详细介绍,现在直接运行Mu编辑器试一下。在初次打开Mu编辑器的时候会提示选择运行模式,如图1.1所示。
在这里插入图片描述

单击“Pygame Zero”模式选项,接下来Mu编辑器便会切换到Pgzero模式,运行界面如图1.2所示。

在这里插入图片描述

Mu编辑器中的空白区域便是将要编写代码的地方,当程序写好之后,单击界面上方的“开始”按钮便可以运行程序了。看起来真是太棒了,还等什么呢?赶快开工吧!

1.2 从何处开始

接下来开始编写游戏。可是,游戏程序究竟什么样呢?或许你会在屏幕上输出“Hello World”,或者你知道如何编程计算斐波拉契数列的值,但是你真的确定游戏程序应该如何编写吗?

首先,游戏运行需有一个图形界面(当然,早期的计算机游戏可能是文本界面的,但那已经是很古老的事了,现在探讨的都是基于图形界面的游戏)。为了显示图形界面,这里的程序应该能够生成一个“窗口”,在其中可以显示各种图形或图像,而游戏的内容正是由各种不同的图形或图像来表示的。

试着创建一个程序窗口。

1.2.1 创建程序窗口

在Mu编辑器上方的工具栏中单击“新建”按钮,可以看到编辑器中出现了一块空白区域,这便是新创建的Python源程序文件。接着单击“保存”按钮将该源程序文件保存在磁盘上,在弹出的对话框中为文件起一个名字,操作界面如图1.3所示。

在这里插入图片描述

然后单击“运行”按钮试试,你会看到屏幕上出现了一个窗口,如图1.4所示。

在这里插入图片描述

感觉如何?是不是惊讶得合不拢嘴?明明连一行代码都没有写,竟然就能出现一个窗口。这正是Pgzero的神奇之处。事实上,Pygzero已经做了大量的“幕后工作”,使得我们可以专注于编写游戏逻辑,而不用太关注显示方面的问题。

然而眼前这个窗口黑乎乎的,并不太好看,而且窗口的大小也不是自己想要的。不要着急,我们一点点地解决问题。

1.2.2 改变窗口大小和颜色

首先解决窗口尺寸问题。在Pgzero中,通过定义两个常量值来确定程序窗口的大小,代码如下所示:
在这里插入图片描述

注意WIDTH和HEGIHT是Pgzero预设的两个常量,分别用来表示程序窗口的宽度和高度值(单位为像素)。上面的代码表示将程序窗口的宽度值设为500像素,高度设为300像素。我们将这两行代码输入刚刚新建的源程序文件中,然后再次运行一下,可以看到窗口的大小发生了改变。

接下来试着改变一下窗口的背景颜色。在Pgzero中,窗口的背景颜色默认是黑色(原来如此),若要改变背景颜色,需要在程序中定义一个draw()函数。那么这个draw()函数又是个什么来头呢?

draw()函数是Pgzero的“幕后主使”之一,它负责显示游戏中的各种图形或图像。只需在程序中定义自己的draw()函数,然后将需要绘制图形图像的代码写进draw()函数中,程序便会自动地执行draw()函数进行显示。

那么,要改变窗口的颜色,究竟要在draw()函数中编写什么代码呢?此时还需要借助Pgzero提供的内置对象screen来完成。事实上,Pgzero为了简化游戏编程,在内部设置了很多的对象来协助完成游戏中的各项操作。screen对象主要就是用来在窗口绘图的,它提供了很多的绘图方法,不仅能够绘制图形和图像,还能绘制文字信息,在后面的游戏编程中还会经常使用到它。

目前需要使用的是screen对象的fill()方法,它表示用某种颜色来填满整个窗口。该方法接受一个RGB元组作为参数。那什么是RGB元组呢?

说明:

了解Python的朋友可能对元组并不陌生,元组就是由一对小括号括起来的一组数值。

RGB元组是由三个数所组成的元组,每一个数代表一个颜色分量。具体来说,第一个数代表红色R(Red),第二个数代表绿色G(Green),第三个数代表蓝色B(Blue),每个数的取值范围从0到255。这其实就是我们熟知的三原色,各种颜色都可以由红绿蓝三种基本颜色混合而成,相应地,可以改变RGB元组中三个数的值来获取不同的颜色。例如(255,0,0)表示红色,(0,255,0)表示绿色,(0,0,255)表示蓝色,(0,0,0)代表黑色,(255,255,255)代表白色等(感兴趣的朋友可以去网上查找某个颜色对应的RGB值)。

了解相关知识后,可以在源代码中加入以下两行代码:

保存并运行程序,可以看到如图1.5所示的界面。没错,我们的窗口背景变成了白色。

在这里插入图片描述

练习:

可以试试改动fill()方法中传入的RGB数值,看看会显示什么不一样的背景颜色。

1.2.3 显示图像

现在拥有了一个程序窗口,但它似乎空空如也,并没有什么内容。我们希望在窗口里面显示点什么。例如准备将一幅精美的图片显示在窗口中,如何做到呢?

首先将图片文件放到指定的位置,即images文件夹中。单击Mu编辑器上方的“图片”按钮,会自动打开images文件夹,如图1.6所示。将图片文件复制到该文件夹中即可。

在这里插入图片描述

说明:

Pgzero只支持三种格式的图片文件,分别是png、jpg和gif格式,因此在显示图片之前要看一看,图片文件后缀是不是符合以上文件类型,如果不是则需使用图片处理软件转换一下格式。同时注意一下图片文件的名字,只能由小写字母、下画线及数字组成,而且要以字母开头。例如以下文件名是符合要求的:

ball.png

ball2.png

breakout_ball.png

而如下文件名是不合要求的:

3.png

3degrees.png

my-cat.png

将图片文件准备好并放入images文件夹后,便可以将其显示在窗口中,这需要调用screen对象的blit()方法。例如显示一个名为breakout_ball的小球图片,只需要在程序中加入一行代码:
在这里插入图片描述

blit()方法的第一个参数是要显示的图片文件名,以字符串表示(不要带后缀),第二个参数为图像显示的坐标。该坐标是由两个数所组成的元组,第一个数表示图像的横坐标,第二个数则为图像的纵坐标。由于Pgzero中窗口的坐标原点位于左上角,向右横坐标值增加,向下纵坐标值增加,因此坐标(200,100)表示图像从窗口左边界向右偏移200像素,从窗口上边界向下偏移100像素。

到目前为止已经编写了5行代码,如下所示:

在这里插入图片描述

提示:

注意draw()函数中的两行代码要保持缩进一致,因为Python程序是根据缩进来划分语句块的,同一个函数中的所有代码要具有相同的缩进。

现在运行程序,可以看到图1.7所示的程序界面,其中标示了图像的坐标值所代表的含义。

现在不仅拥有了一个程序窗口,而且在里面显示了一幅图像,真是太了不起了。但别高兴太早,现在这个程序还不能称为游戏。我们都知道,游戏中的图形或图像是会“活动”的,也就是说它们可以不断地改变位置进行显示,而我们的程序目前只能在某个固定的位置显示一幅图像,它根本就不能动。不要灰心,接下来就想办法让它动起来。

在这里插入图片描述

1.3 建立游戏世界

在采取行动之前,有必要来了解一下游戏的基本概念。在游戏的世界中有两个基本要素:场景和角色。游戏场景是指游戏发生的场所,或者说游戏的一个特定情景。通常我们会为游戏制作一些尺寸比较大的图片,以此作为游戏场景的背景图像。游戏角色是指游戏场景中的各种物体,它们不仅有特定的图像,更重要的是它们能够活动(通常是在场景范围内活动),而且彼此之间还能相互作用。

若是我们想设计游戏,则必须为游戏创建场景和角色。那么如何操作呢?

1.3.1 创建游戏场景

首先来创建游戏场景。其实游戏场景之前已经做好了。没听错吧?我们好像什么都没做啊,仅仅是建立了一个程序窗口,然后用白色将它填充了一下。没错,这就算是一个游戏场景。游戏场景可以很复杂,也可以很简单,就如同之前所做的,仅仅是用单一色彩来填充窗口,也可以作为游戏的场景。因为场景的主要作用是为各个游戏角色提供一个活动的场所,只要能够保证角色正确显示就可以了。我们的游戏编程之旅刚刚起步,一切从简,以后还会学习如何设计更加复杂的游戏场景。

1.3.2 创建游戏角色

接下来创建游戏角色。角色的创建似乎没那么简单,因为角色是需要活动的,而之前在窗口中显示的小球根本无法活动,因此它还不能算作游戏角色,仅仅只是一幅图像而已。怎么办呢?好在Pgzero事先已经准备好了,它通过提供一个叫作Actor的类来帮助创建游戏角色。

说明:

按照面向对象编程的思想,类就是对象的模板,通过类可以创建具体的对象实例。例如要烘焙饼干,可以先购买一个饼干模具(例如可爱的小动物形状模具),然后将原料倒入模具中进行烘焙,最后做好的就是小动物形状的饼干了。在这个例子中,饼干模具就好比是“类”,而做好的饼干就好比是“对象”,一个模具可以制作若干个相同的饼干,而一个类则可以创建若干个相同的对象。

因此可以使用Pgzero提供的Actor类来创建需要的角色对象。例如要创建一个小球角色,可以这样编写代码:
在这里插入图片描述

上面这行代码调用Actor类的构造方法来生成小球角色对象,并将其保存在一个变量ball中,今后若要操作小球则只需访问ball变量即可。Actor类的构造方法有两个基本参数,第一个是角色的图片文件名,第二个是角色的初始位置。这和之前显示图像的参数是一样的。

小球角色创建好了,那么如何将它显示在窗口中呢?是不是还与之前的一样,需要调用screen的blit()方法呢?当然不需要了。现在的小球已经不再是一幅图像,而是一个真正的角色对象,它拥有很多的属性和方法。其中有一个叫作draw()的方法,可以用来将自身显示在窗口中。

将之前的代码改写成如下:
在这里插入图片描述

运行一下你会发现,程序的结果和图1.7所显示的效果是一模一样的。可是现在小球还是不会动呀!不要着急,我们已经做好了一切准备工作,现在是时候让它动起来了。

1.4 移动小球

1.4.1 改变小球坐标

倘若想要移动小球,必须改变它在窗口中的位置,即小球显示的坐标。在Pgzero中,角色对象拥有两个属性:x和y。前者表示角色在窗口中的横坐标,后者表示角色在窗口中的纵坐标。由于小球目前已经被定义为角色对象,可以直接修改它的x和y属性来改变其坐标值。

还有一件事需要注意,Pgzero规定所有对角色操作的代码都要放置在一个叫作update()的函数中。因此首先定义一个update()函数,然后将改变小球坐标的代码放入其中,如下所示:
在这里插入图片描述

提示:

“+=”是复合赋值运算符,意思是把ball的x值加1后再赋给x,该句相当于:

ball.x = ball.x+1

运行一下程序,你会发现小球开始缓缓地向右移动。真是棒极了!可这到底是怎么回事呢?明明只写了一行代码啊,小球的x坐标应该只增加1个单位才对,怎么它会一直朝着右边移动呢?

嘿嘿,这就是游戏循环的神奇魔力!

1.4.2 游戏循环

究竟什么是游戏循环呢?如果你有一点编程经验,一定编写过循环程序。所谓循环程序,就是程序在满足指定的条件下,重复不断地执行某些操作。游戏循环也是类似的原理,即把游戏操作的程序代码放置在一个循环语句中,让其自动地重复执行。那么游戏循环的执行条件是什么呢?循环中又该执行什么样的语句呢?

先来看看游戏循环的条件。想一想你玩游戏的经历,当你玩游戏的时候,除非你主动选择退出,否则你是一直处于游戏之中的。难道不是吗?从程序角度来看,自从你进入游戏开始玩,就已经处于游戏循环之中了,而且一直处于其中。因此,游戏循环的执行是无条件的,它本质上就是个死循环!天呐,没听错吧,编程课上老师可特别强调过,“编写循环程序时要检查循环条件,千万别写成了死循环”,没想到游戏程序竟然是个死循环。没错,游戏就是个死循环,或者称为无限循环。

可以用while语句来表示游戏循环,伪代码如下所示:

在这里插入图片描述

可以看到,while语句的循环条件设为了True,而True是个布尔类型的常数,表达的含义就是“真”。因此,while循环会一直重复地执行下去。

接着看看游戏循环中的操作语句应该如何编写。作为一个游戏,它要执行两个最基本的操作:一个是更新游戏逻辑,包括改变角色位置或图像,处理角色之间的相互作用,切换游戏场景等;另一个是绘制游戏图像,包括绘制游戏的背景,绘制角色的图像,绘制文字信息等,如图1.8所示。

在这里插入图片描述

在之前的程序中,我们编写了update()函数来改变小球坐标,也编写了draw()函数来绘制小球图像,而这两个函数恰好分别对应游戏循环中的两个基本操作:update()函数用来更新游戏逻辑,而draw()函数用来绘制游戏图像。由于游戏是不断运行的,需要不断地更新游戏逻辑,同时显示更新后的内容,因此要将update()函数和draw()函数放入游戏循环中重复执行。程序看起来应该像这样:
在这里插入图片描述

然而,我们编写代码的时候并不是这样写的,只是在程序中定义了update()和draw()函数,却并没有通过类似的无限循环语句来调用它们。确实是这样的,因为Pgzero不需要我们这样做,它已经在内部预先设定好了一个游戏循环,我们只负责定义update()和draw()函数,并将更新游戏逻辑和显示游戏图像的代码分别写入其中即可,Pgzero内部的游戏循环会自动调用这两个函数。

当单击Mu编辑器上的“开始”按钮时,程序会启动游戏循环来开始游戏;当单击“停止”按钮时,程序便会终止游戏循环来退出游戏。

现在终于明白游戏程序竟然是这样运作的,有一点小小的满足感,原来游戏并没有想象的那么神秘嘛。既然Pgzero已经在幕后安排好了一切,那只需要集中精力为update()和draw()这两个函数编写代码就好啦。没错,就是这么简单!

1.4.3 朝其他方向移动

目前虽然程序已经实现了移动小球,可小球只是一直朝着右边移动,假如想要它朝左边、下边或者上边移动,又该如何实现呢?

首先可以确定,需要对update()函数中的代码进行修改,因为改变小球移动方向其实就是修改小球的坐标,这属于更新游戏逻辑的操作,所以无须对draw()函数进行改变。

目前小球之所以朝右边移动,是因为当初的update()函数中是这样的语句:
在这里插入图片描述

它表示游戏循环每执行一次,小球的x坐标都会增加1个单位。因此随着游戏循环的不断运行,小球的x坐标值会不断增大,小球与窗口左边界的距离也会越来越大,从而看起来就像是小球向右边移动。类似地,若想让小球向左移动,只需要不断地减少小球的x坐标值即可,代码如下:
在这里插入图片描述

相应地,让小球向下移动,则增加小球的y坐标值:
在这里插入图片描述

让小球向上移动,则减少小球的y坐标值:
在这里插入图片描述

如果想让小球斜着移动,例如从左上朝右下移动怎么办呢?此时可以同时改变小球的x和y坐标值。代码如下:
在这里插入图片描述

练习:

是否明白了呢?不妨动手试一下,若要小球从右上朝左下移动,应该如何修改代码呢?

1.4.4 移动得快一些

你是不是觉得现在这只小球移动得太慢了,像只蜗牛在爬一样,怎样让它快一点呢?小球之所以移动得慢,是因为每次对小球的坐标值只增加或减少了1个单位。若想加快小球的移动速度,那么可以试着将1替换成更大的数值。但你很快会发现,如果坐标值改变得太大,小球可能一下子就跑到窗口外面了。因此需要根据游戏的效果来反复修改数值,直到获得满意的效果为止。

说明:

其实在游戏编写过程中经常会遇到这样的情况,就是在设定游戏中的某个数值(例如角色的位置、生命的数量、攻击的威力等)的时候,需要根据游戏实际的运行情况来不断调整和修改,以达到比较满意的效果。这种方法常被称为“试错法”。

1.5 实现小球反弹

目前还存在一个问题,那就是当小球移动到窗口之外后,它便消失得无影无踪了。作为游戏角色的小球竟然跑到了场景外面!玩过游戏的朋友都知道,游戏角色是不能置于场景之外的,可怎样将小球的活动范围限定到窗口之内呢?

我们需要做两件事情:一是检查小球是否跑到了场景外面;二是让小球重新回到场景之中。

1.5.1 检测小球的位置

若要知道小球是否跑到场景之外,可以将它的位置与窗口进行比较,例如,如果小球的右边界超过了窗口的右边界,则可判定小球即将从右方跑出场景。那么如何用程序来表达这个意思呢?

目前我们只知道小球的x属性表示横坐标,y属性表示纵坐标。而不论是x还是y的值,都是根据角色中心点的位置来计算的,所以准确来说,小球的x属性其实是小球中心点的横坐标,而y属性是小球中心点的纵坐标。那么如何表示小球右边界的坐标呢?

Pgzero为角色对象提供了4个属性left、right、top、bottom,分别表示角色的左、右、上、下各个边界的位置。具体来说,left和right分别表示角色左边界和右边界与窗口左边界的距离;top和bottom分别表示角色上边界和下边界与窗口上边界的距离。于是可以通过ball对象的right属性来获取小球右边界的位置。而要想知道小球的右边界是否超过了窗口右边界,则需要判断小球的right属性是否大于窗口的宽度WIDTH,这可以借助条件语句if来实现,代码类似如下形式:
在这里插入图片描述

倘若条件成立,那如何让小球回到窗口之内呢?这就看你的意思了。换句话,你想怎样让小球回到窗口之内都可以,例如可以让小球从窗口右边跑出去,然后从窗口左边重新跑进来;或者当小球跑到场景之外后,让它直接回到窗口中的某个指定位置等。你完全可以按照自己的想法来规定小球的动作,然后编写代码实现,游戏便会忠实地按照你的想法来执行。其实这就是所谓的游戏规则设计,也是游戏设计的最大乐趣所在,因为此刻你就是造物主,游戏世界将会按照你制定的规则来运转。

虽然你可以按照自己的想法行事,但为了保证有人愿意玩你的游戏,你还是得仔细考虑一下如何让游戏规则更有乐趣。游戏设计的最高目标就是实现可玩性,为此需要付出巨大的努力。

1.5.2 将小球反弹回来

下面考虑这样一种规则,那就是当小球超出窗口边界后,将其反弹到窗口之内。例如,小球如果向右边移动时超出了边界,则让它掉过头来朝左移动。对其他方向也可采取类似的操作。这就跟桌球运动中的小球一样,若在移动中碰到库边则发生反弹。效果如图1.9所示。这样的设计看起来还蛮有趣的,不是吗?赶紧编写代码吧。

首先实现窗口右边界的反弹。将update()修改为如下代码:

在这里插入图片描述

我们把这段代码“翻译”一下,它表示:小球的横坐标先是增加5个单位,如果它的右边界超出了窗口右边,则将它的横坐标减少5个单位。看起来完全符合逻辑,那么运行看看是什么效果。

在这里插入图片描述

怎样,是不是有点失望?实际情况是小球一动不动地停靠在窗口右边。为何会这样呢?让我们仔细分析一下程序。你可别忘了,update()函数是在游戏循环中反复调用执行的,因此小球的x值会不断增加,当x值大于窗口宽度之后会执行一次x值减5操作。而在下一次的循环中,小球的x值仍然要加5,增加之后超出了边界随即又减5。最后的结果就如同你所见到的,小球的坐标既不能继续增加,也不会持续减少,它永远地停靠在窗口右边界。

以上代码的问题在于,我们希望小球向右移动超出边界后“反弹”,也就是掉转移动的方向,改为向左移动。但是从代码来看,“ball.x += 5”这句却让小球一直朝右移动。原因就在于,为小球的x坐标值增加的是一个常数5,这个常数仅仅代表了移动的距离,而不能表示移动的方向。为了实现小球的反弹,需要让小球的坐标加上一个变量,这个变量既能表示移动的距离,又能表示移动的方向,将其称为速度变量。

由于小球可以在水平和垂直两个方向移动,可以定义两个速度变量dx和dy,分别表示小球在水平和垂直方向的速度。dx和dy的大小用来表示移动距离,而正负则可表示不同的方向,例如dx为5表示向右移动5个单位,而-5表示向左移动5个单位;相应地,dy为5表示向下移动5个单位,而-5表示向上移动5个单位。

另外,由于速度是专属于小球的变量,因此最好将其设置为小球对象的属性。需要注意的是,Pgzero事先并没有为角色设置速度这个属性,所以需要我们自己添加。其实在Python语言中为一个对象添加属性非常方便,可以采用如下的代码为小球添加速度属性:
在这里插入图片描述

这样一来,就能方便地实现小球的反弹了。只需要将update()函数修改为如下形式即可:
在这里插入图片描述

运行一下,看看是不是可以了呢?当然,以上代码只能实现窗口右边界的反弹,如果要让小球在窗口左边界也能反弹,则if语句中还要增加额外的判断条件,即判断小球的左边界是否超出了窗口的左边界。可以用or关键字来连接两个不同的判断条件,则不论是超出右边界还是超出左边界,小球都将发生反弹。代码如下所示:
在这里插入图片描述

这样一来,小球向左移动时,如果左边界值小于0之后,也会掉转方向,重新朝右方移动。于是游戏的运行效果就是,小球不停地在窗口左右两侧之间来回移动。想一想,若要小球在窗口上下边界之间来回移动,又该如何编写代码呢?

最后,让小球的垂直速度也同时发生改变,并添加垂直方向的反弹规则。代码如下所示:
在这里插入图片描述

运行一下看看,现在小球竟然围绕窗口四周愉快地弹跳起来了!是不是很神奇呢?

1.6 加入更多的小球

游戏的设计目标已经基本达成,但现在窗口中只有一个小球,是不是显得有点孤单呢?为了让游戏看起来更好玩,不妨再多添加几个小球。例如要在窗口中加入两个小球,该怎么编写代码呢?

1.6.1 添加两个小球

既然已经知道如何创建一个小球,那么依样画葫芦,再定义一个小球角色,然后分别在update()函数和draw()函数中编写逻辑更新与显示图像的代码即可。完整的代码如下所示:
在这里插入图片描述

运行一下,可以看到窗口中出现了两个小球,它们都在绕着窗口四周进行弹跳,如图1.10所示。是不是很简单呢?

练习:

不妨按照相同的思路再添加一个或更多小球。

在这里插入图片描述

你很快就会发现问题,随着小球数量的增加,代码会变得越来越长。其实不难发现,在上面的代码中,两个小球角色除了名称和初始位置不同之外,其他的操作几乎是一模一样的。倘若要在程序中添加大量的小球,例如几十个甚至上百个小球,那么意味着相同的操作也要重复几十遍甚至上百遍,而且代码的长度将变得不可想象。

1.6.2 使用列表

那么有没有更便捷的方法来统一处理多个角色的操作呢?当然有。Python中提供了组合数据类型,目的就是集中管理多个对象,其中最常用的当属列表类型List。既可以动态地向列表中添加对象,也可以随时从列表中删除对象,而随着对象的增加或减少,列表的长度也会自动改变。还可以将循环语句与列表的操作结合起来,从而实现对列表中所有对象的统一操作。

为了生成多个小球,首先定一个空列表,命名为balls。同时定义一个常量NUM,用来表示小球的数量。代码如下:

NUM = 10
balls =[ ]

其次通过for循环语句自动创建每个小球角色,接着为其设置坐标和速度值,最后将其加入列表balls中。代码如下所示:

for i in range (NUM):
    ball = Actor("breakout ball")
    ball.x = 50 * i + 100
    ball.y = 100
    ball. dx = 5+ iball. dy = 5+ i
    balls. append (ball)

提示:

上述代码所使用的循环叫作计数循环,循环的总次数由range()函数决定(在这里是10),同时使用循环变量i来记录当前的次数。

循环每运行一次都会创建一个小球角色,这是通过调用Actor()构造方法来实现的。可以看到,Actor方法的参数可以仅仅是一个图片文件名,而角色的位置可以在角色创建后再进行设置。

为了让小球的运动呈现不同的轨迹,在上述代码中为每个小球角色设置的坐标和速度都不相同,这是通过循环变量i的不同取值来实现的。而在每一次循环操作的最后,程序都会调用列表的append()方法,将创建的小球加入列表中。当整个循环语句执行完毕,所有的小球便全部加入列表中。

接下来在update()函数中对所有小球统一进行逻辑更新操作,而这也可以借助for循环语句来实现。代码如下所示:
在这里插入图片描述

提示:

在上述代码中所使用的循环叫作遍历循环,意思就是说,循环语句会对列表中的每个对象依次进行操作。

对于balls列表中保存的每一个小球角色,程序首先改变其坐标值,然后执行反弹操作。游戏最终呈现的效果便是所有的小球都在窗口内不停地弹跳。

最后在draw()函数中对所有小球统一进行图像绘制操作,代码如下所示:
在这里插入图片描述

可以看到,这里同样采用了遍历循环。调用balls列表中每个小球的draw()方法来完成图像的绘制。

至此,已经完成弹跳小球游戏,最终的游戏效果如图1.11所示。试一试加入更多的小球吧!

在这里插入图片描述

1.7 回顾与总结

在本章中,我们从无到有编写了一个弹跳小球的游戏。首先学习了Mu编辑器的基本操作,以及如何借助Pgzero库来创建游戏窗口。然后了解了如何在窗口中绘制背景及图像。接下来学习了如何创建游戏角色,并在窗口中生成了一个小球角色。设法让小球移动,并借此理解了游戏循环的概念,正是依靠游戏循环,游戏才会不断运行。此后还实现了小球的反弹效果,让小球围绕窗口的四条边界来回弹跳。最后,添加了很多个弹跳的小球,并通过列表对它们进行统一的管理和操作。

本章涉及的Pgzero库的相关特性总结如表1.1所示。

在这里插入图片描述

下面给出弹跳小球游戏的完整源程序代码。

# 弹跳小球游戏源代码balls.py

WIDTH = 800                         # 屏幕宽度
HEIGHT = 600                        # 屏幕高度
NUM = 10                            # 小球数量
balls = []                          # 小球角色列表

for i in range(NUM):                # 生成小球角色
    ball = Actor("breakout_ball")
    ball.x = 50 * i + 100           # 设置小球水平坐标
    ball.y = 100                    # 设置小球垂直坐标
    ball.dx = 5 + i                 # 设置小球水平速度
    ball.dy = 5 + i                 # 设置小球垂直速度
    balls.append(ball)              # 将小球角色加入列表


# 更新游戏逻辑
def update():
    for ball in balls:
        ball.x += ball.dx           # 更新小球水平坐标
        ball.y += ball.dy           # 更新小球垂直坐标
        # 若小球碰到屏幕左右边界,则水平反向
        if ball.right > WIDTH or ball.left < 0:
            ball.dx = -ball.dx
        # 若小球碰到屏幕上下边界,则垂直反向
        if ball.bottom > HEIGHT or ball.top < 0:
            ball.dy = -ball.dy


# 绘制游戏图像
def draw():
    screen.fill((255, 255, 255))    # 清空屏幕
    for ball in balls:
        ball.draw()                 # 绘制小球

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值