《球球大作戰》原始碼解析——(9)訊息處理

遊資網發表於2019-04-11
《球球大作戰》原始碼解析——(9)訊息處理

服務端會收到來自客戶端的協議,這些協議包括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.     });
複製程式碼

出生

《球球大作戰》原始碼解析——(9)訊息處理

玩家點選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網路遊戲實戰》充分的講解怎樣開發一款網路遊戲,特別對網路框架設計、網路協議、資料處理等方面都有詳細的描述,相信會是一本好書的。目前在籌劃第二版,看過的各位歡迎提些建議。

《球球大作戰》原始碼解析——(9)訊息處理
作者:羅培羽
專欄地址:https://zhuanlan.zhihu.com/p/32860460

相關文章