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

作者:罗培羽 2019-04-11

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

点击Play

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

  1. io.on('connection', function (socket) {
  2.     ……
  3. }
复制代码

ping协议

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

  1. socket.on('pingcheck', function () {
  2.         socket.emit('pongcheck');
  3.     });
复制代码

窗体变化协议

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

  1. socket.on('windowResized', function (data) {
  2.         currentPlayer.screenWidth = data.screenWidth;
  3.         currentPlayer.screenHeight = data.screenHeight;
  4.     });
复制代码

玩家下线

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

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

  5.         socket.broadcast.emit('playerDisconnect', { name: currentPlayer.name });
  6.     });
复制代码

聊天

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

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

出生


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

  1.     socket.on('respawn', function () {
  2.         if (util.findIndex(users, currentPlayer.id) > -1)
  3.             users.splice(util.findIndex(users, currentPlayer.id), 1);
  4.         socket.emit('welcome', currentPlayer);
  5.         console.log('[INFO] User ' + currentPlayer.name + ' respawned!');
  6.     });
复制代码

gotit协议

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

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

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

  10.             socket.emit('gameSetup', {
  11.                 gameWidth: c.gameWidth,
  12.                 gameHeight: c.gameHeight
  13.             });
  14.         }
  15.     });
复制代码

Pass协议

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

  1. socket.on('pass', function(data) {
  2.         if (data[0] === c.adminPass) {
  3.             console.log('[ADMIN] ' + currentPlayer.name + ' just logged in as an admin!');
  4.             socket.emit('serverMSG', 'Welcome back ' + currentPlayer.name);
  5.             socket.broadcast.emit('serverMSG', currentPlayer.name + ' just logged in as admin!');
  6.             currentPlayer.admin = true;
  7.         } else {
  8.             console.log('[ADMIN] ' + currentPlayer.name + ' attempted to log in with incorrect password.');
  9.             socket.emit('serverMSG', 'Password incorrect, attempt logged.');
  10.             // TODO: Actually log incorrect passwords.
  11.         }
  12.     });
复制代码

踢人协议

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

  1. socket.on('kick', function(data) {
  2.         if (currentPlayer.admin) {
  3.             var reason = '';
  4.             var worked = false;
  5.             for (var e = 0; e < users.length; e++) {
  6.                 if (users[e].name === data[0] && !users[e].admin && !worked) {
  7.                     if (data.length > 1) {
  8.                         for (var f = 1; f < data.length; f++) {
  9.                             if (f === data.length) {
  10.                                 reason = reason + data[f];
  11.                             }
  12.                             else {
  13.                                 reason = reason + data[f] + ' ';
  14.                             }
  15.                         }
  16.                     }
  17.                     if (reason !== '') {
  18.                        console.log('[ADMIN] User ' + users[e].name + ' kicked successfully by ' + currentPlayer.name + ' for reason ' + reason);
  19.                     }
  20.                     else {
  21.                        console.log('[ADMIN] User ' + users[e].name + ' kicked successfully by ' + currentPlayer.name);
  22.                     }
  23.                     socket.emit('serverMSG', 'User ' + users[e].name + ' was kicked by ' + currentPlayer.name);
  24.                     sockets[users[e].id].emit('kick', reason);
  25.                     sockets[users[e].id].disconnect();
  26.                     users.splice(e, 1);
  27.                     worked = true;
  28.                 }
  29.             }
  30.             if (!worked) {
  31.                 socket.emit('serverMSG', 'Could not locate user or user is an admin.');
  32.             }
  33.         } else {
  34.             console.log('[ADMIN] ' + currentPlayer.name + ' is trying to use -kick but isn\'t an admin.');
  35.             socket.emit('serverMSG', 'You are not permitted to use this command.');
  36.         }
  37.     });
复制代码

更新目标位置

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

  1.   // Heartbeat function, update everytime.
  2.     socket.on('0', function(target) {
  3.         currentPlayer.lastHeartbeat = new Date().getTime();
  4.         if (target.x !== currentPlayer.x || target.y !== currentPlayer.y) {
  5.             currentPlayer.target = target;
  6.         }
  7.     });
复制代码

发射massfood

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

  1.   socket.on('1', function() {
  2.         // Fire food.
  3.         for(var i=0; i<currentPlayer.cells.length; i++)
  4.         {
  5.             if(((currentPlayer.cells[i].mass >= c.defaultPlayerMass + c.fireFood) && c.fireFood > 0) || (currentPlayer.cells[i].mass >= 20 && c.fireFood === 0)){
  6.                 var masa = 1;
  7.                 if(c.fireFood > 0)
  8.                     masa = c.fireFood;
  9.                 else
  10.                     masa = currentPlayer.cells[i].mass*0.1;
  11.                 currentPlayer.cells[i].mass -= masa;
  12.                 currentPlayer.massTotal -=masa;
  13.                 massFood.push({
  14.                     ……//massFood的信息
  15.                 });
  16.             }
  17.         }
  18.     });
复制代码

分身

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

  1.   socket.on('2', function(virusCell) {
  2.        ……
  3.         if(currentPlayer.cells.length < c.limitSplit && currentPlayer.massTotal >= c.defaultPlayerMass*2) {
  4.             //Split single cell from virus
  5.             if(virusCell) {
  6.               splitCell(currentPlayer.cells[virusCell]);
  7.             }
  8.             else {
  9.               //Split all cells
  10.               if(currentPlayer.cells.length < c.limitSplit && currentPlayer.massTotal >= c.defaultPlayerMass*2) {
  11.                   var numMax = currentPlayer.cells.length;
  12.                   for(var d=0; d<numMax; d++) {
  13.                       splitCell(currentPlayer.cells[d]);
  14.                   }
  15.               }
  16.             }
  17.             currentPlayer.lastSplit = new Date().getTime();
  18.         }
  19.     });
复制代码

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

  1.         function splitCell(cell) {
  2.             if(cell.mass >= c.defaultPlayerMass*2) {
  3.                 cell.mass = cell.mass/2;
  4.                 cell.radius = util.massToRadius(cell.mass);
  5.                 currentPlayer.cells.push({
  6.                     mass: cell.mass,
  7.                     x: cell.x,
  8.                     y: cell.y,
  9.                     radius: cell.radius,
  10.                     speed: 25
  11.                 });
  12.             }
  13.         }
复制代码

还是放个广告吧,笔者出版的一本书《Unity3D网络游戏实战》充分的讲解怎样开发一款网络游戏,特别对网络框架设计、网络协议、数据处理等方面都有详细的描述,相信会是一本好书的。目前在筹划第二版,看过的各位欢迎提些建议。

作者:罗培羽
专栏地址:https://zhuanlan.zhihu.com/p/32860460

最新评论
暂无评论
参与评论

商务合作 查看更多

编辑推荐 查看更多