HTML+JS+websocket 實(shí)例谒拴,聯(lián)機(jī)“游戲王”對戰(zhàn)(十一)- 客戶端消息的收發(fā)

目錄

HTML+JS+websocket 實(shí)例尝江,聯(lián)機(jī)“游戲王”對戰(zhàn) 1
HTML+JS+websocket 實(shí)例,聯(lián)機(jī)“游戲王”對戰(zhàn) 2 - 聯(lián)機(jī)模式
HTML+JS+websocket 實(shí)例英上,聯(lián)機(jī)“游戲王”對戰(zhàn) 3 - 界面布局
HTML+JS+websocket 實(shí)例炭序,聯(lián)機(jī)“游戲王”對戰(zhàn) 4 - 卡組系統(tǒng)
HTML+JS+websocket 實(shí)例,聯(lián)機(jī)“游戲王”對戰(zhàn) 5 - 卡片選中系統(tǒng)
HTML+JS+websocket 實(shí)例苍日,聯(lián)機(jī)“游戲王”對戰(zhàn) 6 - 卡片放置惭聂,戰(zhàn)場更新
HTML+JS+websocket 實(shí)例,聯(lián)機(jī)“游戲王”對戰(zhàn) 7 - 墓地相恃,副控制面板
HTML+JS+websocket 實(shí)例辜纲,聯(lián)機(jī)“游戲王”對戰(zhàn) 8 - 返回手卡,卡組
HTML+JS+websocket 實(shí)例拦耐,聯(lián)機(jī)“游戲王”對戰(zhàn) 9 - 實(shí)現(xiàn)簡單 websocket 通信
HTML+JS+websocket 實(shí)例耕腾,聯(lián)機(jī)“游戲王”對戰(zhàn) 10 - 搭建游戲服務(wù)端
HTML+JS+websocket 實(shí)例,聯(lián)機(jī)“游戲王”對戰(zhàn) 11 - 客戶端消息的收發(fā)
HTML+JS+websocket 實(shí)例杀糯,聯(lián)機(jī)“游戲王”對戰(zhàn) 12 - 消息發(fā)送具體場景
HTML+JS+websocket 實(shí)例幽邓,聯(lián)機(jī)“游戲王”對戰(zhàn) 13 - 實(shí)機(jī)演示

客戶端消息的發(fā)送與接收

上一章我們介紹了服務(wù)端的搭建,用于接收并轉(zhuǎn)發(fā)消息火脉,這章來實(shí)現(xiàn)客戶端的聯(lián)機(jī)機(jī)制牵舵。客戶端的聯(lián)機(jī)分為發(fā)送和接收倦挂,當(dāng)玩家執(zhí)行某些操作時(shí)會發(fā)送一條 message 通知另一位玩家(由服務(wù)端做中介轉(zhuǎn)發(fā))畸颅,同樣,客戶端也可以接收另一位玩家的 message方援,執(zhí)行某些操作没炒。

在聯(lián)機(jī)之前我們需要兩個基本變量:

var playerID = "player1";  //獨(dú)立玩家ID
var ws = new WebSocket("ws://localhost:9999/");

玩家ID以及服務(wù)端的地址。這里由于方便測試寫的本地地址犯戏,如果服務(wù)端在其他設(shè)備上運(yùn)行可以改成那臺設(shè)備的ip地址送火。

之后我們定義一個消息發(fā)送函數(shù) wsSend

function wsSend(content) {  //由于傳輸?shù)膍essage類型多樣拳话,由各函數(shù)自行編碼后傳遞
    if (ws.readyState === WebSocket.OPEN) {
        ws.send(content);
        console.log("message sent");
    }
}

該函數(shù)只負(fù)責(zé)將其他函數(shù)傳來的消息內(nèi)容原封不動發(fā)給服務(wù)端。

然后在客戶端與服務(wù)端初次建立連接時(shí)种吸,會將自己的基本信息率先發(fā)過去讓服務(wù)端保存:

//初次與服務(wù)端建立連接時(shí)觸發(fā)
ws.onopen = function() {
    /*初次與服務(wù)端建立連接時(shí)告知玩家的pid讓服務(wù)器存下來 */
    var message = JSON.stringify({
        "type": "connection",  //向服務(wù)器告知的消息類型
        "pid": playerID,  //向服務(wù)器告知的本玩家ID
    });
    wsSend(message);  
}

