《球球大作戰》原始碼解析——(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)});
- });
出生
玩家點選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<currentPlayer.cells.length; i++)
- {
- if(((currentPlayer.cells[i].mass >= 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<numMax; d++) {
- splitCell(currentPlayer.cells[d]);
- }
- }
- }
- currentPlayer.lastSplit = new Date().getTime();
- }
- });
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
- });
- }
- }
還是放個廣告吧,筆者出版的一本書《Unity3D網路遊戲實戰》充分的講解怎樣開發一款網路遊戲,特別對網路框架設計、網路協議、資料處理等方面都有詳細的描述,相信會是一本好書的。目前在籌劃第二版,看過的各位歡迎提些建議。
作者:羅培羽
專欄地址:https://zhuanlan.zhihu.com/p/32860460
相關文章
- 《球球大作戰》原始碼解析(8):訊息廣播原始碼
- 《球球大作戰》原始碼解析(6):碰撞處理原始碼
- 《球球大作戰》原始碼解析(7):遊戲迴圈原始碼遊戲
- 《球球大作戰》原始碼解析:移動演算法原始碼演算法
- 《球球大作戰》原始碼解析——(1)執行起來原始碼
- 《球球大作戰》原始碼解析:伺服器與客戶端架構原始碼伺服器客戶端架構
- 《球球大作戰》優化之路(上)優化
- 《球球大作戰》優化之路(下)優化
- Handler訊息處理機制原始碼解析 上原始碼
- 尋找伊犁鼠兔,巨人網路《球球大作戰》發起野生動物保護公益行動
- 原始碼分析:Android訊息處理機制原始碼Android
- 【實戰教程】微信卡券訊息處理
- 設計一款籃球經理類遊戲:球員屬性遊戲
- 【XMPP】Smack原始碼之訊息接收與解析Mac原始碼
- snabbdom原始碼解析(七) 事件處理原始碼事件
- 原始碼解析Java Attach處理流程原始碼Java
- 買球賽的軟體哪個好 手機線上球賽買球appAPP
- IOS 訊息推送處理iOS
- Android非同步訊息處理機制詳解及原始碼分析Android非同步原始碼
- 《C++ Primer》 P415 訊息處理機制 原始碼C++原始碼
- Dubbo原始碼解析之服務端接收訊息原始碼服務端
- 程式設計思路-球連球組成的群程式設計
- php ActiveMQ的傳送訊息,與處理訊息PHPMQ
- MPLS RSVP訊息處理——VecloudCloud
- Storm保證訊息處理ORM
- 三、訊息的可靠處理
- Spring Ioc原始碼分析系列--Ioc容器註冊BeanPostProcessor後置處理器以及事件訊息處理Spring原始碼Bean事件
- Python寫個“點球大戰”小遊戲Python遊戲
- [原始碼解析] 訊息佇列 Kombu 之 基本架構原始碼佇列架構
- js重力球效果程式碼例項JS
- 【開源遊戲】Legends-Of-Heroes 基於ET 7.2的雙端C#(.net7 + Unity3d)多人線上英雄聯盟風格的球球大作戰遊戲。遊戲C#Unity3D
- Sidekiq 訊號處理原始碼分析IDE原始碼
- 【科普】Scrum——從橄欖球爭球到敏捷開發Scrum敏捷
- Flutter 安卓 Platform 與 Dart 端訊息通訊方式 Channel 原始碼解析Flutter安卓PlatformDart原始碼
- 設計一款籃球經理類遊戲(三):球員升級、裝備、技能與組合遊戲
- 華瑞IT校園籃球賽:熱血少年,球場爭鋒
- 合法買球的app 網上app買球算犯法嗎APP
- 佛盛龍羽毛球管理軟體v9