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

欢迎回到我们面向青少年的多人游戏编程教程第二部分!

在教程的第一部分,你完成了游戏客户端的大部分代码。你编写的代码在游戏界面上绘制了网格线,并允许玩家在网格线上放置新的线条。

在教程的第二部分,也是本教程的最后一部分,你将完成游戏服务端的编写,通过客户端与服务端的连接通信,你将能实现多玩家游戏。让我们开始吧!

准备开始吧

制作一个多玩家游戏的基本思想是这样的,你和你的朋友都需要一个游戏客户端,你们的客户端程序需要与一个服务端程序连接,这个服务端协调客户端程序之间的通信。

你将使用PodSixNet模块来为Boxes游戏创建服务器程序,PodSixNet是Chris McCormick创建的一个简单、轻量级的Python网络库。从外部看,PodSixNet能让玩家与服务器程序通信。你将会发现使用PodSixNet你能容易地完成你所要的网络操作。在这里你可以学习更多关于PodSixNet的知识。

PodSixNet是这样工作的:首先你在一个指定的端口上运行定制的PodSixNet服务器程序(你可以将它想象成一个带有指定的地址的邮箱)。然后你运行客户端程序,客户端程序通过这个端口与服务器程序通信。

我已经将PodSixNet的源代码放置在你之前下载的项目资源文件里了。但是,如果你想下载一个新的PodSixNet版本,你可以在这里下载:PodSixNet-78

进入你解压的文件夹,你将看到一个setup.py文件。按照下面的提示,将PodSixNet安装到你的电脑上。

使用Mac的用户:

打开一个新的终端,然后键入cd .(注意命令cd后有一个空格)。将包含PodSixNet解压文件的文件夹拖到终端窗口,然后按下回车。再终端上输入sudo python setup.py install,然后按下回车。这样就能在你的电脑上安装PodSixNet了,安装完成后你就可以在电脑的任意位置使用PodSixNet了。(下载的文件夹里没有包含setup.py文件,需要下载最新的PodSixNet-78)

对于Windows用户:

在你解压PodSixNet文件的路径下,按住shift键,并且用鼠标右键点击目录空白处,然后选择在此处打开命令行窗口。如果你已经配置好了Python的环境变量,那么只需要在命令行窗口中输入python setup.py install这条命令就可以了。如果这条命令的返回结果是“Command Not Found”错误,那么你需要配置一下环境变量了。

按照如下的方法将Python添加到你的环境变量中,右键点击我的电脑,选择属性。然后选择高级选项卡,点击环境变量按钮。在系统变量下点击新建,将Python的安装路径添加进系统变量中如C:python2.7。这样,就可以使用python setup.py install命令来安装PodSixNet了。

来测试一下你的安装是否正确,通过在终端上输入python来启动Python解释器,或者进入DOS的命令提示符,然后按下回车键。输入import PodSixNet,再按下回车。如果没有得到什么提示,那么你的安装就是正确的。如果终端提示了一些错误,可以把错误提示贴在这篇博客的评论里,来寻求帮助。

PodSixNet架构

让我们开始创建一个PodSixNet的框架吧。在这个项目中,当你需要使用PodSixNet时,可以使用这个框架,请确保你的各个组件能正确运行。

新建一个名为server.py的文件,并将这个文件放在和boxes.py相同的目录下。然后在这个文件中加入如下的代码:

这段代码建立了一个简单的连接模型,它在默认的端口上监听连接的到来。当有人连接了服务器,它将打印一条消息。

这段代码本应是立即可以使用的,但是它目前还不能使用,因为你还没有编写客户端代码。客户端代码的修改工作是简单的。你需要做的只是初始化客户端的PodSixNet类,利用它连接服务器,然后你的客户端就能向服务器发送许多信息了。

将以下的代码添加到boxes.py文件的顶端,来导入一些需要使用的PodSixNet和timing库:

将class BoxesGame():这行代码修改为如下这行代码,使得BoxesGame继承ConnectionListener:

将以下这行代码添加到BoxesGame类中init函数的最后一行,来初始化PodSixNet客户端:

将下面这段代码添加到update函数的开始处,来连接客户和服务器,等待新的事件或消息:

现在,打开两个终端窗口。使用Python在一个终端中运行boxes.py,在另一个终端中运行server.py。当你启动客户端时,服务器端会提示你,一个新的客户成功连接了。

加入多玩家功能