type 設(shè)為 “connection” 告知服務(wù)端這是初次連接弃衍,pid 是客戶端的基本信息,如果有需要我們還可以設(shè)置更多信息內(nèi)容坚俗。message 編輯好后由 wsSend 函數(shù)發(fā)出镜盯。理論上這應(yīng)該是建立連接后客戶端發(fā)送的第一條消息,接下來客戶端就可以發(fā)送常規(guī) message 了猖败。

在前面第二章聯(lián)機(jī)模式里我們提到過速缆,客戶端接收對方消息后一般就是把對方執(zhí)行的操作復(fù)現(xiàn)一遍,同步到我們自己的界面中來恩闻。比如對方通常召喚一只怪獸時(shí)艺糜,會向戰(zhàn)場上放置一張怪獸卡片,同時(shí)對方的 message 中會告知我們對方放置了什么樣的卡片(圖片url)幢尚,以及放置在哪里(卡槽id)倦踢。我們將根據(jù) message 的內(nèi)容調(diào)用戰(zhàn)場更新函數(shù) updateField 來復(fù)現(xiàn)此操作,這與我們自己召喚怪獸幾乎無異侠草,只是放置對象是對方場上的卡槽辱挥,也就是戰(zhàn)場界面的上半部分。

game_ui_areas.png

針對如上述例子所說的不同功能場景边涕,我們會編輯不同種類的 message晤碘。首先所有 message 都必帶 type,pid 與 msgtype 三個變量功蜓,前兩者用于服務(wù)端的傳輸識別园爷,而 msgtype 則是告知對方需要執(zhí)行那種類型的操作,目前我們共有三種類型:updateHand(更新手牌)式撼,updateField(更新戰(zhàn)場)童社,updateTomb(更新墓地),下面來逐一介紹著隆。

消息發(fā)送

1. 手牌更新:

/**
 * 編碼令對方更新手卡的 message并發(fā)送
 * @param {string} updateType - updating type
 * @param {*} handNo - hand slot number
 */
function messageHand(updateType, handNo) {
    var message_hand = JSON.stringify({
        "type": "message",  //向服務(wù)器告知的消息類型
        "pid": playerID,  //向服務(wù)器告知的本玩家ID
        "msgtype": "updateHand",  //向?qū)Ψ酵婕腋嬷母骂愋?        "updateType": updateType,  //向?qū)Ψ酵婕腋嬷?減手卡
        "handNo": handNo  //向?qū)Ψ酵婕腋嬷桓碌目ú?    });
    wsSend(message_hand);
}

這是一個手牌更新的 message扰楼,在我方抽卡或從手牌打出卡片等手牌有變動的場景時(shí)會調(diào)用。除了前三個必帶的變量外美浦,后面的變量根據(jù)具體的功能特性來設(shè)置弦赖。

變量 值 & 含義
updateType add / reduce - 告知是增加還是減少手卡
handNo 手牌卡槽序號 - 告知是哪個卡槽有增減卡片

2. 戰(zhàn)場更新:

/**
 * 編碼令對方更新(我方/對方)戰(zhàn)場的 message并發(fā)送
 * @param {string} state - card state
 * @param {string} fieldID - updated card slot ID
 * @param {string} cardsrc - card img src
 */
function messageField(state, fieldID, cardsrc) {
    var message_field = JSON.stringify({
        "type": "message",  //向服務(wù)器告知的消息類型
        "pid": playerID,  //向服務(wù)器告知的本玩家ID
        "msgtype": "updateField",  //向?qū)Ψ酵婕腋嬷母骂愋?        "state": state,  //卡片放置狀態(tài)
        "fieldID": fieldID,  //需更新的卡槽ID
        "cardsrc": cardsrc  //放置的卡片src
    });
    wsSend(message_field);
}

這是戰(zhàn)場更新的 message,將在戰(zhàn)場有變化的場景中調(diào)用浦辨,比如召喚蹬竖,放置蓋覆卡等。同樣前三個是必帶變量,大家都統(tǒng)一币厕。

變量 值 & 含義
state attk / defen / back / on / off / change-on / change-off / change-back - 告知卡片更新后的狀態(tài)
fieldID 戰(zhàn)場卡槽 id - 告知是哪一個戰(zhàn)場卡槽需要更新
cardsrc 需要更新的圖片 url

上表的變量中列另,state 的 change-on, change-off, change-back 為特殊狀態(tài),用于表示卡片通過“更變形式”功能而變化成的“打開”旦装,“蓋覆”页衙,“被蓋召喚”狀態(tài),與我們常規(guī)從手牌向場上放置卡片的時(shí)的 on同辣,off拷姿,back 狀態(tài)有所區(qū)別(因?yàn)橛|發(fā)的音效不一樣)惭载。

事實(shí)上這三個變量在被對方客戶端接收后將原封不動的喂給戰(zhàn)場更新函數(shù) updateField旱函,關(guān)于這三個變量將如何被使用完全可以參考 updateField 函數(shù)中的內(nèi)容。

3. 墓地更新 :

前面的章節(jié)有提到過我們有專門用于存放我方墓地卡片的數(shù)組:

var P1Tomb = [];  //我方墓地(卡片src)

事實(shí)上描滔,我們還有用于存放對方墓地卡片的數(shù)組:

var P2Tomb = [];  //對方墓地

無論是我方還是對方墓地發(fā)生變化棒妨,我們都會及時(shí)同步。任何時(shí)刻雙方客戶端的墓地?cái)?shù)組都是相互同步的(當(dāng)然名字是反的含长,我方 P1Tomb 中的內(nèi)容到了對方客戶端就存儲在 P2Tomb 中券腔,我方的 P2Tomb 中也保存著對方的 P1Tomb)。

