Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TenzinCando/d425ba6630dee75271cec4a52d7e4b72 to your computer and use it in GitHub Desktop.
Save TenzinCando/d425ba6630dee75271cec4a52d7e4b72 to your computer and use it in GitHub Desktop.
Multi-player games in HTML5
/* 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 *
/* 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
<!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>
/*
Shared between server and client.
In this example, `item` is always of type game_player.
*/
game_core.prototype.check_collision = function( item ) {
//Left wall.
if(item.pos.x <= item.pos_limits.x_min) {
item.pos.x = item.pos_limits.x_min;
}
//Right wall
if(item.pos.x >= item.pos_limits.x_max ) {
item.pos.x = item.pos_limits.x_max;
}
//Roof wall.
if(item.pos.y <= item.pos_limits.y_min) {
item.pos.y = item.pos_limits.y_min;
}
//Floor wall
if(item.pos.y >= item.pos_limits.y_max ) {
item.pos.y = item.pos_limits.y_max;
}
//Fixed point helps be more deterministic
item.pos.x = item.pos.x.fixed(4);
item.pos.y = item.pos.y.fixed(4);
}; //game_core.check_collision
//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);
}
//....
}
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