你准备好了吗?我们即将开始一段激动人心的游戏编程之旅。
也许你之前学过一点编程,但如果你从未接触过游戏编程,你仍然对游戏程序是如何工作的感到困惑。游戏程序不像计算公式或谜题。得到答案后,程序结束。游戏程序一直在运行。只要不主动退出,就可以永远留在游戏中。这就是游戏循环的魔力。
下面我们尝试用最少的代码编写一个小游戏。
准备好工作了
01 选择合适的开发工具
“要想把工作做好,就必须先利好自己的工具。” 在编写游戏之前,您必须选择合适的工具,这可以大大简化编程工作。该语言中有许多提供游戏编程功能的第三方库。其中最著名的就是库,它提供了丰富的API来实现游戏的各种效果。但是,对于初学者来说,该库仍然有点复杂。我们希望使用更简洁高效的工具,让我们可以专注于游戏算法的实现,而不需要花太多精力学习游戏开发库的使用。
该库在这里用于编写游戏。全称是零,不难看出它是从库中派生出来的。可以说是简化版,可以实现库的主要功能,但是屏蔽了一些复杂的细节,让初学者可以快速上手。
02 搭建开发环境
因为是第三方库,不能独立工作,必须在代码中使用,所以我们首先需要安装开发环境。可以到官网下载最新的安装包进行安装,然后就可以使用提供的IDLE编辑器编写代码了。
等一下,你不觉得用IDLE编辑器写程序不太方便吗?当然简单的小程序没关系,但是游戏程序比较复杂,游戏中需要调用一些图像或声音资源,我们也需要对所有游戏资源进行统一管理。因此,我们必须找到一个更灵活方便的游戏编写工具,这里我使用了 Mu 编辑器。Mu是专门为学习者设计的开发工具。它的编辑器非常友好,提供了很多便捷的操作,如自动代码提示、代码缩进标记、语法检查等功能。更重要的是,它集成了库,还提供了游戏资产的管理,这正是我们所需要的。它吗?Mu 编辑器可以在官方网站(.mu/)上下载安装。现在让我们运行 Mu 并尝试一下。首次打开 Mu 时,会提示选择操作模式,如图 1 所示。
图1 Mu编辑器模式选择界面
我们点击鼠标选择“Zero Mode”,然后Mu会切换到该模式,看到的运行界面如图2所示。
图 2 “零”运行模式
Mu 编辑器中的空白区域是我们将编写代码的地方。程序编写完成后,我们可以点击界面上方的“开始”按钮来运行程序。它看起来很神奇,你还在等什么?现在开始工作!
从哪儿开始
接下来,开始编写游戏。但是游戏程序到底是什么样的呢?也许你会在屏幕上输出“Hello World”,或者你知道如何编写斐波那契数列的值,但你真的确定游戏程序应该如何编写吗?
首先,游戏以图形界面运行(当然,早期的电脑游戏可能有文本界面,但那已成为过去,现在我们谈论的是基于图形的游戏)。为了显示一个图形界面,我们的程序应该能够生成一个“窗口”,在其中可以显示各种图形或图像天外神坛源码网,游戏的内容由各种图形或图像来表示。
让我们尝试创建一个程序窗口。
01 创建程序窗口
点击Mu编辑器上方工具栏中的“新建”按钮,可以看到编辑器中出现了一个空白区域,就是新创建的源程序文件。
然后,单击“运行”按钮进行尝试,您会看到屏幕上出现一个窗口,如图3所示。
图3 游戏窗口界面
图3 游戏窗口界面
感觉怎样?你傻眼了吗?即使不写一行代码,也可以出现一个窗口。这就是魔法。事实上,我们已经为我们做了很多“幕后”工作,让我们可以专注于编写游戏逻辑,而不是过多地关注显示方面的事情。
不过,眼前漆黑的窗户并不难看,窗户的大小也不是我想要的。别担心,我们会一点一点地解决问题。
02 改变窗口大小和颜色
首先解决窗口大小问题。中,通过定义两个常量值来确定程序窗口的大小,代码如下:
WIDTH = 500
HEIGHT = 300
注意,WIDTH 和 是两个预设常量,用于表示程序窗口的宽度和高度(以像素为单位)。上面的代码表示程序窗口的宽度值设置为600像素,高度设置为400像素。我们将这两行代码输入到新创建的源程序文件中,然后再次运行,可以看到窗口的大小发生了变化。
接下来我们尝试改变窗口的背景颜色。在中,窗口的背景颜色默认为黑色(原样)。要更改背景颜色,您需要在程序中定义一个 draw() 函数。那么这个draw()函数的由来是什么?
draw() 函数是“幕后高手”之一,负责在游戏中显示各种图形或图像。我们只需要在程序中定义自己的draw()函数,然后将需要绘制图形图像的代码写入draw()函数,程序就会自动执行draw()函数进行显示。
那么,要改变窗口的颜色,draw()函数中应该写什么代码呢?此时,我们还需要使用提供的内置对象来完成它。实际上,为了简化游戏编程,内部设置了很多对象来辅助完成游戏中的各种操作。该对象主要用于在窗口中绘制,它提供了很多绘制方法,不仅可以绘制图形和图像,还可以绘制文本信息,我们在游戏编程中会经常用到它。
目前我们需要使用对象的fill()方法,即用某种颜色填充整个窗口。该方法接受一个 RGB 元组作为参数。那么什么是 RGB 元组呢?RGB元组是三个数字的元组,每个数字代表一个颜色分量,如(255, 0, 0)表示红色,(0, 255, 0)表示绿色,( 0, 0 , 255) 表示蓝色,(0, 0, 0) 表示黑色,(255, 255, 255) 表示白色等。
所以我们可以在源码中加入以下两行代码:
def draw():
screen.fill((255, 255, 255))
保存并运行程序,可以看到如图4所示的界面。没错,我们的窗口背景变成了白色。
图4 改变背景颜色后的程序窗口
03 显示图像
现在我们有一个程序窗口,但它似乎是空的,没有内容。我们想在窗口中显示一些东西。比如我们准备了一张漂亮的图片,想在窗口中展示。怎么做?
首先,我们需要把图片文件放到指定的位置,也就是“”文件夹。点击 Mu 编辑器上方的“图片”按钮会自动打开“”文件夹。我们可以将图像文件复制到这个文件夹中。
一旦图像文件准备好并放在“”文件夹中,我们就可以在窗口中显示它,这需要调用对象的 blit() 方法。例如,要显示一个名为“”的小球的图片,我们只需要在程序中添加一行代码:
screen.blit("breakout_ball", (200, 100))
blit() 方法的第一个参数是要显示的图像文件名,用字符串表示(不带后缀),第二个参数是图像显示的坐标。坐标是两个数字的元组,第一个数字代表图像的横坐标,第二个数字是图像的纵坐标。由于中间窗口的坐标原点位于左上角,横坐标值向右增加,纵坐标值向下增加,所以坐标(200, 100)表示图像偏移200从窗口左边框向右的像素,从窗口的上边框向下偏移 100 像素。
到目前为止,我们已经编写了 5 行代码,如下所示:
WIDTH = 500
HEIGHT = 300
def draw():
screen.fill((255, 255, 255))
screen.blit("breakout_ball", (200, 100))
现在运行程序,可以看到如图5所示的程序界面,其中表示了图像坐标值所代表的含义。
图 5 显示图像的程序界面
现在我们不仅有一个程序窗口,而且里面还有一个图像,这太神奇了。但是不要太激动,这个程序还不能称为游戏。我们都知道,游戏中的图形或图像会“主动”,也就是说,它们可以不断变换位置进行显示,而我们的程序目前只能在固定位置显示一个图像,根本不不能动。不要气馁,我们会想办法让它动起来。
构建游戏世界
在采取行动之前,有必要了解游戏的基本概念。游戏世界有两个基本元素:场景和角色。游戏场景是指游戏发生的地方,或游戏的特定场景。通常我们为游戏制作一些较大的图片作为游戏场景的背景图片;游戏角色是指游戏场景中的各种物体,它们不仅具有特定的图像,更重要的是它们可以移动(通常在场景范围内活动),并且可以相互交互。
如果我们要设计一个游戏,我们必须为游戏创造场景和角色。那么它是怎样工作的?
01 创建游戏场景
首先让我们创建游戏场景。其实我们之前已经做过游戏场景了。你听错了吗?除了创建一个程序窗口并用白色填充它之外,我们似乎什么也没做。是的,这是游戏场景。游戏场景可以很复杂,也可以很简单,就像我们做的那样,只需用一种颜色填充窗口,也可以是游戏场景。因为场景的主要作用是为每个游戏角色提供一个可以移动的地方,只要角色能在里面正确显示。
02 创建游戏角色
接下来创建游戏角色。角色的创建似乎没有那么简单,因为角色需要移动,而我们之前在窗口中显示的球根本不能移动,所以不能算作游戏角色,只是一个图像。怎么做?幸运的是,它已经提前为我们准备好了,它通过提供一个名为 Actor 的类来帮助我们创建游戏角色。例如,要创建一个小球角色,您可以编写如下代码:
ball = Actor("breakout_ball", (200, 100))
上面这行代码调用了Actor类的构造函数来生成球角色对象,并将其保存在一个变量ball中。如果以后要操作球,只需要访问球变量即可。Actor 类的构造函数有两个基本参数,第一个是角色的图像文件名,第二个是角色的初始位置。这与之前显示图像的参数相同。
球字符创建完成,那么如何在窗口中显示呢?还是和之前一样,需要调用的 blit() 方法吗?当然没必要。现在球不再是一个图像,而是一个真实的角色对象,它有很多属性和方法可以使用。有一个名为 draw() 的方法可用于在窗口中显示自身。
我们将之前的代码改写如下:
WIDTH = 500
HEIGHT = 300
ball = Actor("breakout_ball", (200, 100))
def draw():
screen.fill((255, 255, 255))
ball.draw()
运行一下,你会发现程序的运行结果和图5一模一样。但是现在小球还是不动?别担心,我们已经准备好了一切,现在是时候让它动起来了。
移动球
01 改变球的坐标
如果要移动小球,必须改变它在窗口中的位置,即小球显示的坐标。其中,字符对象有两个属性:x和y,前者表示窗口中字符的横坐标,后者表示窗口中字符的纵坐标。由于球现在已经被定义为一个角色对象,我们可以直接修改它的 x 和 y 属性来改变它的坐标值。
还有一点需要注意的是,所有对字符进行操作的代码都需要放在一个名为 () 的函数中。所以我们先定义一个()函数,然后把改变小球坐标的代码放进去,如下:
def update():
ball.x += 1
运行程序,你会发现小球开始慢慢向右移动。这很棒!但是这里发生了什么?显然只写了一行代码。球的 x 坐标应该只增加 1 个单位。为什么它一直向右移动?
嘿,这就是游戏循环的魔力!
02 游戏循环
究竟什么是游戏循环?如果你有一点编程经验,那你肯定写过循环程序。循环程序是在特定条件下重复执行特定操作的程序。游戏循环也是类似的原理,就是将游戏操作的程序代码放在一个循环语句中,自动重复。那么游戏循环的执行条件是什么呢?循环体中应该执行什么样的语句?
我们先来看看游戏循环的条件。想想你的游戏体验。当你玩游戏时,除非你主动选择退出,否则你总是在游戏中。是不是?从程序的角度来看,从你进入游戏并开始玩的那一刻起,你就一直在游戏循环中,而且你一直在其中。因此,游戏循环的执行是无条件的,本质上是一个无限循环!天哪,你没听错,编程课的老师强调“写循环程序的时候一定要检查循环条件,不要写成死循环”。没想到游戏程序是无限循环的。是的,游戏是一个无限循环,或者说是一个无限循环。
我们可以使用while语句来表示游戏循环,代码如下:
while True:
执行游戏操作
可以看出while语句的循环条件设置为True,而True是一个布尔类型的常量用编程做小游戏,意思是“真”。因此,while循环会被重复执行。
接下来,我们看看游戏循环中的动作语句应该怎么写。作为一个游戏,它执行两个基本操作:一是更新游戏逻辑,包括改变角色位置或图像,处理角色之间的交互,切换游戏场景等;另一种是绘制游戏图片,包括绘制游戏背景、绘制人物形象、绘制文字信息等。如图6所示
图6 游戏循环示意图
在之前的程序中,我们写了()函数来改变球的坐标,draw()函数来绘制球的图像,这两个函数正好对应了游戏循环中的两个基本操作:()函数用于更新游戏逻辑,而 draw() 函数用于绘制游戏图像。由于游戏是连续运行的,游戏逻辑需要不断更新,更新后的内容要再次显示,所以要把()函数和draw()函数放到游戏循环中重复执行。该程序应如下所示:
while True:
update()
draw()
但是,这并不是我们写代码时写的,只是在程序中定义了()和draw()函数,并没有通过类似的无限循环语句调用它们。确实是这样,因为我们不需要这样做,它内部预先设置了一个游戏循环,我们只负责定义()和draw()函数,编写代码更新游戏逻辑并分别显示游戏画面 其中,游戏内部循环会自动调用这两个函数。
当点击 Mu 编辑器上的“开始”按钮时,程序启动游戏循环开始游戏;单击“停止”按钮时,程序终止游戏循环以退出游戏。
现在我终于明白了,游戏程序居然是这样工作的,心里有点满足。事实证明,游戏并没有我想象的那么神秘。现在一切都安排在了幕后,我们只需要集中精力编写 () 和 draw() 这两个函数的代码。是的,就是这么简单!
完善游戏规则
还有一个问题,当小球移动到窗外时,它就消失得无影无踪了。作为游戏角色的小球跑出现场!玩过游戏的朋友都知道游戏角色不能放在场景之外,但是如何限制小球移动到窗口呢?
我们需要做两件事:一是检查球是否已经跑出场景;另一种是让球回到现场。
01 检测球的位置
要知道小球是否跑出场景,我们可以将它与窗口的位置进行比较,例如,如果小球的右边界超出窗口的右边界,则可以确定小球即将用完从右边的场景。那么如何在程序中表达这个意思呢?
目前我们只知道小球的x属性代表横坐标,y属性代表纵坐标。不管x还是y的值,都是根据人物中心点的位置来计算的,所以准确的说,小球的x属性其实就是小球中心点的横坐标,而y属性是小球的中心点。Y 轴。那么如何表示小球右边界的坐标呢?
为字符对象提供了left、right、top 4个属性用编程做小游戏,分别表示字符的左、右、上、下边界的位置。具体来说,left和right分别表示字符左右边框与窗口左边框的距离;top 和 分别表示字符上下边框与窗口上边框的距离。所以可以通过球对象的right属性来获取球的右边框的位置。要知道小球的右边框是否超过窗口的右边框,需要判断小球的右属性是否大于窗口的宽度WIDTH,可以用条件语句if来实现,
if ball.right > WIDTH:
让小球重新回到窗口内
如果条件满足,如何让小球回到窗口?这取决于你的意思。换句话说,你可以用任何你想要的方式让小球回到窗口。例如,可以让球从窗口右侧跑出,然后从窗口左侧跑回来;或者当小球跑出场景时,让它直接回到窗口中的指定位置,以此类推。你可以完全按照自己的想法定义小球的动作,然后编写代码来实现,游戏就会忠实的按照你的想法执行。其实这就是所谓的游戏规则设计,也是游戏设计最大的乐趣,因为此刻你是创造者,游戏世界会按照你设定的规则运行。成就感,不是吗?!
02 让球回到窗口
让我们考虑一个规则,当球超出窗口的边界时,让它回到窗口的另一边。例如,如果球向右移动超出窗口的右边界,则使其从窗口的左边界出现。
我们将()修改为如下代码:
def update():
ball.x += 1
if ball.right > WIDTH:
ball.x = 0
我们来“翻译”一下这段代码,意思是:小球的横坐标先增加1个单位,如果它的右边超出窗口的右边界,它的横坐标就设置为0。运行一下看看效果如何. 你有没有发现小球在窗户里跑来跑去不累?
好了,我们的小游戏写到此为止了,想必大家已经知道游戏程序是干什么的了。你觉得容易吗?想亲自尝试吗?
小游戏的完整源程序如下。不多也不少,只有十行代码!
WIDTH = 800
HEIGHT = 600
ball = Actor("breakout_ball", (0, 300))
def update():
ball.x += 1
if ball.right > WIDTH:
ball.x = 0
def draw():
screen.fill((255, 255, 255))
ball.draw()
图 7 游戏最终运行结果
(本文节选自《趣味学习游戏编程》一书)
PS:不要以为你只能做简单的游戏,它也可以做更复杂的游戏。例如,下面显示的游戏用于制作 |