前言

又过了三个月,咳咳咳……

这次我决定录一个视频,如果不想看文字的朋友,可以翻到最下面看视频,内容和文字差不多。

线上地址: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)
});

效果:

这样就完成了一次攻击,但是卡牌的血量还没有扣减,这就涉及到要设计卡牌了,下一章着重讲一下卡牌的数据设计和发牌的逻辑。


0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注