13岁Python开发者写给青少年的多人游戏编程(上)

这篇博客的作者是一名13岁的Python开发者Julian Meyer。你可以在Google+Twitter上找到他。

我确定,你一定曾和你的朋友们一起玩过在线多人游戏。但是你是否想过这些游戏的内部是怎样实现的呢,游戏是怎样在计算机中运行的呢?

在这个教程中,你将通过编写一个简单的游戏来学习有关多人游戏编程。与此同时,你也将学习到面向对象程序设计的思想。

在这个教程中,你将会使用Python语言和它的PyGame模块。如果你刚接触Python或PyGame模块,你应该先阅读一下这篇文章,将告诉你关于PyGame的基础知识。

着手开始吧

首先,你需要安装PyGame模块。你可以在这个链接here上下载一个mac版的PyGame模块安装程序。如果你的系统是Mac OS 10.7或以上,请下载Lion版的安装程序,其它的,则下载Snow Leopard版本。

你也可以按以下的方法下载和安装PyGame:

如果你使用MacPorts工具,那么请用如下命令来安装:sudo port install python2.7 py27-game

如果你使用Fink,那么请使用如下命令:sudo fink install python27 pygame-py27

如果你使用Homebrew和pip,那么请在这个链接here中查找下载和安装PyGame的命令。

如果你使用的是windows操作系统,那么你可以在这里here找到PyGame的安装程序。

提示:如果你在上面的安装教程中遇到问题,那么请确保你在系统上安装了32位版本的Python。如果你使用的是64位的系统,那么你应该使用python2.7-32来运行Python。

最后,在这里下载我们这个项目所需的文件download the resources for this project,(访问这个链接需要翻墙),这些文件包括游戏所需的图像和声音。

我们的“游戏规则”

我们给教程中将要制作的游戏取名为“Boxes”。也许,你在学校中已经和你的朋友们在纸上玩过这个游戏了。

也许你对游戏规则并不那么熟悉,在这里,就先介绍一下游戏规则:

1.游戏的棋盘包含7×7个网格点,如果你将这些点用线段连起来,将得到6×6个立方格。

2.轮到每个玩家玩时,玩家将垂直或水平相邻的两个点用线段连接起来。

3. 如果一个玩家在网格中连接一条线后,网格中便组成了一个新格子,那么这个玩家就拥有这个新格子,并获得1分。

4 在游戏的最后,拥有格子数最多或分数最高的玩家就是胜利者。

虽然游戏的规则非常简单,但这个游戏玩起来却非常有趣,特别是当你无聊的时候。但是,如果能在线上和其他玩家玩这个游戏,是不是会更有趣呢?

 

面向对象程序设计简介

在我们开始编写游戏之前,先讨论一下你将在本教程中用到的面向对象程序设计思想。

面向对象程序设计,也被称为OOP,它是一个基于对象的编程方式。对象是由许多数据和与这些数据相关的逻辑组成。例如,你有一个“狗”对象,那么这个对象就包含了一些数据(例如,狗的名字、它最大的乐趣等),以及相关逻辑(例如,使狗发出叫声的指令)。

对象是由叫做“类”的模板实例化而成的,类定义了对象所包含的数据,以及这个对象能做的事情。对象的数据和它能做的事情分别称为对象的属性和方法。

方法即是一些函数,你可通过调用函数使对象完成某一任务。例如你可将car.drive()这行代码,理解为是告诉“汽车(car)”这个对象“开车(drive)”。属性是一些属于对象的变量。继续上一个例子,你的汽车“car”也许会有一个名为汽油(gas)的属性,代码car.gas = 100的意思是将汽车的汽油量设置为100.

刚才所说的两个代码操作的是一个已经存在的对象。回想我们刚才提到的,汽车(car)类是一个模板,它定义了怎样实例化一个汽车(car)对象,这个定义包括了对象的属性和方法。在对方法的定义中,你会看到对象内部的方法操作自己的代码。例如,你会看到这样的代码:self.gas=100,它不同于car.gas=100,self.gas=100的意思是汽车(car)对象告诉自己,将自己的汽油量(gas)设置为100。