/**
 * 編碼令對方更新(我方/對方)墓地 message并發(fā)送
 * @param {string} updateType - updating type (add/reduce) 
 * @param {string} ply - indicated player
 * @param {*} cardNo - card number in tomb
 * @param {string} cardsrc - card img src
 */
function messageTomb(updateType, ply, cardNo, cardsrc) {
    var message_tomb = JSON.stringify({
        "type": "message",  //向服務(wù)器告知的消息類型
        "pid": playerID,  //向服務(wù)器告知的本玩家ID
        "msgtype": "updateTomb",  //向?qū)Ψ酵婕腋嬷母骂愋?        "updateType": updateType,  //向?qū)Ψ酵婕腋嬷?減墓地卡片
        "ply": ply,  //定義誰的墓地需被更新, player1表示你的對手拘泞,player2表示你自己
        "cardNo": cardNo,  //卡片序號纷纫,剔出墓地卡片時(shí)需要用到
        "cardsrc": cardsrc  //卡片的src,新增墓地卡片時(shí)需要用到
    });
    wsSend(message_tomb);
}

這個墓地更新的 message陪腌,由于雙方玩家既可以操作自己的墓地也可以操作對方的墓地辱魁,墓地更新的 message 必須指明此次操作是更新哪一方的墓地卡片,以便正確同步诗鸭。

變量 值 & 含義
updateType add / reduce - 告知是增加還是減少墓地的卡
ply player1 / player2 - 告知本次操作的是哪一方的墓地
cardNo 卡片序號 - 告知操作的是墓地中的哪一張卡(從墓地拿出某張卡片時(shí))
cardsrc 卡片圖片 url(向墓地送入某張卡片時(shí))

需要說明一下的是染簇,這四個變量中,cardNo 與 cardsrc 并不是每次發(fā)送信息的時(shí)候都會被使用强岸,需要分情況討論锻弓。當(dāng)我們從自己或?qū)Ψ侥沟靥蕹瞿硰埧ㄆ瑫r(shí),由于這張卡片已經(jīng)存在于墓地?cái)?shù)組中蝌箍,我們只需告知其在數(shù)組中的位置便可定位并操作該卡片青灼,這時(shí)候我們只傳送 cardNo 即可,cardsrc 可以留空或賦值一個“null”(便于識別妓盲,避免奇怪 bug)聚至。當(dāng)我們向墓地丟入新卡片時(shí),我們則需要提供該卡的圖片信息本橙,即 cardsrc扳躬,這時(shí)候 cardNo 是不需要的變量。

