用js寫卡牌遊戲(四)

前言

又過了三個月,咳咳咳……

這次我決定錄一個視頻,如果不想看文字的朋友,可以看視頻(點這裏),內容和文字差不多。

線上地址:http://cardgame.xiejingyang.com

github:https://github.com/xieisabug/card-game

正文

首先來做攻擊效果,也就是卡牌衝過去,再回來。這屬於動畫效果,我這裏就不造輪子了,直接找一個現成的好用點的動畫庫,這裏我用的是velocity-animate,首先進行安裝。

npm install --save velocity-animate

先取到頁面上的兩個卡牌容器,用於遍歷雙方桌面上的卡牌dom:

this.myCardAreaDom = document.querySelector(".my-card-area");
this.otherCardAreaDom = document.querySelector(".other-card-area");

之後要尋找出需要攻擊的卡牌,思路是:在mouseup的監聽事件中,如果處於選擇攻擊目標的狀態,那麼判斷鬆開鼠標的點是在哪個卡牌的box裏,就是攻擊的哪個卡牌。

window.onmouseup = (e) => {
    if (window.isAttackDrag) {
        window.isAttackDrag = false;
        this.showCanvas = false;
        this.canvasContext.clearRect(0, 0, this.windowWidth, this.windowHeight);

        let x = e.pageX, // 鼠標鬆開的x
            y = e.pageY, // 鼠標鬆開的y
            k = -1; // 用於記錄找到的卡牌的index

        this.otherCardAreaDom.childNodes.forEach(cd => { // 循環遍歷對手的卡牌dom
            let top = cd.offsetTop,
                width = cd.offsetWidth,
                left = cd.offsetLeft,
                height = cd.offsetHeight;

            if (x > left && x < (left + width) && y > top && y < (top + height)) { // 邊緣檢測
                k = cd.dataset.index;
            }
        });
    }
}

找到要攻擊的卡牌之後,寫出動畫代碼,卡牌位移衝過去,擊中後返回原位置,其中就是要找到translate的偏移量,然後做css動畫:

attackAnimate(from, to) {
    let myDom = this.myCardAreaDom.childNodes[from];
    let otherDom = this.otherCardAreaDom.childNodes[to];
    let h = otherDom.offsetLeft - myDom.offsetLeft; // 偏移x
    let v = otherDom.offsetTop + otherDom.offsetHeight - myDom.offsetTop - myDom.parentElement.offsetTop; // 偏移y
    Velocity(myDom, { translateX: h, translateY: v }, {
        easing: 'ease-in',
        duration: 200,
        begin: () => {
            myDom.style['transition'] = 'all 0s';
        }
    }).then(el => {
        return Velocity(el, { translateX: 0, translateY: 0 }, {
            easing: 'ease-out',
            duration: 300,
            complete: () => {
                myDom.style['transition'] = 'all 0.2s';
            }
        })
    })
},

在邊緣檢測完畢的時候,運行動畫:

if (x > left && x < (left + width) && y > top && y < (top + height)) { // 邊緣檢測
    k = cd.dataset.index;
    this.attackAnimate(0, k);
}

但是這樣僅僅只是單機上完成了,不僅對方不知道你進行了攻擊,也將邏輯放在了客戶端,這樣很不安全,現在就需要服務端來進行數據的計算,客戶端只做效果的展示。

那麼先處理一下攻擊事件,在客戶端發起攻擊的時候,暫時不需要做過多的處理,註釋之前的動畫代碼,直接發送攻擊事件給後端:

attackCard(k) {
    if (this.isMyTurn && this.currentTableCardK !== -1) {
        this.socket.emit("COMMAND", { // 使用COMMAND,集中處理
            type: "ATTACK_CARD",
            r: this.roomNumber,
            myK: this.currentTableCardK,
            attackK: k
        });
        this.resetAllCurrentCard();
    }
},
if (x > left && x < (left + width) && y > top && y < (top + height)) { // 邊緣檢測
    k = cd.dataset.index;
    // this.attackAnimate(0, k);
    this.attackCard(k);
}

這裏我將socket的事件,統一用”COMMAND”進行處理,這樣後臺進行處理的時候,代碼比較好管理。

後端修改之前的代碼,處理COMMAND命令:

socketServer.on('connection', function (socket) {
    console.log("connect one on :" + new Date().toLocaleString());
    socket.on('COMMAND', function () {
        let args = Array.prototype.slice.call(arguments);
        args.push(socket, socketServer);
        handleSynchronousClient.apply(this, args);
    });
    // other code ……
});
module.exports = function handleSynchronousClient(args, socket, socketServer) {
    switch (args.type) {
        case "CONNECT":
            connect(args, socket, socketServer);
            break;
        case "ATTACK_CARD":
            attackCard(args, socket);
            break;
    }
};

可以看到,修改之後用COMMAND命令之後可以用switch判斷type,集中的在一塊地方處理對戰的所有指令。connect就是將之前的CONNECT命令的處理拷貝過來,attackCard是處理攻擊的邏輯,由於對戰的卡牌等數據都沒有設計,所以使用最簡單的轉發,將攻擊卡牌的事件分發給對戰的雙方:

function attackCard(args, socket) {
    let roomNumber = args.r, myK = args.myK, attackK = args.attackK, card, attackCard;
    if (!memoryData[roomNumber]) {
        return
    }
    let belong = memoryData[roomNumber]["one"].socket.id === socket.id ? "one" : "two"; // 判斷當前是哪個玩家出牌
    let other = memoryData[roomNumber]["one"].socket.id !== socket.id ? "one" : "two";
    memoryData[roomNumber][belong].socket.emit("ATTACK_CARD", {
        k: attackK
    });
    memoryData[roomNumber][other].socket.emit("ATTACK_CARD", {
        k: attackK
    });
}

前端處理後端的轉發:

this.socket.on("ATTACK_CARD", (param) => {
    this.attackAnimate(0, param.k)
});

效果:

 

這樣就完成了一次攻擊,但是卡牌的血量還沒有扣減,這就涉及到要設計卡牌了,下一章着重講一下卡牌的數據設計和發牌的邏輯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章