OOP包含了许多内容,但是以上介绍的基础知识已经足够我们的教程了。我们用代码将Boxes游戏描述成许多对象的交互。这些对象都包含了我们在类中对其定义的若干属性和方法。当你在编写代码时,应注意,你写的类代码是从类的内部操作自身,还是操作其它外部对象。

 

编写一个简单的面向对象的游戏

有许多面向对象的框架可被用于我们的游戏设计。我们给Boxes游戏设计了两个对象,一个对象负责游戏的客户端,另一个对象负责游戏的服务器,现在,让我们来创建一个客户端的主类,这个类的代码将在用户启动游戏时运行。

在制作每一个游戏之前,我喜欢先为这个游戏程序创建一个文件夹。当你解压刚才下载的项目压缩文件时,将会看到一个名为boxes的文件夹。你需要将你的源文件和图像文件一起放在这个文件夹中。

在这个文件目录下,使用你最喜欢的编辑器创建一个名为boxes.py的文件(如果你没有最喜欢的编辑器,那么我推荐你在mac上使用TextEdit,或者在Windows上使用Notepad)。然后在这个文件中使用import语句:

这条语句导入了PyGame模块,我们在后续编码中,将使用这个模块。在进入下一步之前,你应该首先测试一下,这个模块是否已经正确导入并可以使用了。打开终端,并用cd命令,进入我们的项目文件夹,然后在终端里输入python boxes.py命令,例如,在我的机器上应输入这样的命令:

如果你能成功执行这个命令,就说明PyGame模块已经正确的安装在你的电脑上了。我们就可以进入下一步教程了。

提示:如果你运行上述命令时,收到“No module named pygame”的错误提示,则说明你没有安装PyGame,或者你安装的PyGame版本和你系统中的Python版本不兼容。例如,如果你使用MacPorts来安装Python 2.7和PyGame,那么你将使用这个命令来安装他们:port install python2.7 py27-game PyGame,安装好之后,你需要保证在终端里调用2.7版本的Python:python2.7。如果运行上述代码得到这个错误:

就说明你需要运行32位模式的Python,例如:python2.7-32

接下来,像定义每一个类一样,在boxes.py文件中添加类定义代码:

这段代码的第一行告诉编译器,你将创建一个名叫BoxesGame的新类。第二行定义了一个名叫__init__的方法。init两边的双下划线暗示着这是一个特殊的方法名字。事实上,这个名称__init__确定了这个类的构造方法,这个方法将在你创建或实例化一个类的对象时调用。

现在,你将在init方法中编写初始化PyGame的代码。将以下的代码紧接在原来代码中的注释部分,#put something here…:

请注意输入代码的缩进格式,即刚才输入的代码都要和“#put

something here…”这段代码左对齐。你可以在这个链接中查看更多

关于Python代码缩进的内容:Python Indentation.

接下来我们一段一段解释刚才添加的代码:

1. 首先,你初始化了PyGame和两个变量width、height,这两个变量是用来设置我们游戏窗体的大小的。

2. 接着,用width和height变量设置窗体的宽和高。这段代码也设置了窗体的标题。

3. 最后,你初始化了PyGame的时钟,这个时钟将会用来追踪游戏中的时间。

接下来,我们添加update()方法,这个方法每隔一段时间更新一次游戏,包括重绘界面和接收用户输入。将以下的代码添加在__init__方法之后就能实现这些功能(update代码的左缩进必须和__init__相同):

这是一个简单的循环方法,这个方法定期清除窗体中的内容并检查用户是否想退出游戏。稍后,你会在这个方法中添加更多的内容。目前,你运行这个Python文件并不会见到什么效果,因为现在你做的仅是定义了一个名为BoxesGame的类。你还需要创建这个类的对象,然后使用这个对象运行游戏!

现在,我们已经有了update方法,让我们添加运行游戏主类的方法吧。之后,你会在这个游戏中添加一些基本的图片,例如绘制游戏中的棋盘。