消息接收

消息編輯,發(fā)送之后贷币,下一步就是接收它击胜。當(dāng)客戶端接收到服務(wù)端轉(zhuǎn)發(fā)來的消息時(shí)會觸發(fā) ws.onmessage 函數(shù),函數(shù)內(nèi)容如下:

//接收服務(wù)器消息后觸發(fā)
ws.onmessage = function(message) {
    var msg = JSON.parse(message.data);
    var msgtype = msg.msgtype;

    switch(msgtype) {
        case 'updateHand':
            var handNo = msg.handNo;
            var updateType = msg.updateType;
            updateP2Hand(handNo, updateType);
            break;
        case 'updateField':
            var fieldID = msg.fieldID;
            var state = msg.state;
            var cardsrc = msg.cardsrc;
            updateField(fieldID, state, cardsrc);
            break;
        case 'updateTomb':
            var updateType = msg.updateType;
            var ply = msg.ply;
            var cardNo = msg.cardNo;
            var cardsrc = msg.cardsrc;
            updateTomb(updateType, ply, cardNo, cardsrc);
            break;
        default:
            alert("error message!");
            break;
    }
}

函數(shù)解碼傳來的 JSON 消息后役纹,首先獲取 msgtype偶摔,確認(rèn)更新類型。之前介紹的每種類型的 message 中促脉,前三個必帶變量用于了服務(wù)端的識別與這里的更新類型判定辰斋,而后面那些自定義變量則是在確認(rèn)了具體的更新類型后,作為參數(shù)被原封不動的傳入相關(guān)函數(shù)中瘸味。

更新手牌會調(diào)用 updateP2Hand 函數(shù)宫仗,用于同步對方手牌區(qū)域的顯示情況:

/**
 * 更新對方手牌區(qū)域
 * 對方手牌均為卡片背面圖片
 * @param {string} handNo - updated hand slot number 
 * @param {string} updateType - updating type (add/reduce)
 */
function updateP2Hand(handNo, updateType) {
    var handID = 'p2-hand' + handNo;
    element = document.getElementById(handID);

    /*執(zhí)行增或減手牌 */
    if(updateType == "add") {  
        element.src = CardBackSrc;
    } else {
        element.src = "";
    }
}


更新戰(zhàn)場會調(diào)用 updateField 函數(shù),此函數(shù)我們已經(jīng)介紹過旁仿,是專門用于更新整個戰(zhàn)場狀態(tài)的函數(shù):

/**
 * 戰(zhàn)場狀態(tài)更新藕夫,單獨(dú)更新某一個卡槽
 * @param {string} fieldID - field img container id 
 * @param {string} cardstate - state of card (attk/defen/back/on/off)
 * @param {string} cardsrc - card source url
 */
function updateField(fieldID, cardstate, cardsrc) { 
    var stateclass;
    element = document.getElementById(fieldID);

    /**
     * 如果是蓋卡或背蓋召喚直接顯示卡片背面
     * 檢查showCardInfo函數(shù)可知對于我方來說,即使卡片是背面圖片仍可以顯示卡片信息
     * 由于音效種類問題修改分類了多種情況
     */
    switch (cardstate) {
        case 'off':
        case 'back':
            element.src = CardBackSrc;
            stateclass = "card-" + cardstate;
            /*觸發(fā)背蓋或蓋卡音效 */
            var snd = new Audio("sound/activate.wav");
            snd.play();
            break;
        case 'on':  //正常發(fā)動卡片
            element.src = cardsrc;
            stateclass = "card-" + cardstate;
            /*觸發(fā)發(fā)動卡片音效 */
            var snd = new Audio("sound/activate.wav");
            snd.play();
            break;
        case 'change-off':  //通過更變形式覆蓋卡片
            element.src = CardBackSrc;
            stateclass = "card-" + cardstate.replace("change-", "");
            break;
        case 'change-back':  //通過更變形式背蓋召喚卡片
            element.src = CardBackSrc
            stateclass = "card-" + cardstate.replace("change-", "");
            break;
        case 'change-on':  //通過更變形式實(shí)現(xiàn)的打開蓋卡
            /*觸發(fā)打開蓋卡音效 */
            element.src = cardsrc;
            stateclass = "card-" + cardstate.replace("change-", "");
            var snd = new Audio("sound/open.wav");
            snd.play();
            break;
        case 'null':
            stateclass = "card";
            element.src = cardsrc;
            break;
        default:
            element.src = cardsrc;
            if (cardstate.search("change-") == -1) {  //正常召喚
                stateclass = "card-" + cardstate;
                /*觸發(fā)發(fā)召喚怪獸音效 */
                var snd = new Audio("sound/summon.wav");
                snd.play();
            } else {                                  //更變形式
                stateclass = "card-" + cardstate.replace("change-", "");
            }
            break;
    }

    element.setAttribute("class", stateclass);  //更新對應(yīng)img容器的class
}


