前言
又過了三個月,咳咳咳……
這次我決定錄一個視頻,如果不想看文字的朋友,可以看視頻(點這裏),內容和文字差不多。
線上地址: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)
});
效果:
這樣就完成了一次攻擊,但是卡牌的血量還沒有扣減,這就涉及到要設計卡牌了,下一章着重講一下卡牌的數據設計和發牌的邏輯。