《球球大作战》源码解析——(9)消息处理

服务端会收到来自客户端的协议,这些协议包括ping协议、窗口变化协议(windowResized)、玩家下线协议(disconnect)、聊天请求、分身请求等等,那么当服务端收到这些协议后,它会怎样处理呢?

 

点击Play

当客户端点击play的时候,客户端发起连接,服务端io.on('connection',fun)的回调函数被执行,具体过程可参见该系列的第2篇文章:程序流程

io.on('connection', function (socket) {
    ……
}

 

ping协议

当服务端收到客户端发送的pingcheck协议后,立即返回pongcheck协议,供客户端计算网络延迟时间。

    socket.on('pingcheck', function () {
        socket.emit('pongcheck');
    });

 

窗体变化协议

服务端收到窗体大小变化的协议windowResized后,会更新玩家数值,以调整sendUpdates的数据量。

    socket.on('windowResized', function (data) {
        currentPlayer.screenWidth = data.screenWidth;
        currentPlayer.screenHeight = data.screenHeight;
    });

 

玩家下线

当服务端收到disconnect协议,它会把玩家从玩家列表中删除,然后广播playerDisconnect,通知所有客户端该玩家下线。

    socket.on('disconnect', function () {
        if (util.findIndex(users, currentPlayer.id) > -1)
            users.splice(util.findIndex(users, currentPlayer.id), 1);
        console.log('[INFO] User ' + currentPlayer.name + ' disconnected!');

        socket.broadcast.emit('playerDisconnect', { name: currentPlayer.name });
    });

 

聊天

服务端收到聊天协议playerChat后,通过serverSendPlayerChat协议将聊天内容广播给所有客户端。

    socket.on('playerChat', function(data) {
        var _sender = data.sender.replace(/(<([^>]+)>)/ig, '');
        var _message = data.message.replace(/(<([^>]+)>)/ig, '');
        if (c.logChat === 1) {
            console.log('[CHAT] [' + (new Date()).getHours() + ':' + (new Date()).getMinutes() + '] ' + _sender + ': ' + _message);
        }
        socket.broadcast.emit('serverSendPlayerChat', {sender: _sender, message: _message.substring(0,35)});
    });

 

 

出生

《球球大作战》源码解析——(9)消息处理_第1张图片

玩家点击play按钮,客户端在连接服务端后,会发送respawn协议添加玩家。服务端先把原先小球的干掉(如果有),然后发welcome回给客户端。

    socket.on('respawn', function () {
        if (util.findIndex(users, currentPlayer.id) > -1)
            users.splice(util.findIndex(users, currentPlayer.id), 1);
        socket.emit('welcome', currentPlayer);
        console.log('[INFO] User ' + currentPlayer.name + ' respawned!');
    });

 

gotit协议

客户端收到welcome协议后,发送gotit协议,服务端做一些判断,比如看看名字是否合法,查一查玩家是否有重复登录。然后服务端广播playerJoin协议,并使用gameSetup协议向客户端发送游戏地图大小的信息。

    socket.on('gotit', function (player) {
        console.log('[INFO] Player ' + player.name + ' connecting!');

        if (util.findIndex(users, player.id) > -1) {
            ……//已经登录,断开连接
        } else if (!util.validNick(player.name)) {
            ……//名字不合法,断开连接
        } else {
            ……//一些初变量始化
            io.emit('playerJoin', { name: currentPlayer.name });

            socket.emit('gameSetup', {
                gameWidth: c.gameWidth,
                gameHeight: c.gameHeight
            });
        }
    });

 

 

Pass协议

 

Pass协议用于管理员认证,暂未发现客户端哪里发送该协议。

    socket.on('pass', function(data) {
        if (data[0] === c.adminPass) {
            console.log('[ADMIN] ' + currentPlayer.name + ' just logged in as an admin!');
            socket.emit('serverMSG', 'Welcome back ' + currentPlayer.name);
            socket.broadcast.emit('serverMSG', currentPlayer.name + ' just logged in as admin!');
            currentPlayer.admin = true;
        } else {
            console.log('[ADMIN] ' + currentPlayer.name + ' attempted to log in with incorrect password.');
            socket.emit('serverMSG', 'Password incorrect, attempt logged.');
            // TODO: Actually log incorrect passwords.
        }
    });

 

踢人协议

管理员可以踢人,但客户端中暂未发现相关的功能,应该只是一个预留功能。

    socket.on('kick', function(data) {
        if (currentPlayer.admin) {
            var reason = '';
            var worked = false;
            for (var e = 0; e < users.length; e++) {
                if (users[e].name === data[0] && !users[e].admin && !worked) {
                    if (data.length > 1) {
                        for (var f = 1; f < data.length; f++) {
                            if (f === data.length) {
                                reason = reason + data[f];
                            }
                            else {
                                reason = reason + data[f] + ' ';
                            }
                        }
                    }
                    if (reason !== '') {
                       console.log('[ADMIN] User ' + users[e].name + ' kicked successfully by ' + currentPlayer.name + ' for reason ' + reason);
                    }
                    else {
                       console.log('[ADMIN] User ' + users[e].name + ' kicked successfully by ' + currentPlayer.name);
                    }
                    socket.emit('serverMSG', 'User ' + users[e].name + ' was kicked by ' + currentPlayer.name);
                    sockets[users[e].id].emit('kick', reason);
                    sockets[users[e].id].disconnect();
                    users.splice(e, 1);
                    worked = true;
                }
            }
            if (!worked) {
                socket.emit('serverMSG', 'Could not locate user or user is an admin.');
            }
        } else {
            console.log('[ADMIN] ' + currentPlayer.name + ' is trying to use -kick but isn\'t an admin.');
            socket.emit('serverMSG', 'You are not permitted to use this command.');
        }
    });

 

更新目标位置

由于玩家实时控制小球移动,每一帧都会更新目标位置,这个协议会频繁调用,故而选用简单的名字,命名为0号协议,以减少传输的数据量。通过该协议更新目标位置,然后记录心跳时间。

    // Heartbeat function, update everytime.
    socket.on('0', function(target) {
        currentPlayer.lastHeartbeat = new Date().getTime();
        if (target.x !== currentPlayer.x || target.y !== currentPlayer.y) {
            currentPlayer.target = target;
        }
    });

 

发射massfood

发射massfood的操作由1号协议通信,玩家发射massfood就发送一条该协议,然后服务端记录起来。

    socket.on('1', function() {
        // Fire food.
        for(var i=0; i= c.defaultPlayerMass + c.fireFood) && c.fireFood > 0) || (currentPlayer.cells[i].mass >= 20 && c.fireFood === 0)){
                var masa = 1;
                if(c.fireFood > 0)
                    masa = c.fireFood;
                else
                    masa = currentPlayer.cells[i].mass*0.1;
                currentPlayer.cells[i].mass -= masa;
                currentPlayer.massTotal -=masa;
                massFood.push({
                    ……//massFood的信息
                });
            }
        }
    });

 

分身

玩家点击屏幕中的分身按钮,客户端发送2号协议,服务端收到后做一些判断,然后调用splitCell执行小球分身。

    socket.on('2', function(virusCell) {
       ……
        if(currentPlayer.cells.length < c.limitSplit && currentPlayer.massTotal >= c.defaultPlayerMass*2) {
            //Split single cell from virus
            if(virusCell) {
              splitCell(currentPlayer.cells[virusCell]);
            }
            else {
              //Split all cells
              if(currentPlayer.cells.length < c.limitSplit && currentPlayer.massTotal >= c.defaultPlayerMass*2) {
                  var numMax = currentPlayer.cells.length;
                  for(var d=0; d

 

splitCell方法如下所示,它将小球分成两个等质量的球。

        function splitCell(cell) {
            if(cell.mass >= c.defaultPlayerMass*2) {
                cell.mass = cell.mass/2;
                cell.radius = util.massToRadius(cell.mass);
                currentPlayer.cells.push({
                    mass: cell.mass,
                    x: cell.x,
                    y: cell.y,
                    radius: cell.radius,
                    speed: 25
                });
            }
        }

你可能感兴趣的:(NodeJS)