现在你的服务器和客户端可以互相通信了,真棒!但是你还需要做一些工作来完善游戏。让我们给游戏添加一些功能吧。首先,当你在客户端界面放置一条线时,你需要将这个信息告诉服务器。在update方法里找到你绘制线条的代码,将那部分代码像下面这样修改:

请注意这个代码引入了新的属性self.gameid和self.num。将以下这两行代码添加到__init__函数中,并把它们初始化为None:

现在运行服务器和客户端代码,然后在客户端上点击一条横线。服务端会记录下你在客户端点击的线的信息。

编写游戏类

接下来,你将实现游戏类,这个类将表示游戏的所有元素:一对客户端,游戏网格面板和现在该轮到谁操作了。

在server.py文件的BoxesServer类后添加如下的代码:

这个类表示了游戏的状态。服务器是这个游戏的“长官”,它会控制每个客户端界面的更新与显示。

当第一个用户连接时,服务器应新开始一个游戏。服务器将有一个游戏列表以及玩家的等待队列,这样当有一个客户连接时,服务器就能知道是应该新开始一个游戏,还是让新连接的玩家和一个正在等待的玩家一起玩。现在,让我们来添加这个功能吧。

在BoxesServer类的开始处添加以下这些代码吧:

看到那行很奇怪的以PodSixNet开头的代码了吗?因为你在BoxesServer类里继承了PodSixNet.Server.Server类,所以,你需要在BoxesServer类的__init__方法里调用父类的__init__方法。这就是对PodSixNet的server类的初始化,初始化时需要把所有它需要的参数传给它。currentIndex变量用来记录那些正在进行的游戏,当每个游戏开始时,它就会加1。

让我们添加下列这些代码吧,使新连接的玩家加入等待队列或让他与正在等待的玩家一起进行游戏。将下面这部分代码添加到Connected()方法的末尾。

正如你看到的那样,服务器首先检查是否有一个游戏在等待队列中。如果没有,服务器将新开始一个游戏,然后将新开始的游戏加入到队列中。这样,当下一个用户连接时,这个用户将被分配到刚创建的游戏中。

考虑一下这个问题:当玩家在游戏的网格界面上选择了一条线,服务器将知道玩家放置线的位置。但是,当很多个游戏同时进行时,服务器并不知道当前的玩家属于哪一个游戏。因此,服务器就不知道该更新哪一个游戏的网格界面,也不知道该通知哪一个用户,他的网格界面应该改变了。

为了使服务器能得到上述的信息,在你将用户分配到一个游戏时,你首先应给用户一个gameid。你可将gameid作为“startgame”信息中的一个参数传递给用户。这也将作为你提醒用户游戏已经开始的信号。

在游戏开始前,你需要让用户等待“startgame”信息直到这个信息的到达,然后你需要决定,哪一个玩家先进行游戏。这将告知游戏的两个玩家,游戏开始了,他们都有一个特定的游戏id。让我们接下来做这个功能吧。

在客户端的代码中添加如下的方法:

你希望客户端保持等待状态,直到收到开始游戏的消息。因此,在__init__方法的末尾添加如下的代码:

两个玩家,同一个游戏

还记得我说的那个还不能使用的功能——drawOwnermap吗,它用指定的颜色绘制每个方格。现在它可以了,因为客户端能知道你在网格线上是蓝色的或绿色的了。

现在运行游戏吧。这一次,你需要打开三个终端窗口——一个用来运行服务器程序,另外两个运行客户端程序,因为如果没有两个玩家同时在线,游戏就不会开始。现在还没有什么其他的效果,但是至少两个玩家的游戏连接到了同一个服务器。

1

现在我们来快速实现放置线条的功能。首先,你需要在server.py文件里的Game类中添加一个方法,当用户在网格上放置一条线时,它将发挥作用。Game类首先会检查游戏现在是否轮到当前玩家,如果是,那么它会更新两个玩家的游戏网格界面,将当前玩家放置的线条添加到两个游戏玩家的界面中。

将下面这个方法添加到server文件的Game类中:

这段代码首先检查玩家的动作是否有效,即游戏是否轮到该玩家,如果有效,则将玩家的动作发送给两个玩家,更新他们的网格界面以及轮次。接下来,你需要使服务器能够调用我们刚写的方法,添加下面这段代码到BoxesServer类中:

这段代码循环遍历所有游戏,找到gameid与当前玩家相同的游戏。然后它调用Game.placeline()方法,将界面更新的消息发送给客户。

