-
Notifications
You must be signed in to change notification settings - Fork 2
Realtime Multiplayer Game
netzzwerg edited this page Mar 3, 2013
·
29 revisions
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
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 );
});
<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);
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');
}
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) |
<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;
- 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)