目录
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. 卡片放置:
卡片的攻击,防御,背盖防御召唤以及发动,覆盖,这些操作都可归类为玩家向战场上放置卡片,卡片放置函数可通过传递不同参数来分别执行这些功能。
实现卡片放置主要有几个步骤:
(1)选中手牌某一张卡片,并记录卡片信息:
这个操作由我们前面章节介绍的卡片选中系统来完成,当我们选中某张卡片后会用一个全局对象记录卡片的来源类型(手牌/场上),卡片序号,卡牌图片 url 等信息供其他函数使用。
(2)寻找场上的空卡槽:
确定怪兽/魔法陷阱区域的某个空卡槽,如果没有空卡槽剩余则无法执行召唤/发动/覆盖。
(3)将被选中的手牌删除:
被选中的手牌被打到场上去了,故需从手牌被删除。
(4)更新战场信息并更新战场:
将被选中的卡片按要求加载至战场上并记录其状态。
卡片放置函数 placeCard:
参数 | 含义 |
---|---|
placetype | 放置类型,包括 攻击 / 防御 / 背盖防御 / 发动 / 覆盖 |
cardtype | 卡片类型,包括 怪兽 / 魔法陷阱 |
寻找 怪兽/魔法陷阱 区域的空卡槽;
如果(卡槽未满);
如果(被选中的卡片来源于手牌):
记录手牌id;
通过手牌id获取手牌卡槽对象;
清除手牌卡槽的卡片;
更新战场数组fieldArrayPly1的内容;
更新战场上的卡片;
清空所有卡槽的选中状态;
/**
* 我方从手牌向场上放置卡片,并发出放置指令 (攻击,防御,背盖防御,发动,盖卡)
* @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();
}
}
}
在html中:
<button class="button" type="button" name="attkSummon" onclick="placeCard('attk', 'monster')">攻击召唤</button>
<button class="button" type="button" name="defenSummon" onclick="placeCard('defen', 'monster')">守备召唤</button>
<button class="button" type="button" name="backSummon" onclick="placeCard('back', 'monster')">背盖召唤</button>
<button class="button" type="button" name="launchCard" onclick="placeCard('on', 'magic')">发动(手卡)</button>
<button class="button" type="button" name="coverCard" onclick="placeCard('off', 'magic')">覆盖(手卡)</button>
这些 button 都会调用 placeCard 函数,且根据不同的功能传入不同的参数。
寻找空卡槽函数 findEmptySlot:
/**
* 返回当前我方场上/手牌的空卡槽序号(怪兽卡槽与魔法陷阱卡槽也要区分开)
* @param {string} slottype - type of wanted empty slot (monster/magic/hand)
*/
function findEmptySlot(slottype) {
var emptySlot = -1;
if (slottype == 'monster') { //放置怪兽卡搜索0-4卡槽
for (var i=0; i<5; i++) {
if (fieldArrayPly1.FieldCards[i].state == "null") {
emptySlot = i;
break;
}
}
} else if (slottype == 'magic') { //放置魔法陷阱卡搜索5-9卡槽
for (var i=5; i<10; i++) {
if (fieldArrayPly1.FieldCards[i].state == "null") {
emptySlot = i;
break;
}
}
} else if (slottype == 'hand') {
for (var i=0; i<8; i++) {
var handID = 'p1-hand' + i.toString();
element = document.getElementById(handID);
if (element.src == emptysrc) { //如果该卡槽为空
emptySlot = i;
break;
}
}
}
return emptySlot;
}
根据被选中卡片的类型去寻找相应区域的空卡槽,并返回卡槽序号。
2. 更变卡片表示形式:
卡片表示形式的更变包括怪兽卡与魔法陷阱卡。
怪兽卡的形式变更顺序:
攻击 -> 防御 -> 背盖 -> 攻击 -> ...
魔法陷阱的形式更变顺序:
覆盖 -> 表侧 -> 覆盖 -> ...
更变形式函数 changeState:
参数 | 含义 |
---|---|
cardtype | 卡片类型,包括 怪兽/魔法陷阱 |
如果(被选中的卡属于场上 且 属于我方玩家player1):
获取卡槽id;
获取卡槽中的卡片url以及当前状态(即表示形式state);
如果卡片类型是:
怪兽:
按 攻击/防御/背盖 的顺序变更1次状态并记录;
魔法陷阱:
按 表侧/背盖 的顺序变更1次状态并记录;
更新战场数组fieldArrayPly1中该卡槽的状态(state);
更新战场上的卡片;
/**
* 更变卡片的表示形式
* 更变顺序为:攻击 -> 防御 -> 背盖 -> 攻击, 盖覆卡 -> 表侧卡 -> 盖覆卡
* @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);
}
}
更变后的形式(state)会被战场数组记录并同时传递给战场更新函数 updateField 以更新所选卡槽的卡片样式。
3. 战场更新函数:
战场更新函数 updateField 用于更改某一个卡槽的卡片样式,让玩家可以实际的看到操作带来的变化,前面介绍的两个函数均有在作用域的末尾调用此函数。
参数 | 含义 |
---|---|
fieldID | 需要更新的卡槽 id |
cardstate | 需要更新的卡片状态 |
cardsrc | 需要更新的卡牌图片 url |
updateField 的原理很简单,我们在css中准备了几种卡片状态对应的卡槽样式,根据参数的不同修改卡槽样式即可(其实有些样式是一样的完全可以共用…)。
.main-field .battle-field .card-field .item .card-attk {
width: 65px;
height: 94px;
margin: 1px 40px;
}
.main-field .battle-field .card-field .item .card-defen {
width: 65px;
height: 94px;
transform: rotate(90deg);
margin: 1px 40px;
}
.main-field .battle-field .card-field .item .card-back {
width: 65px;
height: 94px;
transform: rotate(90deg);
margin: 1px 40px;
}
.main-field .battle-field .card-field .item .card-on { /* 魔法陷阱翻开状态*/
width: 65px;
height: 94px;
margin: 1px 40px;
}
.main-field .battle-field .card-field .item .card-off { /* 魔法陷阱覆盖状态*/
width: 65px;
height: 94px;
margin: 1px 40px;
}
/**
* 战场状态更新,单独更新某一个卡槽
* @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函数可知对于我方来说,即使卡片是背面图片仍可以显示卡片信息
* 由于音效种类问题修改分类了多种情况
*/
switch (cardstate) {
case 'off':
case 'back':
element.src = CardBackSrc;
stateclass = "card-" + cardstate;
/*触发背盖或盖卡音效 */
var snd = new Audio("sound/activate.wav");
snd.play();
break;
case 'on': //正常发动卡片
element.src = cardsrc;
stateclass = "card-" + cardstate;
/*触发发动卡片音效 */
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': //通过更变形式实现的打开盖卡
/*触发打开盖卡音效 */
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;
/*触发发召唤怪兽音效 */
var snd = new Audio("sound/summon.wav");
snd.play();
} else { //更变形式
stateclass = "card-" + cardstate.replace("change-", "");
}
break;
}
element.setAttribute("class", stateclass); //更新对应img容器的class
}
注:这里由于添加音效的问题多出了3种状态,change-off, change-back, change-on,因为通过更变形式打开或背盖卡片与直接从手牌发动或背盖卡片的音效是不同的,故多加了几个状态区分一下。前文 changeState 函数中也有一段代码是为了这里区分而在调用战场更新前修改了状态名称:
fieldArrayPly1.FieldCards[SelectedCard.cardNo].state = cardstate; //更新场上卡片状态信息
cardstate = "change-" + cardstate; //为通过更变形式而导致的战场更新操作添加一个标签方便更新函数识别(因为更变形式不触发音效)
updateField(fieldID, cardstate, cardsrc); //更新指定卡槽
最后来测试一下:
各种召唤:
发动/放置 魔法陷阱:
更变形式:
下一章继续介绍其他的功能按键。