Skip to content

Realtime Multiplayer Game

netzzwerg edited this page Mar 3, 2013 · 29 revisions

Preparation

node --version

0.8.21

npm --version

1.2.11

[sudo] npm install socket.io
[sudo] npm install express
cd node_modules
ls
node server.js

http://localhost:1337/?debug

Example 01 Basic Express Setup

  var serverPort    = process.env.PORT || 1337,
      express       = require('express'),
      app           = express(),
      http          = require('http'),
      server        = http.createServer(app);

  /* ------  ------  ------ Express ------  ------  ------ */

  server.listen(serverPort);
  console.log('\t :: Express :: Listening on port ' + serverPort );

  app.get('/', function(req, res){
    res.send('Hello World');
  });

File Respond

  app.get( '/', function( req, res ){
    res.sendfile( __dirname + '/index.html' );
  });

  app.get( '/*' , function( req, res, next ) {
    var file = req.params[0];
    if(verbose) console.log('\t :: Express :: file requested : ' + file);
    res.sendfile( __dirname + '/' + file );
  });

Example 02 Local Player Movement

Example 03 Basic SocketIO Setup

<script src="/socket.io/socket.io.js"></script>
var sio = require('socket.io').listen(server);

 sio.configure(function (){
    sio.set('log level', 0);
    sio.set('authorization', function (handshakeData, callback) {
     callback(null, true); // error first callback style
    });
 });

Send Message

sio.sockets.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
   console.log(data);
  });
});
var socket = io.connect('http://localhost:1337');
socket.on('news', function (data) {
  console.log(data);
  socket.emit('my other event', { my: 'data' });
});

Broadcast message

  • Send to the current socket:
socket.emit('message', data);
  • Send to all sockets
sio.sockets.emit('message', data);
  • Send to all sockets except the current one:
socket.broadcast.emit('message', data);

Example 04 Multiplayer Setup

client.js

socket.on('connected', function (data) {
  localUID = data.uid;
  for (var prop in data.clients) {
    if( data.clients.hasOwnProperty(prop) ) {
      var client = data.clients[prop];
      var actor = new Actor(client.data.uid);
      actor.targetX = client.data.x;
      actor.targetY = client.data.y;
      actors.push(actor);
    }
  }
});
socket.on('clientConnect', function (data) {
  var actor = new Actor(data.uid);
  if(data.uid === localUID){
    actor.c = '#FF00FF';
  }
  actors.push(actor);
});
socket.on('clientDisconnect', function (data) {
  for (var i = 0; i < actors.length; i++) {
    if(actors[i].uid === data.uid) {
      actors.splice(i, 1);
    }
  }
});
socket.on('clientMessage', function (data) {
  for (var i = 0; i < actors.length; i++) {
    if(actors[i].uid === data.uid) {
      actors[i].setTarget(data.x, data.y);
    }
  }
});
socket.on('connect', function () {
  console.log('server connected');
});
socket.on('disconnect', function (data) {
  actors = [];
});

server.js

sio.sockets.on('connection', function (socket) {
  newClient(socket);
  socket.on('clientMessage', onClientMessage);
  socket.on('disconnect', onDisconnect);
});
function onClientMessage (data) {
  clients[data.uid].data = data;
  sio.sockets.emit('clientMessage', data);
  console.log(' client\t - '.blue, data);
}
function onDisconnect () {
  var uid = this.id;
  sio.sockets.emit('clientDisconnect', {uid:uid});
  delete clients[uid];
  console.log(' client\t - '.red + uid + ' disconnected');
}
function newClient(socket) {
  var clientUID = socket.id;
  clients[clientUID] = {'data' : {
    'x' : 0,
    'y' : 0,
    'uid': clientUID
  }};

  // tell current connection that it is connected
  socket.emit('connected', {
    'uid' : clientUID,
    'clients' : clients
  });

  // tell other sockets that there is a new client
  sio.sockets.emit('clientConnect', {
    'uid' : clientUID
  });

  console.log(' client\t - '.green + clientUID + ' connected');
}

Example 05 Advanced Networking

Example 06 RequireJS Refactoring

<script data-main="js/client/client.js" src="js/vendor/require.js"></script>
domReady(function () {
  init();
});
define(['ready','underscore','Stage','Actor'], function (ready, _, Stage, Actor) {
  function init() {
    var stage = new Stage();
  }
  ready(init);
});
define(function () {
  'use strict';
  return value; // object, function, constructor
});

Refactoring of Actor Object to Module:

function Actor() {
  this.uid = null;
  this.c = "#FFFFFF"; // color
  this.x = 0;
  this.y = 0;
  this.r = 20; // radius
  this.vx = 5; // velocity x
  this.vy = 5; // velocity y
  this.targetX = 0;
  this.targetY = 0;
  this.animate = false;
  this.spring = 0.005;
  this.friction = 0.95;
}

Actor.prototype = {

  init: function(c, uid) {
    this.uid = uid;
    this.c = c; // color
  },

  calc: function() {
    if(this.animate) {
      // movement with spring and friction
      var dx = this.targetX - this.x;
      var dy = this.targetY - this.y;
      var ax = dx * this.spring;
      var ay = dy * this.spring;

      this.vx += ax;
      this.vy += ay;
      this.vx *= this.friction;
      this.vy *= this.friction;

      this.x += this.vx;
      this.y += this.vy;
    } else {
      this.x = this.targetX;
      this.y = this.targetY;
    }
  },

  draw: function(context) {
    // ball
    context.fillStyle = this.c;
    context.beginPath();
    context.arc(this.x, this.y, this.r, 0, Math.PI * 2, true);
    context.fill();
  },

  setTarget: function(x,y) {
    this.animate = true;
    this.targetX = x;
    this.targetY = y;
  },

  setPosition: function(x,y) {
    this.animate = false;
    this.targetX = x;
    this.targetY = y;
  }
};

return Actor;

Example 07 Server

Problems with the simple approach:

  • Frame rate dependence
  • Server is dumb (cheat protection, ...)
  • At client movement the server only knows the end position
  • Update position (using client prediction)
  • Move the other clients based on the server position (interpolation)
Client Rendering max. 60FPS (17ms) Server Update 22FPS (45ms)
Client Physics 66FPS (15ms) Server Physics 66FPS (15ms)

Move Logic to the server

Example 08 Grunt Build

Warning

  • strict data validation on any input received
  • limit maximum connection
  • don´t use nodejs as the webserver on the public interface (nginx with websocket support)
Clone this wiki locally