在源文件的末尾添加这些代码来运行我们编写的游戏(代码的左缩进必须与文件的左缩进相同):

这三行代码体现了面向对象程序设计的一个优点:真正让程序运行的代码其实只有三行。

至此,整个源文件的内容应该是这样的:

就这样,是不是很简单?现在,让我们来运行这个游戏:

正如你看到的那样,游戏运行的结果就是看到一个令人非常印象深刻的黑色界面。

也许你现在并不理解这些代码,但是游戏的编写就像是一个战略过程。把自己想象成一个建筑设计师来编写游戏。你刚刚就为你的建筑打下了一个坚固的地基。每个雄伟的建筑都有一个良好的奠基,所以在你开始编写游戏之前,需要首先做好规划。

让我们添加另一个方法。如果你不记得什么是方法的话,那么你可以再看看教程中的“面向对象程序设计简介”这部分内容。

 

在游戏界面中绘制棋盘和线段

PyGame将窗体的左上角的坐标定义为(0,0)。所以我们为Boxes中的网格点定义一个坐标系统,其中(0,0)表示左上角的点,(6,6)表示右下角的点:

需要用某种方法来表示游戏中所有可能的线段。在游戏中,我们有垂直和水平的两种线段。让我们考虑一下,如何用一个列表来表示所有的线段集合,这是一个可以表示所有垂直和水平线段集合的方法:

从程序设计的角度来说,一个列表也被称为一个数组。当你的列表元素也是列表时,这个列表就被称为二维数组,例如水平和垂直线段的集合。

举一个例子,如果要表示从(0,0)点到(1,1)点水平方向上的路线,那么,就应该在“horizontal lines”这个列表中选择第0行,第0列的元素来表示。

请注意,“horizontal lines”列表有6行7列,而“vertical lines”有7行6列。(这里按照作者的代码,horizontal lines应该是7行6列,vertical lines是6行7列)

将下面两行代码添加到__init__代码中,来定义“horizontal lines”和“vertical lines”这两个数组:

[valuePerItem for x in y],这个语句可以快速创建一个数组。上述代码在创建数组的同时还将数组的元素初始化为False。False代表一个空的区域。

至此,你就有了表示棋盘的方法了,接下来我们来看看怎样用代码画出这个棋盘。

首先,新建一个名为initGraphics()的方法。这个方法将被__init___方法调用,但为了使你的代码结构保持清晰,我们将载入图片的代码封装到其它方法中。在__init__方法之前,添加这个代码:

正如你看到的那样,我们有三个垂直的线条图像:一个普通(空)线条,一个已被画过(占用)的线条和一个悬浮效果线条。将这些垂直的线条图像旋转90度,就可以表示水平的线条了。线条的图像保存在你下载的项目资源文件夹里,并且它们必须和你的python文件同处一个目录下。

你已经有了一个载入图像的方法,但是你需要调用它。猜一猜应该在哪添加调用的代码?

当你有了答案时,点击下面的“show”按钮,看看你是否答对了。

在__init__方法的末尾添加这个代码:

接下来,你应该添加绘制棋盘的代码。若要循环整个棋盘上的x坐标和y坐标,你就必须在一个for循环中再嵌套一个for循环。每一个for循环可以循环x或y方向上的所有点。在__init__方法之后添加这段代码:

这段代码分别循环了垂直的和水平的线段组成的列表,并检查每个线段是否被点击选中了。self.boardv[y][x]和self.boardh[y][x]返回true或false取决于这条线段是否被玩家选中了。

现在执行这个程序仍然不会有任何作用。现在你完成的代码仅定义了在drawBoard这个方法被调用时,drawBoard所要做的事情。现在让我们在update方法中添加对drawBoard的调用吧。在清空窗体的语句screen.fill(0)后添加下面的代码:

当然,作为一个优秀的程序员,请记住在你的代码中添加注释,解释你刚才写的代码。

现在可以运行你刚写的代码了,程序运行后,你将看到界面上出现了你绘制的网格:

每次我写绘制图片的代码时,我都会对这些代码做一点测试,因为这样很有趣,而且也能发现一些BUG。在定义self.boardh和self.boardy的代码的后面添加这个代码:

