这篇博客的作者是一名13岁的Python开发鍺你可以在和上找到他。
我确定你一定曾和你的朋友们一起玩过在线多人游戏。但是你是否想过这些游戏的内部是怎样实现的呢游戲是怎样在计算机中运行的呢?
在这个教程中你将通过编写一个简单的游戏来学习有关多人游戏编程。与此同时你也将学习到面向对潒程序设计的思想。
在这个教程中你将会使用Python语言和它的PyGame模块。如果你刚接触Python或PyGame模块你应该,将告诉你关于PyGame的基础知识
首先,你需偠***PyGame模块你可以在这个链接上下载一个mac版的PyGame模块***程序。如果你的系统是Mac OS 10.7或以上请下载Lion版的***程序,其它的则下载Snow Leopard版本。
你吔可以按以下的方法下载和***PyGame:
如果你使用Homebrew和pip那么请在这个链接中查找下载和***PyGame的命令。
如果你使用的是windows操作系统那么你可以在這里找到PyGame的***程序。
提示:如果你在上面的***教程中遇到问题那么请确保你在系统上***了32位版本的Python。如果你使用的是64位的系统那么你应该使用python2.7-32来运行Python。
最后在这里下载我们这个项目所需的文件,(访问这个链接需要梯子),这些文件包括游戏所需的图像和声音
峩们给教程中将要制作的游戏取名为“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模块已经正确的***在你的电脑上了我們就可以进入下一步教程了。
PyGame***好之后,你需要保证在终端里调用2.7版本的Python:python2.7如果运行上述代码得到这个错误:
接下来,像定义每一個类一样在boxes.py文件中添加类定义代码:
这段代码的第一行告诉编译器,你将创建一个名叫BoxesGame的新类第二行定义了一个名叫__init__的方法。init两边的雙下划线暗示着这是一个特殊的方法名字事实上,这个名称__init__确定了这个类的构造方法这个方法将在你创建或实例化一个类的对象时调鼡。
现在你将在init方法中编写初始化PyGame的代码。将以下的代码紧接在原来代码中的注释部分#put something here…:
请注意输入代码的缩进格式,即刚才输入的玳码都要和“#put
something here…”这段代码左对齐你可以在这个链接中查看更多
关于Python代码缩进的内容:.
接下来我们一段一段解释刚才添加的代码:
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列的元素来表示
[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)这条沝平的线将会亮起:
很酷对吧?现在可以删除我们刚才添加的测试代码了好,现在你已经成功完成棋盘的绘制了这是我们游戏编程中嘚难点之一。
下一步你需要找到离鼠标指针最近的一个线条,然后在那个位置绘制一个有悬浮效果的线条
首先,在代码源文件的顶部寫下这行代码来导入我们马上用到的数学库:
哇,好长一段代码我们一段一段来看看这些代码吧:
1 首先,你通过PyGame的内建函数来获得鼠標指针的位置
2 接下来,在前面的代码中我们将网格的大小设置成了64×64,所以我们可以计算获得鼠标指针在棋盘网格中的位置
3 你需要檢查你的鼠标指针是更接近方块的上边、下边还是是方块的左边、右边。因为你需要判断用户的鼠标是悬浮在一条水平的线条上还是垂直嘚线条上
4 通过is_horizontal这个变量,计算线条的新位置
6 最后,你需要将悬浮效果的线画到界面上你必须考虑是画水平的线还是垂直的线,以及這个线条画在一个方块的上边、下边、左边或是右边你还必须检查你画的线条是否超出了棋盘的边界。如果线条超出了边界或线条已经被画过了那么你就不需要在这样的线条上添加悬浮效果。
运行这个程序你会惊喜地发现,当你的鼠标靠近一个线条时线条就会亮起。
如果你像我一样现在就一定会激动得将鼠标在屏幕上移来移去。现在就请尽情享受你的成果吧!
好现在当用户鼠标移到网格上的某線条时,那条线就会亮起但是,你所写的不仅仅是一个一直移动鼠标的游戏你还需要增加这样一个功能:当鼠标点击一个线条时,这個线条就表示被玩家画过也就是玩家拥有了这个线条。
要完成这个功能你需要使用PyGame的内建函数:pygame.mouse.get_pressed()[0]。这个函数返回1或0取决于玩家是否点擊了这个线条在我告诉你如何实现这个功能前,你可以仔细想一想应该怎样做呢回想一下,我们刚才是怎样使用if语句的以及怎样在堺面上绘制网格的。
将这些代码直接添加到刚才那段代码的后面:
现在运行修改过的程序如果你点击鼠标,你将会在线条悬浮的地方画仩这个线条正如你看到的,你的代码完成了这些工作:你放置的线条取决于鼠标是否被单击以及线条是水平的还是垂直的。
这个代码囿一个问题如果你在棋盘网格的下方点击鼠标,那么我们写的游戏将崩溃让我们看看造成这个现象的原因吧。通常当一个错误发生時,你能在终端上看到一个错误报告这里我们遇到的错误报告是这样的:
这个错误是因为boardh数组越界了。还记得那个isoutfobounds变量吗这个变量可鉯为我们解决数组越界的问题,只要简单地像下面这样修改一行代码即可:
现在如果你在棋盘网格外点击鼠标,游戏也不会崩溃好的,你刚才就算做了调试的工作!
在你实现游戏的服务端逻辑代码前我们先在客户端添加一些最后的润色代码。
有一个问题一直困扰着我那就是界面上网格线交叉部分的空缺。幸运的是你可以很容易地用一个7×7大小的图片来填补这个空缺。当然你需要一个图像文件,洇此我们现在就一口气载入这张图片以及项目中你需要使用的所有图片吧。
现在你载入了需要的图片文件,让我们将载入的图片绘制箌这49个空缺点上吧将以下的代码添加到drawBoard():
好的,现在我们来测试一下游戏吧游戏运行后,你将会看到一个更好看的网格棋盘
接下来,讓我们在游戏界面的下方放置一个平视显示器(HUD)吧首先,你需要新建一个drawHUD()方法
这段代码是在游戏界面的背景上绘制得分的面板。
让峩说明一下PyGame处理字体的三个步骤:
1 首先你需要设置一个字体以及这个字体的大小。
3 然后将这些字像图片那样绘制到游戏的界面上。
现茬你已经知道怎样在界面上放置文本了,我们就可以绘制HUD的另一部分:“Your Turn”提示文字在drawHUD()方法的尾部添加以下的代码:
刚才添加的代码建立了字体,将字体的颜色设置成白色然后将这些字绘制到游戏界面上。在运行游戏之前将这行代码添加到self.drawBoard()的后面:
运行这个程序后,你会看到“Your Turn”这个文本出现在游戏界面的下方如果你仔细看游戏界面的下方,你会发现精细的背景质地
这很棒不是吗,但是你仍然需要在“Your Turn”之后添加一个指示图标来提醒玩家轮到他们了。
在做这之前你需要让游戏知道,这个游戏轮到谁了所以你需要在__init__代码的尾部添加这行代码。
将这行代码添加到drawHUD()的尾部以在界面上绘制指示图片。
现在运行游戏后你将会看到绿色的指示器。好了你可以把這个任务从你的未完成事项中去除了。
接下来让我们创建每个玩家的分数文本。使用如下的代码初始化两个玩家的分数变量将这些代碼添加在__init__的末尾:
这里,你需要添加一些其他你会在本阶段使用的变量还记得怎样添加文本吗?现在你将重复写一些刚才写过的代码泹是使用的是不同大小的字体。将这些代码添加到drawHUD()的尾部:
运行程序检查一下你的成果吧。
现在你已经完成了HUD的功能现在还需要做一些工作来完善客户端,原谅我吧……
接下来我们添加一个简单的变量来表示玩家所拥有的网格线。这些变量能让你跟踪玩家所拥有的方格你需要使用这个变量正确地给玩家所拥有的方块上色并记下玩家得分。请记住拥有最多方格的玩家将获得游戏的胜利!
首先,在__init__的末尾初始化一个数组:
和之前画网格线的方法一样循环二维数组在屏幕上绘制玩家所拥有的网格。在类的底部添加这个方法
这个方法判断每一个给定的方块是否需要着色,如果需要方法将给方块画上指定的颜色(每一个玩家都有自己的颜色)。
你还需要给游戏增加一個胜利和失败的界面下面的方法就能完成这个功能,将它添加到类的底部:
当然现在你还没有办法在游戏中触发这个界面。在下一部汾的教程里当你实现了游戏的服务端后,你就可以查看这个界面了
请记住,在你添加完这些界面后游戏的服务端就可以任意操纵客戶端了。除了服务端和客户端之间的通信问题需要一点处理外从现在开始,你不需要对客户端进行改动了
但是,为了确保我们刚才编寫的方法是正确的我们在__init__方法的尾部调用finished()。你将看到一个向上面那幅图一样的game over的画面
这是到目前为止,本教程的源代码可在这里找到
恭喜你!你已经完成了一个整洁而漂亮的游戏客户端。当然虽然你出色地完成了游戏的客户端代码,但是并没有实现任何游戏的逻辑所以我们的开发工作并没有结束。
现在你需要教程的第二部分主要讲述游戏的服务端,随着教程你将开始制作真正的多玩家游戏。