又过了三个月,咳咳咳……
这次我决定录一个视频,如果不想看文字的朋友,可以看视频(点这里),内容和文字差不多。
线上地址: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)
});
效果:
这样就完成了一次攻击,但是卡牌的血量还没有扣减,这就涉及到要设计卡牌了,下一章着重讲一下卡牌的数据设计和发牌的逻辑。