Welcome to Cykod. We are a fully-integrated, self-funded web-development startup located in Boston, MA.

Using Node.js and your phone to control a Browser game

This past week I undertook a pretty cool project as the Intern here at Cykod. We were wondering how easily a smart phone –specifically using its gyroscopes and accelerometers– could be used as a controller for a multi-player game on a larger screen. With a bit of Node.js and HTML5 magic, it turned out to be pretty simple.

Concept

We want to use a desktop (laptop, iPad, etc. something with a bigger screen that multiple players can easily look at) connection to act as the common game space. Once that screen is initialized, each player connects to a specific URL in their phone browser that links them to that game instance. We'll follow this basic outline:

  • Register new connections to the server and decide if it is a room or mobile user:
  • Create a new room,
  • Or add the connection to an existing room
  • Constantly poll the mobile device for orientation data
  • Use said data to update the HTML5 Canvas game
  • Handle dropped connections appropriately

Result

The proof-of-concept full game is up at http://bit.ly/G4LSpaceWords  

 

The Technology

Node.js
Node.js is what makes this project possible. Built on Google's V8 Javascript Engine, Node.js is a server environment written in -wait for it- JavaScript. I started this project with zero knowledge of writing a server or what it would take, and Node made it super easy. Unfortunately, because the Node.js project is growing so quickly, up-to-date documentation with current-version examples can be lacking.

Socket.io
A Node.js module, Socket.io adds multiple levels of socket support, accommodating nearly every browser. This allows Node.js to quickly communicate with the browser similar to the way AJAX would, but with less overhead. Socket.io does not yet seem to support the updated Websocket Spec deployed in the latest Chrome and Firefox. As soon as it does, performance will be significantly smoother compared to the current default of XHR Long polling.  Now supported in the newest version of Socket.io, and controller performance is better.

Mobile Phone Orientation
Nearly all smart phones on the market have some sort of accelerometer or gyroscope in them. The phone parses this information and makes it accessible in the browser. The HTML5 DeviceOrienation and DeviceMotion events allow us to take advantage of this. You can read more about it at HTML5 Rocks. (Fun Fact: The native Android browser does not support access of this data. A third party browser like Firefox is needed)

Building the server

The actions performed by the mobile phone and desktop will be completely independent of each other, communicating only through the server (app.js). The best way to do this is through two different html files. We could embed all the code into one large file, but that would overly complicate our simple proof of concept. Create index.html (for the desktop), and mobile.html (for the phone).

Room Structure
Each game instance needs a desktop screen and at least one mobile device to communicate. We don't want any outside interference, so we'll create a new game instance for each desktop screen. We'll refer to a game instance as a room. Each room contains a desktop and an unknown number of mobile connections. For clarity's sake (and to help distinguish between different rooms), we'll also include a room id. Room Structure:

App.js

//An array to store the existing rooms
var rooms = [];
function room(roomSocket, roomId){
  this.roomSocket = roomSocket;  //Stores the socket for the desktop connection
  this.roomId = roomId;          //The room id/name. A unique string that links desktop to mobile
  this.mobileSockets = [];       //A list of all the mobile connections
};

Definition: A socket is how we refer to a single connection. So for every device connected to the server, a socket is created. Node.js and Socket.io streamline this process, automatically creating and destroying sockets as needed. This continually simplifies our room management.
 

New Connections
When a new socket is created (anytime a user visits index.html or mobile.html), we want to notify app.js so it can either:

A) Create a new room (if index.html is sending the data),

index.html

//This sends the signal 'new room' to the server, along with data containing the room name id. For live deployment, this should be a random string. See the full documentation for example socket.emit('new room', { room: “lolcats”});

 

app.js

//The receiving end of 'new room'
socket.on("new room", function(data){
  //Pushes a new room instance, storing the desktop connection as the roomSocket and data.room ("lolcats") as the roomId
  rooms.push(new room(socket, data.room));
});

Awesome! So we have a new room object being created and stored each time a new desktop connection is established. For efficiency's sake, we want to monitor any lost connections and delete those rooms, but we'll get into that shortly.

 

B) Or add a user to a room instance (if mobile.html is sending the data)

mobile.html

socket.emit('connect mobile', { room: getUrlVars()["id"]}, function(data){
  if(data.registered = true){
    registered = true;
  }else{
    $('#error').append(data.error);
  }
});

Similar to a new desktop connection, mobile.html sends an emit('connect mobile') to app.js. We also pass along the id parameter from the URL (mobile.html?id=RoomName) to specify which room this mobile user should belong to. Lastly, a callback function informs the mobile user that they have connected successfully, and can now transmit data.

app.js