运行修改后的代码,你将看到,从(5,3)到(5,4)这条水平的线将会亮起:

很酷对吧?现在可以删除我们刚才添加的测试代码了。好,现在你已经成功完成棋盘的绘制了,这是我们游戏编程中的难点之一。

添加其它类型的线条

下一步,你需要找到离鼠标指针最近的一个线条,然后在那个位置绘制一个有悬浮效果的线条。

首先,在代码源文件的顶部写下这行代码,来导入我们马上用到的数学库:

然后在pygame.display.flip()之前,添加以下这一大段代码:

哇,好长一段代码。我们一段一段来看看这些代码吧:

1 首先,你通过PyGame的内建函数来获得鼠标指针的位置。

2 接下来,在前面的代码中,我们将网格的大小设置成了64×64,所以我们可以计算获得鼠标指针在棋盘网格中的位置。

3 你需要检查你的鼠标指针是更接近方块的上边、下边还是是方块的左边、右边。因为你需要判断用户的鼠标是悬浮在一条水平的线条上还是垂直的线条上。

4 通过is_horizontal这个变量,计算线条的新位置。

5 用boardh或boardv初始化board变量。

6 最后,你需要将悬浮效果的线画到界面上,你必须考虑是画水平的线还是垂直的线,以及这个线条画在一个方块的上边、下边、左边或是右边。你还必须检查你画的线条是否超出了棋盘的边界。如果线条超出了边界或线条已经被画过了,那么你就不需要在这样的线条上添加悬浮效果。

运行这个程序,你会惊喜地发现,当你的鼠标靠近一个线条时,线条就会亮起。

如果你像我一样,现在就一定会激动得将鼠标在屏幕上移来移去。现在就请尽情享受你的成果吧!

好,现在当用户鼠标移到网格上的某线条时,那条线就会亮起。但是,你所写的不仅仅是一个一直移动鼠标的游戏。你还需要增加这样一个功能:当鼠标点击一个线条时,这个线条就表示被玩家画过,也就是玩家拥有了这个线条。

要完成这个功能,你需要使用PyGame的内建函数:pygame.mouse.get_pressed()[0]。这个函数返回1或0取决于玩家是否点击了这个线条。在我告诉你如何实现这个功能前,你可以仔细想一想应该怎样做呢?回想一下,我们刚才是怎样使用if语句的,以及怎样在界面上绘制网格的。

将这些代码直接添加到刚才那段代码的后面:

现在运行修改过的程序,如果你点击鼠标,你将会在线条悬浮的地方画上这个线条。正如你看到的,你的代码完成了这些工作:你放置的线条取决于鼠标是否被单击,以及线条是水平的还是垂直的。

这个代码有一个问题,如果你在棋盘网格的下方点击鼠标,那么我们写的游戏将崩溃。让我们看看造成这个现象的原因吧。通常,当一个错误发生时,你能在终端上看到一个错误报告,这里我们遇到的错误报告是这样的:

这个错误是因为boardh数组越界了。还记得那个isoutfobounds变量吗?这个变量可以为我们解决数组越界的问题,只要简单地像下面这样修改一行代码即可:

现在,如果你在棋盘网格外点击鼠标,游戏也不会崩溃。好的,你刚才就算做了调试的工作!

在你实现游戏的服务端逻辑代码前,我们先在客户端添加一些最后的润色代码。

最后的润色

有一个问题一直困扰着我,那就是界面上网格线交叉部分的空缺。幸运的是,你可以很容易地用一个7×7大小的图片来填补这个空缺。当然,你需要一个图像文件,因此,我们现在就一口气载入这张图片以及项目中你需要使用的所有图片吧。

在initGraphics()后添加这些代码:

现在,你载入了需要的图片文件,让我们将载入的图片绘制到这49个空缺点上吧。将以下的代码添加到drawBoard():

好的,现在我们来测试一下游戏吧。游戏运行后,你将会看到一个更好看的网格棋盘。

