繼以前用遊戲引擎(青瓷引擎)作了鬥地主單機版遊戲以後,這裏分享下使用socket.io來實現網絡對戰,代碼可已放到github上,在此談談本身整個的開發思路吧。javascript
客戶端代碼html
服務端代碼java
(點擊圖片進入遊戲體驗)node
前文連接:mysql
javascript開發HTML5遊戲--鬥地主(單機模式part1)git
javascript開發HTML5遊戲--鬥地主(單機模式part2)github
javascirpt開發HTML5遊戲--鬥地主(單機模式part3)sql
本文章爲網絡對戰第一部份內容。主要內容以下:數據庫
多人對戰遊戲,我用Nodejs作開發服務端,使用socket.io作通信。整個鬥地主遊戲流程是同樣的,只是單機版啥都要本身作,網絡版更多交給服務器,本身主要就是遊戲界面展現了,先看看整個服務器的大體結構:npm
服務器中用Nodejs的mysql模塊作mysql數據庫存儲,這裏並非很是必要的,只是我嘗試使用熟悉這個東西,這裏我只是存了個uid在客戶端瀏覽器緩存中,因此也不能真正達到持久化做用。若是要作持久化應該是要作個用戶登錄了,這裏引擎給咱們提供了微信支持,後面優化完善能夠考慮加入微信登錄來保存用戶數據。
對於Nodejs項目的搭建建,仍是比較簡單的,簡單說明下項目搭建,在工做區下建立個目錄landlordServer,做爲項目目錄。而後用命令行進入到該目錄下,加入Nodejs的socket.io和mysql模塊,命令以下:
npm install socket.io npm install mysql
出於以前編寫java服務端的習慣,我在項目目錄下建立了src目錄,用於放置代碼。在src下建立server.js做爲入口腳本,加入socket.io的端口監聽,而後就能夠用Nodejs來啓動了。這樣子咱們的服務端也算是搭建完成了,相對java還真是簡易,接下來就能夠寫邏輯代碼了。因爲用了Nodejs,小弟對這個也不算很是熟悉,不過它的模塊化用起來感受很像java類,一個文件一個類這樣的形式,這個類須要什麼就import進來,Nodejs是require,這樣的話就不用去考慮腳本前後的問題。
客戶端須要有socket.io.js的客戶端文件,這個文件在服務端項目中 node_modules/socket.io-client /路徑下,找到它並把它丟到客戶端Scripts下就能夠了,這裏我是放到了Scripts/operation目錄下,由於青瓷引擎也是基於Nodejs開發的,因此這裏只要在Scripts也不須要聲明其餘什麼引用便可使用。
在網絡對戰鬥地主中,都是三個玩家一桌,這樣的形式,有一些鬥地主遊戲還會把桌位合成再分爲房間,再到大廳。我這裏的實現只有桌位,一開始服務器啓動是沒有任何桌位的,有玩家進入建立桌位,後面的玩家進來就是進未滿員的桌位,找不到再建立新桌位,若是有一個桌位玩家所有離開了,就把該桌位刪除。這些事情都交給桌位管理器DeskMgr.js。我用的是一個desks對象來緩存座位信息,利用了js中對象能夠是當成一個Map使用。socket.io還提供了一個分組功能,差很少是一個房間的概念,我把每一桌的玩家都加入到同一個分組中,這個分組的標識也就是這個桌位的桌位號,固然玩家退出的時候也要退出該分組。每一個桌位有三個階段:
每一個桌位上都須要有3個座位,這裏不只是方便統計,也是爲了方便確認前後順序。同時是用一個對象當作Map使用來存放三個座位,分別名爲p一、p二、p3,這樣順序也就很容易確認,好比p1出了牌,輪到下家時,能夠寫個獲取下家座位號的方法,而後獲得p2,去通知p2出牌。
相比單機下的玩家信息,服務器上的玩家須要有其餘屬性:
整個遊戲流程跟單機模式是差很少的,只是網絡對戰是由服務器來作發牌、輪換等操做,至關三我的在鬥地主,有一個助手替他們發牌,告訴他們輪到誰搶地主,輪到誰出牌,誰贏了,這樣,細分下服務器作的操做以下:
玩家加入與退出:當有玩家加入的時候,服務器會返回給玩家的客戶端他所在桌位的信息,還有該玩家獲得的桌位號。這樣在客戶端就能夠顯示出整個桌位的信息了,其實就是顯示左右邊玩家的名字。除此以外,若是該桌位還有其餘玩家,還須要給其餘玩家廣播有新玩家加入的消息。退出(點擊退出、刷新、關閉頁面)時若是該桌位還有其餘玩家也須要進行廣播通知。
玩家準備:每次玩家切換準備狀態時判斷玩家所在桌位是否有3個玩家準備了,符合條件就能夠開始發牌了,這裏都準備後就已經進入了搶地主階段。發牌由服務器來進行,因此原有的發牌代碼還須要搬一份到服務器上去,發完牌後分別通知給每一位玩家,給玩家對應的手牌信息,還要隨機生成哪一位座位號玩家先開始搶地主,這樣客戶端收到開始遊戲信息後就播放發牌動畫,播放完畢後若是是本身先開始搶地主就顯示搶地主操做的按鈕。
搶地主:玩家叫分後將數據(主要是玩家叫的分數和玩家信息)發給服務器,服務器處理後,將當前叫分交給下一家,廣播給當前桌位全部玩家,通知他們上一家叫分多少,還有如今輪到誰叫分搶地主了。這樣的客戶端就能夠顯示相應信息,每一個玩家根據本身的座位號顯示上家叫分和當前叫分,是本身就顯示叫分的操做按鈕。肯定地主後再發生底牌信息給每一個玩家,客戶端接收到消息後將底牌顯示出來。
退出/斷線:在這個階段退出的話我作的處理是直接結束遊戲回到準備階段。由於地主尚未肯定,也沒法進行出牌,直接結束是比較好的作法,再讓AI幫離線玩家叫分也是能夠的,這就看開發者想怎麼作了,對於離開的玩家也能夠進行減分處罰。
玩家出牌:出牌階段的輪換跟邏輯搶地主差很少,主要是在客戶端渲染會有所差別。玩家將出的牌發給服務器,服務器判斷玩家出牌後是否還有手牌,有就繼續下家出牌,沒有就斷定該玩家勝利。這跟單機版邏輯都是同樣的,只是這裏服務器來作控制了。
退出/斷線:在出牌階段退出後,只有該桌位還有在線玩家,遊戲就不會結束,離開的玩家會有AI代爲出牌。爲了讓斷線玩家會有回來的時間,AI第一次出牌會延遲出牌,這個使用定時器很容易實現。等玩家重連以後若是這個計時器還沒執行再去把這個計時器取消了。
斷線重連:這個問題一開始我比較沒思路,本身想了個方法實現,不知道算不算好的。實現思路:在出牌階段,有玩家離開了,就將這個玩家加入到一個離線列表中。當有玩家加入遊戲後,再也不是直接分配桌位,而是先去查離線列表,若是找到了,就進到原來的桌位,沒有找到再去匹配桌位。這樣找到了桌位信息,就能夠在客戶端顯示當前遊戲的情況。玩家要是直到遊戲結束都沒有重連,遊戲結束時將本桌離線的玩家從離線列表刪除。或者一桌玩家都離線了,那就所有都從離線列表刪除。
有玩家完牌後,服務器給全部玩家廣播有人遊戲結束,發送計算完後的玩家分數、沒出完玩家的手牌信息以及勝利玩家最後出的一手牌,該桌位回到準備階段。客戶端個人處理是先顯示剩下手牌,最後一手牌等信息,添加個定時器,3秒後遊戲界面才渲染成準備階段狀態。
我在開發過程當中,遇到過個問題,控制檯一直提示死循環,在報錯的地方查了很久,也沒發現異樣。後面跟進socket.io源碼才發現,是當我要發送給客戶端的數據中,有一個循環引用的問題,其實在我裏面有個對象存着計時器,這樣將這個數據對象轉換成json時,轉換代碼就會陷入死循環。這個問題雖然不大,我也知道這種狀況會引發轉換異常,可是不容易發現,一開始沒有引發個人注意,在此記錄下。網絡對戰的鬥地主就介紹到這裏,有須要改進望提出,互相學習。