你还有最后一个方法需要添加到server文件的ClientChannel类中。

你已经看到了当玩家在客户端游戏界面上放置一条线时,服务器打印出来的信息,这段代码将会读这些信息,从这些信息中抽取出每一个参数,然后调用server的placeLine方法。

现在游戏的服务器能够向客户端发送信息了。但是,仍然还有一个大问题:客户端还不能处理这些信息。让我们给客户端代码添加一个方法来解决这个问题吧。

将下面的代码添加到客户端代码中:

客户端收到放置线条信息时将调用这个方法。它从信息中读出参数,然后根据情况更新游戏状态。

现在,尝试运行我们的游戏。你放置的第一条线将会在另一个客户端界面上看到(但是后面放的线条将不会,别急,你将马上能解决这个问题)。现在,你已经完成了第一个多玩家服务器!你可以证明,这并不是一个简单的事情,回头看看到目前为止,你所做的工作吧!

轮流玩

接下来你需要实现游戏的轮流功能,这样玩家才不能在游戏中作弊。信不信,你已经为这个功能建立了一个变量(turn)。但首先,你需要一个延时功能,玩家放置一条线后,它将等待10帧才允许玩家在界面中放置下一条线。

在boxes.py的__init__方法中添加下面这个变量:

你要使这个变量在每一帧后减1,在玩家放置一条线后重置为10。将以下的这个代码做一些改动:

代码检查了当前是否轮到该玩家进行游戏,并且保证他在10帧内不能再次放置线条。

接下来,在update方法的顶部添加下面这行代码:

这样在游戏的每一帧,justplaced变量都能减1。现在你还要保证当用户放置一条线时,需要将justplaced变量重置为10。将这行代码添加到刚才修改的if语句中:

好的,现在你可能注意到了一个问题,两个玩家的轮次指示标志一直是绿色的!这是因为你还没有添加控制颜色转换的代码。

在drawHUD()方法中找到绘制指示标志indicator的方法screen.blit。将它改成下面这样:

再一次运行游戏——其中一个玩家的indicators指示标志将是绿色或者红色。

2

这个指示标志是正确的,但是当你放置一条线时,游戏显然应当切换轮次。但目前,还没有实现。我们现在快速编写服务器代码来添加这个功能吧。

在服务端,当任何事件发生时,你都需要发送给客户一条信息,告诉客户端,现在轮到哪个玩家进行游戏了。你可以简单地在server.py的Game类中添加placeLine方法来完成这一功能。将下面这行代码添加到更新turn变量的if语句中:

tof代表了“true or false”。这个变量将告诉客户端,现在是否轮到他们进行游戏。在游戏开始时,你不需要发送这些信息,因为客户端根据玩家的number知道了谁应该第一个放置线条。

让我们在客户端实现“轮到你了”的指令。这个是一个非常简单的事情,在客户端的Game类中,添加这一方法。

现在再次运行服务器程序和两个客户程序。你必须重启一下服务器程序,因为你对它进行了修改。

4

现在你可以看到,玩家必须按照次序轮流进行游戏了,很不错对吗?现在你已经告诉客户端怎样按照顺序轮流进行游戏,你还应该奖励玩家“努力的成果”,那就是填充方块的颜色!

编写游戏的逻辑

这个游戏有一个简单的逻辑:在玩家进行游戏时,判断他是否完成了一个方格。服务器通过循环所有可能的方块来查找被玩家完成的方块。

在BoxesServer类中新建一个名为tick()的方法。将下面的代码添加到方法中:

哇,有许多代码啊!让我们拆开这些代码,逐个看看是什么意思吧。

  1. 在方法的最顶部,你声明了一些变量:index,这个变量常用在for循环中,用来跟踪你遍历到的当前变量,change,告诉你现在是否要改变玩家的轮次,以及现在轮到谁玩游戏了。
  2. 接下来循环遍历所有的游戏列表,将change变量重置为3,代表没有变化发生。
  3. 然后遍历所有可能的方块。你需要遍历两次,因为一个玩家有可能在两个方块的中间放置了一条线,这样一次就同时完成了两个方块。
  4. 对于每一个可能的方块,你要检查这个方块是否被绘制完成,如果是,还要确保这个方块不是在之前的游戏轮次中就被完成的。
  5. 最后,你还要检查是哪一个玩家在完成一条线的放置后完成了一个方块,然后正确的设置variable变量。

现在,你已经有了tick这个方法,你需要将它添加到server中。这非常简单,到server.py的底部,找到下面这段代码:

把它改为:

现在,你已经有了一些游戏逻辑代码在server服务器上,让我们在client端添加一个方法,来告诉客户端是它赢了一个方块还是输了一个方块(也就是说对方赢了一个方块)。

在客户端中添加以下两个方法:

这两个方法用来处理从网络上接收到的赢或输的信息,然后适当的更新游戏的状态。

再一次运行一个服务器和两个客户端吧,感受一下在游戏界面上同时显示两个客户端的信息。

3

游戏结束了!

等会儿,游戏什么时候才结束呢?你需要让服务器实现你在教程第一部分的最后添加的finish()方法。记得吗,这个方法在游戏的界面上显示玩家获胜或失败的画面,以及让玩家退出游戏。

将下面这几行代码添加到update方法的顶部:

这段代码查看你以及你的对手一共获得了多少个方块。如果总共获得了36块(网格线中总共包含的方块),那么游戏结束。如果游戏结束,那么查看哪个玩家获得的方块数最多,获得方块数最多的玩家赢得游戏胜利,然后返回1。

最后,在文件底部,找到bg.update(),然后将它改为下面这样:

在文件的最底部,添加这行代码,注意,这行代码不要有任何缩进:

现在,你能使游戏以某一玩家的胜利而终止了。但是,在测试这个功能之前,让我们给服务器和客户端再添加一个功能吧。当一个玩家退出了游戏,你要使游戏中的另一个玩家也退出游戏。

将下面的代码添加到ClientChannel类中:

然后将以下这部分代码添加到BoxesServer类中:

要使客户端能够理解close()命令,需要在客户端的类中添加如下代码:

再一次运行这个游戏吧,打开两个客户端,然后一直玩到你赢为止。这并不太难吧!

现在,你已经正式完成了这个游戏的开发。如果你想给游戏添加一些音乐和声音效果或让游戏更完美的话,那么就进入下一部分教程吧,教程的下一部分还将讲述游戏网络的连接。否则,可直接跳过本教程的下一部分,看看你的成果以及接下来可以做的工作。

最后的润色

在client客户端的主类(BoxesGame)里,添加下面这个方法:

这个代码载入了音乐和声效文件,这样你就可以在需要的地方播放它们。这些.wav文件存放在你在教程(上)中下载的资源包里。我用cxfr制作了这些音效,这些音效来自于Kevin MacLeod

现在在__init__中的initGraphics方法中添加如下这行代码:

然后在合适的地方添加下面这些代码:

这些代码将会在合适的地方发出音效。

再一次运行游戏,并确保你的电脑打开了声音。享受groovy曲调吧(这里不太确定)!

到此,你已经给游戏增加了音效,接下来,我们让游戏的玩家可以通过网络连接进行游戏,而不是只能在同一台电脑上玩。

将BoxesGame类中的self.Connect()方法替代为如下的代码:

这段代码让客户端确定如何查找服务器。在运行游戏客户端后,客户端将要求你输入游戏服务器的IP地址。

让我们修改一下server端的代码吧。将server.py文件中的boxesServe=BoxesServer()代码修改为如下:

最后再运行一次游戏,看看效果吧!

现在该做什么呢?

这是finished sample project教程里的最终代码。恭喜你!你用Python和PyGame完成了你的第一个多玩家游戏。希望你能在这个小项目中获得乐趣。

如果你对这个游戏有兴趣,还想继续完善游戏的话,以下这些建议你可以自己尝试一下:

  • 添加一些随机的“坏块”,当一个玩家获得这种“坏块”时,它将失去一分。
  • 随机地让一些方块的分值超过1分。
  • 让第一个进入游戏的玩家能够决定网格的规模(即网格中有多少个方块)。
  • 让玩家在他的轮次中,能移除其它玩家放置的线条。

对于这个教程,如果你有任何疑问或建议,可参与论坛的讨论。希望你愉快地用Python编程!

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

5 收藏 2 评论

关于作者:justyoung

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

相关文章

可能感兴趣的话题



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

    很好,有帮助

     

  • t t   04/06

    请问你有微信吗,wechat 有吗?我的微信名叫做tt 你的微信号是多少?,我希望可以加你微信,询问你几个问题,谢谢了。这真的是我第一个制作的多人游戏,很有帮助

跳到底部
返回顶部