接下来,让我们在游戏界面的下方放置一个平视显示器(HUD)吧。首先,你需要新建一个drawHUD()方法。

这段代码是在游戏界面的背景上绘制得分的面板。

让我说明一下PyGame处理字体的三个步骤:

1 首先,你需要设置一个字体以及这个字体的大小。

2 接下来,你调用font.render(“your text here”),使你输入的文本以你设置的字体呈现。

3 然后,将这些字像图片那样绘制到游戏的界面上。

现在,你已经知道怎样在界面上放置文本了,我们就可以绘制HUD的另一部分:“Your Turn”提示文字。在drawHUD()方法的尾部添加以下的代码:

同样在pygame.init()的后面添加这行代码:

刚才添加的代码建立了字体,将字体的颜色设置成白色,然后将这些字绘制到游戏界面上。在运行游戏之前,将这行代码添加到self.drawBoard()的后面:

运行这个程序后,你会看到“Your Turn”这个文本出现在游戏界面的下方。如果你仔细看游戏界面的下方,你会发现精细的背景质地。

这很棒不是吗,但是你仍然需要在“Your Turn”之后添加一个指示图标,来提醒玩家轮到他们了。

在做这之前,你需要让游戏知道,这个游戏轮到谁了。所以你需要在__init__代码的尾部添加这行代码。

将这行代码添加到drawHUD()的尾部,以在界面上绘制指示图片。

现在运行游戏后,你将会看到绿色的指示器。好了,你可以把这个任务从你的未完成事项中去除了。

接下来,让我们创建每个玩家的分数文本。使用如下的代码初始化两个玩家的分数变量,将这些代码添加在__init__的末尾:

这里,你需要添加一些其他你会在本阶段使用的变量。还记得怎样添加文本吗?现在你将重复写一些刚才写过的代码,但是使用的是不同大小的字体。将这些代码添加到drawHUD()的尾部:

运行程序,检查一下你的成果吧。

现在你已经完成了HUD的功能。现在还需要做一些工作来完善客户端,原谅我吧……

接下来,我们添加一个简单的变量来表示玩家所拥有的网格线。这些变量能让你跟踪玩家所拥有的方格。你需要使用这个变量正确地给玩家所拥有的方块上色并记下玩家得分。请记住,拥有最多方格的玩家将获得游戏的胜利!

首先,在__init__的末尾初始化一个数组:

和之前画网格线的方法一样,循环二维数组在屏幕上绘制玩家所拥有的网格。在类的底部添加这个方法。

这个方法判断每一个给定的方块是否需要着色,如果需要,方法将给方块画上指定的颜色(每一个玩家都有自己的颜色)。

你还需要给游戏增加一个胜利和失败的界面。下面的方法就能完成这个功能,将它添加到类的底部:

当然,现在你还没有办法在游戏中触发这个界面。在下一部分的教程里,当你实现了游戏的服务端后,你就可以查看这个界面了。

请记住,在你添加完这些界面后,游戏的服务端就可以任意操纵客户端了。除了服务端和客户端之间的通信问题需要一点处理外,从现在开始,你不需要对客户端进行改动了。

但是,为了确保我们刚才编写的方法是正确的,我们在__init__方法的尾部调用finished()。你将看到一个向上面那幅图一样的game over的画面。

接下来要做什么呢?

这是到目前为止,本教程的源代码可在这里找到source code

恭喜你!你已经完成了一个整洁而漂亮的游戏客户端。当然,虽然你出色地完成了游戏的客户端代码,但是并没有实现任何游戏的逻辑,所以我们的开发工作并没有结束。

现在你需要进入我们教程的第二部分,教程的第二部分主要讲述游戏的服务端,随着教程,你将开始制作真正的多玩家游戏。

9 收藏 1 评论

关于作者:justyoung

可怜的小硕 个人主页 · 我的文章 · 10

相关文章

可能感兴趣的话题



直接登录
最新评论
  • t t   04/06

    我怎么也打不开,一直在加载,怎么打开三个终端呢,服务器编号是多少呢

跳到底部
返回顶部