服务端会收到来自客户端的协议,这些协议包括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