[ part 1 ] – [ part 2 ] – [ part 3 ] – [ part 4 ]
At this point, you should already have enough to produce a realtime multiplayer game. You’ve got a client that sends a message to the server and gets broadcasted back to other clients.
I still wanted to add this section, because there are many ways to handle multiplayer once you have the system hooked up. Depending on which method you use, synchronization can be tricky.
Let’s go over a few examples where we use the system to produce a multiplayer app or game.
Example 1 – Collaborative Drawing board
Let’s the program you wrote previously as a baseline. You don’t have to change anything, except just remove the code that sends “Hello, World” when you click on the screen.
First, we’re going to implement our drawing program as single player. Add the following code in the constructor:
var color:uint = uint(uint.MAX_VALUE*Math.random());
stage.addEventListener(MouseEvent.MOUSE_DOWN,
function(e:MouseEvent):void {
penDown(e.stageX,e.stageY);
});
stage.addEventListener(MouseEvent.MOUSE_MOVE,
function(e:MouseEvent):void {
if(e.buttonDown) penMove(e.stageX,e.stageY,color);
});
We also need to implement the penDown and penMove actions:
private function penDown(x:Number,y:Number):void {
graphics.moveTo(x,y);
}
private function penMove(x:Number,y:Number,color:uint):void {
graphics.lineStyle(1,color);
graphics.lineTo(x,y);
}
This code lets you draw when you move the mouse with the left button down. Now we need to make sure the draw commands are broadcasted.
Replace the code that you added in the constructor as follow:
stage.addEventListener(MouseEvent.MOUSE_DOWN,
function(e:MouseEvent):void {
connection.send("penDown",e.stageX,e.stageY);
});
stage.addEventListener(MouseEvent.MOUSE_MOVE,
function(e:MouseEvent):void {
if(e.buttonDown)
connection.send("penMove",e.stageX,e.stageY,color);
});
You will also need to add message handlers inside your onJoin function:
private function onJoin(connection:Connection):void {
log("Successfully joined room:",connection.roomId);
this.connection = connection;
connection.addMessageHandler("penDown",
function(m:Message,x:Number,y:Number):void {
penDown(x,y);
});
connection.addMessageHandler("penMove",
function(m:Message,x:Number,y:Number,color:uint):void {
penMove(x,y,color);
});
}
To test the collaborative whiteboard click here.
Example 2 – Multiplayer shoot-em-up
Another way to use the bounce server, rather than broadcast commands, is to broadcast the self state. For our multiplayer shoot-em-up, we will broadcast the x,y coordinates of the space ship, and add a shoot command.
Let’s start by adding our listeners inside the constructor:
stage.addEventListener(MouseEvent.MOUSE_DOWN,
function(e:MouseEvent):void {
if(connection)
connection.send("shoot",e.stageX,e.stageY,client.connectUserId);
});
stage.addEventListener(MouseEvent.MOUSE_MOVE,
function(e:MouseEvent):void {
if(connection)
connection.send("moveShip",e.stageX,e.stageY,client.connectUserId);
});
addEventListener(Event.ENTER_FRAME,animateScene);
Also add the message handlers inside the onJoin function:
private function onJoin(connection:Connection):void {
log("Successfully joined room:",connection.roomId);
this.connection = connection;
connection.addMessageHandler("shoot",
function(m:Message,x:Number,y:Number,id:String):void {
shoot(x,y,id);
});
connection.addMessageHandler("moveShip",
function(m:Message,x:Number,y:Number,id:String):void {
moveShip(x,y,id);
});
}
The animateScene will be called in a loop continuously to animate and display the spaceship and missiles. Next, we’re adding our moveShip and shoot functions:
private function getShip(id:String):Object {
return ships[id] ? ships[id]:
(ships[id] = {color:uint(uint.MAX_VALUE*Math.random())});
}
private function moveShip(x:Number,y:Number,id:String):void {
var ship:Object = getShip(id);
ship.x = x;
ship.y = y;
}
private function shoot(x:Number,y:Number,id:String):void {
var ship:Object = getShip(id);
ship.x = x;
ship.y = y;
createMissile(x,y,-10,0);
createMissile(x,y,10,0);
createMissile(x,y,0,10);
createMissile(x,y,0,-10);
}
private function createMissile(x:Number,y:Number,xmov:Number,ymov:Number):void {
missiles[Math.random()] = {x:x+xmov,y:y+ymov,xmov:xmov,ymov:ymov};
}
Note that you’ll also need to define the ships
and missiles
member variables as objects. Since we have to remember each ship’s position in the screen, we have to index them using each player’s ID. We also index missiles by a random ID to track their trajectory and remove them when they exit the screen. Next we have the animateScene function:
private function animateScene(e:Event):void {
graphics.clear();
for each(var ship:Object in ships) {
graphics.lineStyle(1,ship.color);
graphics.moveTo(ship.x,ship.y-10);
graphics.lineTo(ship.x+10,ship.y);
graphics.lineTo(ship.x,ship.y+10);
graphics.lineTo(ship.x-10,ship.y);
graphics.lineTo(ship.x,ship.y-10);
}
graphics.lineStyle(1,0xFF0000);
for(var mid:String in missiles) {
var missile:Object = missiles[mid];
graphics.moveTo(missile.x,missile.y);
graphics.lineTo(missile.x+missile.xmov,missile.y+missile.ymov);
missile.x += missile.xmov;
missile.y += missile.ymov;
if(missile.x>stage.stageWidth || missile.x<0
|| missile.y>stage.stageHeight || missile.y<0) {
delete missiles[mid];
}
}
}
Demo available here.
One thing that we can noticed is that the movement of the ship you control is not really that smooth. That’s because there is a delay for the message to go to the server and back. When making a game, you need to make sure that the controls are as responsive as possible. So rather than sending the coordinates of our own ship to the server and back, we will apply them directly. Change the MOUSE_MOVE event listener as follow:
stage.addEventListener(MouseEvent.MOUSE_MOVE,
function(e:MouseEvent):void {
if(connection) {
connection.send("moveShip",e.stageX,e.stageY,client.connectUserId);
moveShip(e.stageX,e.stageY,client.connectUserId);
}
});
You will also need to change the message handler to ignore messages that relate to your own ship.
connection.addMessageHandler("moveShip",
function(m:Message,x:Number,y:Number,id:String):void {
if(id!=client.connectUserId)
moveShip(x,y,id);
});
The ship should look a lot more responsive. See the difference in the demo here.
Now this raises a few questions. If the ship’s coordinates is slightly different than the one sent to the other player, how can we determine if a ship has been hit by a missile? The answer is simple. Simply do a collision check only on your own ship, and if a collision occurred, broadcast that message. While another player might see a missile colliding with your missile yet not damaging it, it’s not as frustrating as seeing your own ship explode when you clearly dodged a missile. In terms of real-time, you can never get perfect synchronization (unless you update the ship using the coordinates that made a roundtrip to the server and back, which deteriorates responsiveness).
So for our final test, we will test collision between the ship and the missiles. Inside the animateScene function at the end, add the following:
if(client && ships[client.connectUserId]) {
var myShip:Object = ships[client.connectUserId];
var rect:Rectangle = new Rectangle(myShip.x-10,myShip.y-10,20,20);
for each(missile in missiles) {
if(rect.contains(missile.x,missile.y)) {
// ship has been hit
gameOver = true;
connection.send("gotHit",client.connectUserId);
break;
}
}
}
And add a message handler inside the onJoin function:
connection.addMessageHandler("gotHit",
function(m:Message,id:String):void {
delete ships[id];
log("Ship",id,"has been destroyed!");
});
You can check out the result here.
Next >> TUTORIAL – Create realtime multiplayer games using Player.IO (Part 4 – Lobby)
[ part 1 ] – [ part 2 ] – [ part 3 ] – [ part 4 ]