Created
March 27, 2012 15:01
-
-
Save ruby0x1/2216646 to your computer and use it in GitHub Desktop.
Multi-player games in HTML5
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Copyright (c) 2012 Sven "FuzzYspo0N" Bergström | |
http://underscorediscovery.com | |
MIT Licensed. See LICENSE for full license. | |
Usage : node simplest.app.js | |
*/ | |
var | |
gameport = process.env.PORT || 4004, | |
io = require('socket.io'), | |
express = require('express'), | |
UUID = require('node-uuid'), | |
verbose = false, | |
app = express.createServer(); | |
/* Express server set up. */ | |
//The express server handles passing our content to the browser, | |
//As well as routing users where they need to go. This example is bare bones | |
//and will serve any file the user requests from the root of your web server (where you launch the script from) | |
//so keep this in mind - this is not a production script but a development teaching tool. | |
//Tell the server to listen for incoming connections | |
app.listen( gameport ); | |
//Log something so we know that it succeeded. | |
console.log('\t :: Express :: Listening on port ' + gameport ); | |
//By default, we forward the / path to index.html automatically. | |
app.get( '/', function( req, res ){ | |
res.sendfile( __dirname + '/simplest.html' ); | |
}); | |
//This handler will listen for requests on /*, any file from the root of our server. | |
//See expressjs documentation for more info on routing. | |
app.get( '/*' , function( req, res, next ) { | |
//This is the current file they have requested | |
var file = req.params[0]; | |
//For debugging, we can track what files are requested. | |
if(verbose) console.log('\t :: Express :: file requested : ' + file); | |
//Send the requesting client the file. | |
res.sendfile( __dirname + '/' + file ); | |
}); //app.get * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Socket.IO server set up. */ | |
//Express and socket.io can work together to serve the socket.io client files for you. | |
//This way, when the client requests '/socket.io/' files, socket.io determines what the client needs. | |
//Create a socket.io instance using our express server | |
var sio = io.listen(app); | |
//Configure the socket.io connection settings. | |
//See http://socket.io/ | |
sio.configure(function (){ | |
sio.set('log level', 0); | |
sio.set('authorization', function (handshakeData, callback) { | |
callback(null, true); // error first callback style | |
}); | |
}); | |
//Socket.io will call this function when a client connects, | |
//So we can send that client a unique ID we use so we can | |
//maintain the list of players. | |
sio.sockets.on('connection', function (client) { | |
//Generate a new UUID, looks something like | |
//5b2ca132-64bd-4513-99da-90e838ca47d1 | |
//and store this on their socket/connection | |
client.userid = UUID(); | |
//tell the player they connected, giving them their id | |
client.emit('onconnected', { id: client.userid } ); | |
//Useful to know when someone connects | |
console.log('\t socket.io:: player ' + client.userid + ' connected'); | |
//When this client disconnects | |
client.on('disconnect', function () { | |
//Useful to know when someone disconnects | |
console.log('\t socket.io:: client disconnected ' + client.userid ); | |
}); //client.on disconnect | |
}); //sio.sockets.on connection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title> Real time multi-player games with HTML5</title> | |
<style type="text/css"> | |
html , body { | |
background: #212121; | |
color: #fff; | |
margin: 0; | |
padding: 0; | |
} | |
#canvas { | |
position: absolute; | |
left: 0; right: 0; top: 0; bottom: 0; | |
margin: auto; | |
} | |
</style> | |
<!-- Notice the URL, this is handled by socket.io on the server automatically, via express --> | |
<script type="text/javascript" src="/socket.io/socket.io.js"></script> | |
<!-- This will create a connection to socket.io, and print the user serverid that we sent from the server side. --> | |
<script type="text/javascript"> | |
//This is all that needs | |
var socket = io.connect('/'); | |
//Now we can listen for that event | |
socket.on('onconnected', function( data ) { | |
//Note that the data is the object we sent from the server, as is. So we can assume its id exists. | |
console.log( 'Connected successfully to the socket.io server. My server side ID is ' + data.id ); | |
}); | |
</script> | |
</head> | |
<body> | |
<canvas id="canvas"> </canvas> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//We store server messages so that we can interpolate the client positions of other clients | |
//between a past and less past point. This is offset from the server time by net_offset ms. | |
client_onserverupdate_recieved = function(data){ | |
//.... | |
//Store the server time (this is offset by the latency in the network, by the time we get it) | |
this.server_time = data.t; | |
//Update our local offset time from the last server update | |
this.client_time = this.server_time - (this.net_offset/1000); | |
//.... | |
//Cache the data from the server, | |
//and then play the timeline | |
//back to the player with a small delay (net_offset), allowing | |
//interpolation between the points. | |
this.server_updates.push(data); | |
//we limit the buffer, roughly in seconds | |
// 60fps * buffer seconds = number of samples in the array | |
if(this.server_updates.length >= ( 60*this.buffer_size )) { | |
this.server_updates.splice(0,1); | |
} | |
//.... | |
} //onserverupdate | |
//Before we draw the other clients, we interpolate them based on where they are in the timeline (client_time) | |
client_process_net_updates = function() { | |
//First : Find the position in the updates, on the timeline | |
//We call this current_time, then we find the past_pos and the target_pos using this, | |
//searching throught the server_updates array for current_time in between 2 other times. | |
// Then : other player position = lerp ( past_pos, target_pos, current_time ); | |
//.... | |
//The other players positions in the timeline, behind and in front of current_time | |
var other_target_pos = target.pos; | |
var other_past_pos = previous.pos; | |
//this is a simple lerp to the target from the previous point in the server_updates buffer | |
//we store the destination position on the ghost first, so we smooth even further if we wanted | |
this.ghosts.pos_other.pos = this.v_lerp( other_past_pos, other_target_pos, time_point ); | |
//If applying additional smoothing, | |
if(this.client_smoothing) { | |
//Lerp from the existing position to the ghost position, based on a smoothing amount and physics delta time | |
this.players.other.pos = this.v_lerp( this.players.other.pos, this.ghosts.pos_other.pos, this._pdt*this.client_smooth); | |
} else { | |
//No additional smoothing? Just apply the position | |
this.players.other.pos = this.pos(this.ghosts.pos_other.pos); | |
} | |
//.... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
client_handle_input = function() { | |
//.... | |
//Update what sequence we are on now | |
this.input_seq += 1; | |
//Store the input state as a snapshot of what happened. | |
this.players.self.inputs.push({ | |
inputs : input, | |
time : this.local_time.fixed(3), | |
seq : this.input_seq | |
}); | |
//Send the packet of information to the server. | |
//The input packets are labelled with an 'i' in front. | |
var server_packet = 'i.'; | |
server_packet += input.join('-') + '.'; | |
server_packet += this.local_time.toFixed(3).replace('.','-') + '.'; | |
server_packet += this.input_seq; | |
//Go | |
this.socket.send( server_packet ); | |
//.... | |
} | |
//In the update loop and when we recieve a message from the server | |
//we immediately set the client position, as the server has final say, | |
//but then we apply any input the server has not acknowledged yet, keeping our position consistent | |
client_process_net_prediction_correction = function() { | |
//.... | |
//The most recent server update | |
var latest_server_data = this.server_updates[this.server_updates.length-1]; | |
var my_last_input_on_server = this.players.self.host ? | |
latest_server_data.his : | |
latest_server_data.cis; | |
//If the server has sent us a 'host input sequence' or 'client input sequence' state | |
if(my_last_input_on_server) { | |
//The last input sequence index in my local input list | |
var lastinputseq_index = -1; | |
//Find this input in the list, and store the index of that input | |
for(var i = 0; i < this.players.self.inputs.length; ++i) { | |
if(this.players.self.inputs[i].seq == my_last_input_on_server) { | |
lastinputseq_index = i; | |
break; | |
} | |
} | |
//Now we can crop the list of any updates we have already processed | |
if(lastinputseq_index != -1) { | |
//since we have now gotten an acknowledgement from the server that our inputs here have been accepted | |
//and we now predict from the last known position instead of wherever we were. | |
//remove the rest of the inputs we have confirmed on the server | |
var number_to_clear = Math.abs(lastinputseq_index + 1)); | |
//Then clear the past ones out | |
this.players.self.inputs.splice(0, number_to_clear); | |
//The player is now located at the new server position, authoritive server | |
this.players.self.cur_state.pos = this.pos(my_server_pos); | |
this.players.self.last_input_seq = lastinputseq_index; | |
//Now we reapply all the inputs that we have locally that | |
//the server hasn't yet confirmed. This will 'keep' our position the same, | |
//but also confirm the server position at the same time. | |
this.client_update_physics(); | |
this.client_update_local_position(); | |
} // if(lastinputseq_index != -1) | |
} //if my_last_input_on_server | |
//.... | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
this version of the demo is written in the pre-socket.io 0.9 version so it needs to be refactored