socket.on("connect mobile", function(data, fn){
  var desktopRoom = null;

  //Cycle through all the rooms and find the room with the same roomId as our mobile device
  for(var i = 0; i < rooms.length; i++){
    if(rooms[i].roomId == data.room){
      desktopRoom = i;
    }
  }

  if(desktopRoom !== null){
    rooms[desktopRoom].mobileSockets.push(socket);

    //Store the position of our room that this mobile device belongs to
    socket.set('roomi', desktopRoom, function(){})

    //Return the callback as true
    fn({registered: true});

    //Access the room that this socket belongs to, and emit directly to the index.html to 'add user' with the socketId as a unique indentifier.
    rooms[socket.store.data.roomi].roomSocket.emit('add user', socket.id, data);
  }else{
    //Callback returns false with an error
    fn({registered: false, error: "No live desktop connection found"});
  }
});

The server searches through the array of rooms to locate the correct one. Once we've identified the room by its name, it is saved in desktopRoom. After double checkingagainst a null value to ensure we have located a room, the mobile socket is pushed into the room[id].mobileSocket array. The socket.set method is then used to store data directly in the socket. We save the room's position in the array. With this value, we can easily access the appropriate room without having to search the array each time. The callback is returned as true if successful, or false with an error message.

Lost connections
But what happens when we lose a connection? Socket.io has a built in 'disconnect' function that is called when a socket disconnects. We start by testing for the existence of socket.store.data.roomi. Because we only set that value for the mobile connections, we know instantly the type of connection.

node.js

socket.on("disconnect", function(){
  var destroyThis = null;
  i
f(typeof socket.store.data.roomi == 'undefined'){

    //The lost socket is a room

    //Search through all the rooms and remove the socket which matches our disconnected id
    for(var i in rooms){
      if(rooms[i].roomSocket.id == socket.id){
        destroyThis = rooms[i];
      }
    }

    if(destroyThis !== null){ rooms.splice(destroyThis, 1);}

  }else{
    //Lost socket is a mobile connections

    //Sort through the mobile sockets for that particular room, and remove accordingly
    var roomId = socket.store.data.roomi;
    for(var i in rooms[roomId].mobileSockets){
      if(rooms[roomId].mobileSockets[i] == socket){
        destroyThis = i;
      }
    }

    if(destroyThis !== null){rooms[roomId].mobileSockets.splice(destroyThis, 1);}

    //alert the room that this user was a member of
    rooms[roomId].roomSocket.emit('remove user', socket.id);
  }
});

We now have a fully functioning Node.js server that handles all connections, disconnections, and stores our data in easy to parse room structures.

Updating the Tilt Data

With the connection established, we can easily send tilt data from the mobile to desktop side. This is covered in much better detail over at HTML5 Rocks, so we'll skip to the Node stuff.

mobile.html

function deviceOrientationHandler(tiltLR, tiltFB, dir, motionUD) {
  if(registered){
    socket.emit('update movement', { tilt_LR: Math.round(tiltLR), tilt_FB: Math.round(tiltFB)});
  }
}

This function will run every time the phone gets new mobile tilt data. We're really only interested in the tiltLR (Left Right), and tilfFB (front back). Read through the HTML5 Rocks for more info. app.js receives this data and immediately forwards it along to the desktop corresponding to our mobibile device.

app.js

//Update the position socket.on("update movement", function(data){
  if(typeof socket.store.data.roomi !== 'undefined'){
    if(typeof rooms[socket.store.data.roomi] !== 'undefined'){
      rooms[socket.store.data.roomi].roomSocket.emit('update position', socket.id, data);
    }
  }
});

Because mobile.html transmits data after a connection is established, there is no error checking to make sure the index.html counterpart still exists. Our 'update movement' performs this check to ensure the desktop connection still exits. It emits data directly to the correct room in the 'update position' signal.

index.html

socket.on('update position', function(socketId, data){}

The server will then signal index.html and call the socket.on("update position") . This passes all our tilt data to the desktop client, leaving the world wide open for awesome canvas implementations.

I won't be going into the canvas game aspects, but the example code and a basic version of canvas game play is available on Github. You can also see our language learning game implementation, Super Space Words .

Posted Wednesday, Aug 24 2011 09:15 PM by James Burke | Development, HTML5 | Node.js, Socket.io

Comments    Leave a comment

Posted by Babzy at 02:42AM on January 25 2012

Nice

Posted by gman at 01:34PM on March 03 2012

Great minds think alike
http://powpow.googlecode.com

Posted by Friv at 10:57AM on February 11 2014

Good information

Posted by wilco at 09:25AM on May 02 2014

few days ago this code works perfectly i have not made any changes , now its not working it always gives me a message on console :
 error: “No live desktop connection found”

Posted by wilco at 09:26AM on May 02 2014

can any body help me how to solve it plzzz

Leave a Comment

Display Name:


Your Email (Optional, not displayed):

Add a Comment: