HTML+JS+websocket 实例,联机“游戏王”对战(六)- 卡片放置,战场更新

目录

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);  //更新指定卡槽


最后来测试一下:

各种召唤:

summons.gif

发动/放置 魔法陷阱:

magics.gif

更变形式:

change_state.gif

下一章继续介绍其他的功能按键。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容