13歲Python開發者寫給青少年的多人遊戲程式設計(下)

justyoung發表於2014-12-09

歡迎回到我們面向青少年的多人遊戲程式設計教程第二部分!

在教程的第一部分,你完成了遊戲客戶端的大部分程式碼。你編寫的程式碼在遊戲介面上繪製了網格線,並允許玩家在網格線上放置新的線條。

在教程的第二部分,也是本教程的最後一部分,你將完成遊戲服務端的編寫,通過客戶端與服務端的連線通訊,你將能實現多玩家遊戲。讓我們開始吧!

準備開始吧

製作一個多玩家遊戲的基本思想是這樣的,你和你的朋友都需要一個遊戲客戶端,你們的客戶端程式需要與一個服務端程式連線,這個服務端協調客戶端程式之間的通訊。

你將使用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的命令提示符,然後按下Enter鍵。輸入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上找到他。

相關文章