更新墓地會調(diào)用 updateTomb 函數(shù)枯冈,同步雙方墓地的狀態(tài):

/**
 * 更新我方/對方墓地
 * @param {string} updateType - updating type (add/reduce) 
 * @param {string} ply - indicated player
 * @param {*} cardNo - card number in tomb
 * @param {string} cardsrc - card img src
 */
function updateTomb(updateType, ply, cardNo, cardsrc) {
    /*向墓地增卡一定是對方將卡牌送入對方墓地(對方無法將卡牌放入我方墓地) */
    if (updateType == 'add') {  
        P2Tomb.push(cardsrc);
        sf_buttons('p2tomb');

    /*向墓地剔出卡片則分情況 */
    } else if (updateType == 'reduce') {  
        if (ply == 'player1') {  //對方拿走我方墓地卡片
            P1Tomb.splice(cardNo, 1);
            sf_buttons('p1tomb');  //刷新副面板顯示
        } else {  //對方拿走對方墓地卡片毅贮,我方執(zhí)行同步
            P2Tomb.splice(cardNo, 1);
            sf_buttons('p2tomb');
        }
    }
}

每次更新完某一方墓地后會刷新一次副面板顯示,讓玩家獲悉墓地的變化尘奏。

到這里一個完整的客戶端消息發(fā)送接收機(jī)制就搭建完畢了滩褥!把各種功能與需求分類為幾個明確的類型,再針對每個類型定制相關(guān)消息與函數(shù)就是這個系統(tǒng)實(shí)現(xiàn)的核心炫加。再加之服務(wù)端的消息識別瑰煎,轉(zhuǎn)發(fā)功能,我們已經(jīng)完整地建立了一套可用的聯(lián)機(jī)交互系統(tǒng)琢感。

下一章我們把部分已經(jīng)介紹過的函數(shù)拿出來丢间,討論一下具體是哪些功能的哪些操作需要我們編輯并發(fā)送相關(guān)消息指示對方進(jìn)行同步。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末驹针,一起剝皮案震驚了整個濱河市烘挫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柬甥,老刑警劉巖饮六,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異苛蒲,居然都是意外死亡卤橄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門臂外,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窟扑,“玉大人喇颁,你說我怎么就攤上這事『炕酰” “怎么了橘霎?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長殖属。 經(jīng)常有香客問我姐叁,道長,這世上最難降的妖魔是什么洗显? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任外潜,我火速辦了婚禮,結(jié)果婚禮上挠唆,老公的妹妹穿的比我還像新娘处窥。我一直安慰自己,他們只是感情好损搬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布碧库。 她就那樣靜靜地躺著柜与,像睡著了一般巧勤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弄匕,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天颅悉,我揣著相機(jī)與錄音,去河邊找鬼迁匠。 笑死剩瓶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的城丧。 我是一名探鬼主播延曙,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼亡哄!你這毒婦竟也來了枝缔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚊惯,失蹤者是張志新(化名)和其女友劉穎愿卸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體截型,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趴荸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宦焦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片发钝。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡顿涣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出酝豪,到底是詐尸還是另有隱情园骆,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布寓调,位于F島的核電站锌唾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏夺英。R本人自食惡果不足惜晌涕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望痛悯。 院中可真熱鬧余黎,春花似錦、人聲如沸载萌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扭仁。三九已至垮衷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乖坠,已是汗流浹背搀突。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留熊泵,地道東北人仰迁。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像顽分,于是被迫代替她去往敵國和親徐许。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容