目录
HTML+JS+websocket 实例,联机“游戏王”对战 1
HTML+JS+websocket 实例,联机“游戏王”对战 2 - 联机模式
HTML+JS+websocket 实例,联机“游戏王”对战 3 - 界面布局
HTML+JS+websocket 实例,联机“游戏王”对战 4 - 卡组系统
HTML+JS+websocket 实例,联机“游戏王”对战 5 - 卡片选中系统
HTML+JS+websocket 实例,联机“游戏王”对战 6 - 卡片放置,战场更新
HTML+JS+websocket 实例,联机“游戏王”对战 7 - 墓地,副控制面板
HTML+JS+websocket 实例,联机“游戏王”对战 8 - 返回手卡,卡组
HTML+JS+websocket 实例,联机“游戏王”对战 9 - 实现简单 websocket 通信
HTML+JS+websocket 实例,联机“游戏王”对战 10 - 搭建游戏服务端
HTML+JS+websocket 实例,联机“游戏王”对战 11 - 客户端消息的收发
HTML+JS+websocket 实例,联机“游戏王”对战 12 - 消息发送具体场景
HTML+JS+websocket 实例,联机“游戏王”对战 13 - 实机演示
客户端发送消息的具体场景
这章最后总结下游戏客户端中具体哪些函数调用了消息发送。
代码中发送消息的地方均有注释。
(1)抽卡:首先我方抽牌时手牌会增加,需要让对方同步我们的手牌数;
/*抽取牌组最上方一张卡至手卡 */
function drawCard() {
for (var i=0; i<8; i++) {
var handID = 'p1-hand' + i.toString();
element = document.getElementById(handID);
if (element.src == emptysrc) { //如果该卡槽为空
element.src = P1Deck.pop();
/*触发抽卡音效 */
var snd = new Audio("sound/draw.wav");
snd.play();
/**
* 告知对手哪张手卡卡槽添加了一张卡
*/
messageHand('add', i);
break;
}
}
}
(2)卡片放置:召唤,覆盖卡片等从手牌向战场放置卡片的操作时,我方战场需要更新,且我方手牌减少,同时调用 messageField 与 messageHand;
/**
* 我方从手牌向场上放置卡片,并发出放置指令 (攻击,防御,背盖防御,发动,盖卡)
* @param {string} placetype - place type (attack/defence/back/on/off)
* @param {string} cardtype - card type (monster/magic)
*/
function placeCard(placetype, cardtype) {
var cardslot = findEmptySlot(cardtype); //寻找空的卡槽
var cardsrc;
if(cardslot == -1) {
alert("卡槽已满");
} else {
if (SelectedCard.type == 'hand') { //放置卡片必须来源于手牌
/*获取被选中手卡信息 */
var handslot = (SelectedCard.cardNo).toString();
var handID = "p1-hand" + handslot;
element = document.getElementById(handID);
cardsrc = SelectedCard.cardSrc;
element.src = ""; //手牌该卡消失
/*更新战场信息 */
fieldArrayPly1.FieldCards[cardslot].imgsrc = cardsrc;
fieldArrayPly1.FieldCards[cardslot].state = placetype;
/*发出指令,执行更新战场卡片的函数 */
var fieldID = "p1-field" + cardslot.toString();
updateField(fieldID, placetype, cardsrc);
/**
* 放置后告知对手执行战场更新函数;
* 放置完成后记得告诉对手哪张手卡消失了;
* 注意:我方战场变化对对方来说是P2;
*/
var updateID = "p2-field" + cardslot.toString();
messageField(placetype, updateID, cardsrc);
messageHand('reduce', handslot);
/*清空所有选中状态 */
cleanSelected();
}
}
}
(3)更变形式:更变战场卡片表示形式时,我方战场会有更新,需告知对方同步;
/**
* 更变卡片的表示形式
* 更变顺序为:攻击 -> 防御 -> 背盖 -> 攻击, 盖覆卡 -> 表侧卡 -> 盖覆卡
* @param {string} cardtype - card type (monster/magic)
*/
function changeState(cardtype) {
if (SelectedCard.type == 'field' && SelectedCard.player == 'player1') { //必须是我方场上的卡方可更变表示形式
var fieldID = "p1-field" + (SelectedCard.cardNo).toString();
var cardsrc = fieldArrayPly1.FieldCards[SelectedCard.cardNo].imgsrc;
var cardstate = fieldArrayPly1.FieldCards[SelectedCard.cardNo].state;
switch (cardtype) {
case 'monster':
if (cardstate == 'attk') {
cardstate = "defen";
} else if (cardstate == 'defen') {
cardstate = "back";
} else if (cardstate == 'back') {
cardstate = "attk";
}
break;
case 'magic':
if (cardstate == 'off') {
cardstate = "on";
} else {
cardstate = "off";
}
break;
default:
break;
}
fieldArrayPly1.FieldCards[SelectedCard.cardNo].state = cardstate; //更新场上卡片状态信息
cardstate = "change-" + cardstate; //为通过更变形式而导致的战场更新操作添加一个标签方便更新函数识别(因为更变形式不触发音效)
updateField(fieldID, cardstate, cardsrc); //更新指定卡槽
/**
* 告知对手某一卡槽的表示形式发生变化,执行战场更新函数
*/
var updateID = "p2-field" + (SelectedCard.cardNo).toString();
messageField(cardstate, updateID, cardsrc);
}
}
(4)回到手牌:
若将战场卡片返回手牌,战场将更新,我方手牌增加;
若将墓地卡片返回手牌,墓地将更新,我方手牌增加;
若将牌组卡片返回手牌,我方手牌增加;
注:回到手牌操作可操作双方战场与墓地,注意参数的填写;
/**
* 将场上选中的卡牌回到手卡槽
*/
function backtoHand() {
var cardslot = findEmptySlot('hand'); //寻找我方空的手牌卡槽
var cardNo = SelectedCard.cardNo; //被选中卡牌的序号
if (cardslot == -1) {
alert("手牌已满");
} else {
var handID = "p1-hand" + (cardslot).toString();
element = document.getElementById(handID);
/*## 若卡片来源于场上 */
if (SelectedCard.type == 'field') {
var fieldID;
/*我方 卡牌信息从用于存储被选中卡片信息的对象中获取 */
if (SelectedCard.player == 'player1') {
fieldID = "p1-field" + cardNo.toString();
element.src = SelectedCard.cardSrc; //手牌获取被选中的卡片
fieldArrayPly1.FieldCards[cardNo].imgsrc = "null"; //场上该卡的记录清空
fieldArrayPly1.FieldCards[cardNo].state = "null";
/**
* 通知对方更新战场信息
*/
var updateID = "p2-field" + cardNo.toString();
messageField("null", updateID, "");
/*对方 卡牌信息从用于存储被选中卡片信息的对象中获取(必须是正面表示的卡片) */
} else if (SelectedCard.cardSrc != CardBackSrc) {
fieldID = "p2-field" + cardNo.toString();
element.src = SelectedCard.cardSrc
/**
* 通知对方更新战场信息
*/
var updateID = "p1-field" + cardNo.toString();
messageField("null", updateID, "");
}
/*更新战场信息 */
updateField(fieldID, "null", "");
/**
* 通知对方更新手卡数
*/
messageHand("add", cardslot);
/*## 若卡片来源于卡组或墓地 */
} else if (SelectedCard.type != 'hand') {
/*我方牌组/墓地的卡片回到我方手牌 */
if (SelectedCard.player == 'player1') {
element.src = SelectedCard.cardSrc; //手牌获取该卡片src
/*更新 卡组/墓地 列表并刷新副面板显示 */
if (sf_Card.type == 'deck') {
P1Deck.splice(cardNo, 1); //剔出卡组中的被选中卡片
sf_buttons('deck'); //刷新副面板显示
} else if (sf_Card.type == 'p1tomb') {
P1Tomb.splice(cardNo, 1);
sf_buttons('p1tomb');
/**
* 通知对方玩家更新我方墓地(对于对方来说,我方是player2)
*/
messageTomb("reduce", "player2", cardNo, SelectedCard.cardSrc);
}
/**
* 通知对方更新手卡数
*/
messageHand("add", cardslot);
/*对方墓地的卡片回到我方手牌 */
} else if (SelectedCard.player == 'player2') {
element.src = SelectedCard.cardSrc; //手牌获取该卡片src
P2Tomb.splice(cardNo, 1); //剔出对方墓地中的对应卡片
sf_buttons('p2tomb');
/**
* 通知对方玩家我方手卡数及对方墓地 (对于对方来说,他自己是player1)
*/
messageHand("add", cardslot);
messageTomb("reduce", "player1", cardNo, SelectedCard.cardSrc);
}
}
/*清空所有选中状态 */
cleanSelected();
}
}
(5)回到牌组:
若将我方手牌返回牌组,我方手牌减少;
若将我方战场卡片返回牌组,我方战场将更新;
/**
* 将我方被选中的卡片返回卡组
* 默认回到卡组最上方
*/
function backtoDeck() {
if (SelectedCard.player == 'player1') { //只允许将我方的卡片送回牌组
var cardNo = SelectedCard.cardNo;
/*手牌卡片回到卡组 */
if (SelectedCard.type == 'hand') {
var handID = "p1-hand" + cardNo.toString();
element = document.getElementById(handID);
element.src = ""; //手牌被选中卡片消失
P1Deck.push(SelectedCard.cardSrc); //卡片src存回卡组
/**
* 告知对手手卡变动
*/
messageHand("reduce", cardNo);
alert("卡片已回到卡组最上方!");
/*场上卡片回到卡组 */
} else if (SelectedCard.type == 'field') {
var fieldID = "p1-field" + cardNo.toString();
P1Deck.push(SelectedCard.cardSrc); //卡片src存回卡组
fieldArrayPly1.FieldCards[cardNo].imgsrc = "null"; //场上该卡的记录清空
fieldArrayPly1.FieldCards[cardNo].state = "null";
/*更新战场 */
updateField(fieldID, "null", "");
/**
* 通知对方玩家更新我方战场
*/
var updateID = "p2-field" + cardNo.toString();
messageField("null", updateID, "");
alert("卡片已回到卡组最上方!");
} else {
alert("请先将卡片拿到手牌再放回卡组"); //防止直接从卡组或墓地中选卡放回卡组
}
/*清空所有选中状态 */
cleanSelected();
}
}
(6)送去墓地:
若将我方手牌送入墓地,我方墓地将更新,我方手牌减少;
若将我方战场卡片送入墓地,我方墓地将更新,我方战场将更新;
/**
* 将我方的卡片(场上/手牌)送去墓地
*/
function sendtoTomb() {
var cardsrc = SelectedCard.cardSrc;
var cardNo = SelectedCard.cardNo;
var fieldID;
if (SelectedCard.player == 'player1') {
/*若卡片来源于手牌 */
if (SelectedCard.type == 'hand') {
var handID = "p1-hand" + cardNo.toString();
element = document.getElementById(handID);
element.src = ""; //手牌该卡消失
P1Tomb.push(cardsrc); //将选中卡片的src存入墓地数组
/**
* 告知对方更新我方手牌数及墓地
*/
messageHand("reduce", cardNo);
messageTomb("add", "player2", "null", cardsrc); //向墓地添加卡片不需要cardNo
/*若卡片来源于场上 */
} else if (SelectedCard.type == 'field') {
fieldID = "p1-field" + cardNo.toString();
fieldArrayPly1.FieldCards[cardNo].imgsrc = "null"; //场上该卡的记录清空
fieldArrayPly1.FieldCards[cardNo].state = "null";
P1Tomb.push(cardsrc); //将选中卡片的src存入墓地数组
updateField(fieldID, "null", ""); //更新战场
/**
* 告知对方更新我方战场及墓地
*/
var updateID = "p2-field" + cardNo.toString();
messageField("null", updateID, "");
messageTomb("add", "player2", "null", cardsrc);
} else {
alert("请先将卡片拿到手牌再放入墓地"); //防止直接从卡组或墓地中选卡放回墓地
}
/*清空所有选中状态 */
cleanSelected();
sf_buttons('p1tomb'); //刷新副面板显示
}
}
整个游戏制作流程到这里就基本结束啦,架起服务端,连上客户端就可进行开黑。记住需要提前在代码里设置卡组信息,玩家信息,ip地址等相关参数。有时间可以把这些基本参数设置转到UI界面里来,完善游戏的完成度。
var P1DeckName = "Deck_KaiMa"; //我方牌组名
var P1DeckNum = 50; //我方牌组卡片数量
var playerID = "player1"; //独立玩家ID
var ws = new WebSocket("ws://localhost:9999/");
下一章就做一个小demo演示,以及讨论一些待完善的细节,感谢耐心阅读。