How to make a multi-player game – part 1

Realm of the Mad God
Submit to StumbleUpon

Hello and welcome back to my blog!

Its been a while since my last post, this is because I’ve been working on a multi-player game, called mmoAsteroids which you can play by clicking on the icon on the side-bar. This post is my attempt to crystallise the most important points I’ve learned during the making of this game and my other multi-player prototypes.

Introduction

Firstly, it’s important to identify what I mean by multi-player and what implications that has. I’m talking about non-local multi-player, over the internet rather than at the same computer.

Of course this means there needs to be some kind of way for the players to communicate with each other over the internet. I’ve chosen the server-client model rather than peer-to-peer, because that’s what my target platform client, Adobe Flash supports.

Client side

Why choose Flash? Because:

  • The install base is massive, even compared to HTML5
  • It’s a fixed platform, you don’t need to worry about different browsers, or different hardware
  • The Flash portals are an amazing resource for distributing games
  • There are very mature development environments available which support full debugging via the world class Visual Studio
  • It has support for TCP sockets built right in

Server side

Because I’m using the client-server model, I need to choose a platform for the server-side code.

I’ve chosen node.js for this article, simply because it’s the fastest way to get a decently performing server set up and running with the least amount of code. Also, with your own server there are no limits on the number of clients you can support simultaneously, unlike ready made packages like player.io, or SmartFoxServer which limit you to a certain number of users until you sign up to their paid plan.

Of course, they provide a lot more features for that money as well; it’s important to weigh up the costs and benefits of whatever platform you choose.

Client-server model

How do server-client based multi-player games work?

Briefly, the client and server communicate by sending messages to one another. The client might send a message to the server saying he wants to move forwards. The server will receive the message and react accordingly. The server might send back a message saying that the player collected a power-up, or that someone else has joined the game.

The client and server both maintain a copy of the game universe; the server has the master copy and then client has a (possibly partial) copy for local simulation and display purposes. The server has authority over all the important decisions in game, like players getting hit by bullets, or being killed, or levelling up. The client has authority about what keys are being pressed and other pieces of user input data.

The reason for this separation of authority is to prevent cheating; although this is only really something you need to worry about once your game is big enough for hackers to invest time in finding cheats, it’s worth taking simple steps ahead of time to prevent the possibility of a hacked client from, for example, transmitting that he killed everyone in the world to the server, or something of that nature.

Having the server be in authority simply prevents this from being possible.

WoW recently suffered from hackers exploiting the system

MMO? MO?

It’s important to distinguish between the various types of multi-player game before we get into this too deeply, because the type can have a massive effect on the amount of code you need to write.

Starting at its simplest form, I’m going to define an MO or Multi-player Online game as a multi-player game supporting as many players as possible running on one server (a physical computer located on the internet somewhere).

An MMO is a Massively Multi-player Online game. There are many different forms this can take, but nearly all of them will involve more than one physical server working together to handle the huge load that ‘massively’ implies.

How they work together will define how the game is to be designed; for example you might want your game universe to be shared by all players at the same time, as in Eve Online; this requires that several servers work together to form a large shard to share the load. Or you might want to instance your universe so that each individual server actually holds a unique copy of the universe and players in one universe cannot see players in another, like Realm of the Mad God. Or yet again, maybe you want to have some kind of combination of the two, where the universe is split up into realms and players cannot see players in other realms but they can travel between these realms, like in World of Warcraft.

Realm of the Mad God

Making a genuine MMO with a non-instanced universe is a massive undertaking, not to be attempted lightly. Even one with realms and travel between is fantastically complicated.

The more you instance your universe, the easier it gets to create the architecture because you can simply hand off instances to new servers as your game expands. If the players can’t communicate across these instances things are easier still. This is the architecture that mmoAsteroids uses currently. Each universe can hold 20 players (since the level is only a certain physical pixel size) and each server can hold 10 instances at once, giving an on-line capacity of 200 players per server.

What I’m going to describe in this article is an MO, since that is the easiest concept to code. Adapting this to a highly instanced MMO is entirely possible.

TCP vs UDP

The choice of whether to use TCP or UDP communication protocols has been made for us by the fact that Flash only supports TCP. This would seem like a big problem for a very fast paced multi-player game because TCP has a reliable, in-order delivery policy and in particular has Nagles algorithm which tries to group separate packets of data together into larger payloads before actually sending them, which can introduce lag. UDP is very different in this regard and offers no such features, but also suffers from no such issues. However, using TCP it is possible to disable Nagle and thereby remove this lag inducing restriction, so all is not lost.

TCP and UDP

At the end of the day, anyone set on using UDP for their game will have to implement their own version of TCP’s reliable, in-order delivery policy anyway, so once Nagle has been disabled there really isn’t that much separating TCP and UDP in terms of raw speed. Here is a related discussion on GameDev.net.

Requirements

If you would like to follow this article through and make your own server-side code, you will need download and install node.js. At the prototyping stage you don’t need a real server, you can just run everything locally – this is a far simpler and quicker way to develop anyway.

When you want to deploy for real, you’ll either need your own VPS, or you can subscribe to one of the growing number of node.js hosting providers. Here is a large list of providers.

The game

For this article, I’ve chosen to recreate the prototype version of mmoAsteroids. For me this game is interesting because it’s a very fast paced, bullet-hell like, twitch reaction arcade game. Not a genre that is typically seen as a multi-player game (although there are exceptions such as Continuum and Realm of the Mad God) perhaps because it’s difficult to get such games performing well over the internet.

Prototyping is extremely important and is absolutely the first thing you should do once you’ve figured out what game you’d like to make. The goal is to get the core part of the game up and into testing as fast as possible because the number one thing which will stop your game from being a success is it not being finished! Read more about how to approach the process of making a game here.

Here is the prototype in all its glory which you can buy the full source code to at the bottom of this article:

Where to start?

The fundamental component of a multi-player game is the server. In this case it’s a socket-server since clients will be connecting via sockets. A socket is just an access channel for communication over a persistent connection between computers on a network. This is different to HTTP (which is how you are reading this article right now), which is a request/response protocol with no persistent connection.

Having a persistent connection is very important for a game like this, because response times are so important – we can’t be waiting around for seconds for a response to keyboard input from the server!

The socket server

For the socket-server, I’m going to be using the net package which is built into node.js, because it provides a nice clean interface and a good starting point. The experienced reader might be wondering why I’ve not chosen to use the socket.io package which enables WebSocket support for node.js. The reason is that I want more control over the way messages get broadcast from the server and that finding a decent Flash implementation of the full web-socket protocol is difficult.

If you have chosen to use javascript for your client, you should use socket.io because javascript is best suited to WebSockets since it has no native TCP socket support of its own.

The simplest socket server

Here is what the most simple socket server possible looks like in node.js:

var port = 8000;
var host = "localhost";
var net = require('net');
 
var server = net.createServer( function(socket)
{
	socket.on('data', function(data)
	{
		console.log("data received: " + data);
	});
 
	socket.on('close', function(socket)
	{
		console.log("close received");
	});
 
	socket.on('timeout', function(data)
	{
		console.log("timeout received");
	});
 
	socket.on('error', function(data)
	{
		console.log("error received");
	});
});
 
server.listen(port, host);

It really is a testament to the quality of node.js that you can create a socket server in such few lines of code. You can browse the documentation for the net package here.

The first parameter to createServer() is the closure which will be called once a connection is made from a client. During this closure we attach various listeners to handle events that node.js sends us, such as close (when a client disconnects), or data (when a client has transmitted some data). Then on the final line we start the server listening for connections, using the port we want and the host address that we want to listen on.

The mighty node.js

The choice of which port to use is entirely up to you, although it probably pays to do some research to find out whether the majority of your players are able to connect to the internet via the port you’ve chosen; many players might be at work, or behind a firewall in a library which prohibits connections on non-standard port numbers. For mmoAsteroids, I chose 443, which is usually used by the HTTPS protocol; I found this to be the port which afforded me the greatest amount of external visibility to players.

The data event

The most interesting aspect of the above server is the data event, which indicates that data has arrived for processing – this is how the client and server will communicate primarily. Note that it does not mean a message has arrived, just that some amount of data is present to be read. It could be part of a message sent from a client, or it could be several messages together. The reason for this is that TCP is a stream oriented protocol, not a message oriented one. We need to do a little bit of work to build our own message protocol on top of this.

The message protocol

We would like the server to notify us when individual messages get received from the client. In order to do this we must design a simple message protocol. The one I’ve chosen for this tutorial is a simple UTF8 string based protocol with a terminating character separating each message.

Figure 1

Figure 1 shows a simple example of a message and the terminating character (which in this case is \n, or ASCII code 10, the newline character). Of course this means that messages cannot contain \n as part of the message, as this would confuse the protocol.

Figure 2

Figure 2 shows a possible set of individual data events numbered 1-5 encountered during the transmission of two individual messages: message\n and message\n. This gives you an example of the type of processing that we need to handle.

Fortunately, actually separating messages like this is quite easy when dealing with strings because both javascript and actionscript have the split() function on a string, which divides the input string up into chunks based on a separating character; effectively separating our messages from each other. It’s not quite as simple as that, though as you can see from Figure 2, because not all data events will yield a string with a terminating character; we need to buffer the data as we go along.

var server = net.createServer( function(socket)
{
	//
	// this gets called once for each connection we have from a client...
	//
 
	// utf8 encoding
	socket.setEncoding('utf8');
 
	// no data received yet
	var socketData = "";
 
	//
	// attach data handler
	//
 
	socket.on('data', function(data)
	{
		socketData += data;
 
		var substrings = socketData.split( "\n" );
		var lastMsg = substrings.length-1;
 
		if ( substrings[lastMsg].length!=0 )
		{
			// partial data read, store for later
			socketData = substrings[lastMsg];
		}
		else
		{
			// full read, clear buffer
			socketData = "";
		}
 
		// process all messages
		for ( var i = 0; i<lastMsg; i++ )
		{
			var message = substrings[i];
 
			// process message!
		}
	}
});

The above code is all we need in reality. Inside the data handler, the data received is first buffered:

socketData += data;

Then it is split into sub-strings:

var substrings = socketData.split( "\n" );

This returns an array with minimum of 1 entry. Then we check to see if we have a partial read:

var lastMsg = substrings.length-1;
if ( substrings[lastMsg].length!=0 )
{
	// partial data read, store for later
	socketData = substrings[lastMsg];
}

In which case we replace the buffered data with the last substring. If not it means we have a full read and we clear the buffered data completely:

else
{
	// full read, clear buffer
	socketData = "";
}

Then we go on to process any and all messages received so far:

// process all messages
for ( var i = 0; i<lastMsg; i++ )
{
	var message = substrings[i];
 
	// process message!
}

We write the same code on the client and now we have a working message protocol!

Serialisation

Now we can happily send string based messages to and from our client. But what use is that? We need to be able to send data, not just strings!

Both javascript and now actionscript, Flash Player 11.0 have built-in support for the industry standard data serialisation/deserialisation format, JSON which is what I’ve chosen to use for this tutorial. There are many other possible serialisation formats, but this is the one which is fastest to get up and running. It pays to do some research and find the format which is best suited for your individual needs.

Mmm, serialisation...

Serialising an object to JSON in javascript is as simple as:

var messageString = JSON.stringify(data) + Message.Constants.kMessageTermintor;

Notice the appended message terminator which forms part of our protocol. In actionscript it’s nearly identical:

var message:String = JSON.stringify(data) + kMessageTermintor;

Deserialisation is just as simple:

var data = JSON.parse(messageString);
var data:Object = JSON.parse(messageString);

The only slight issue is that the deserialised data is always just annoymous objects, not concrete types. This doesn’t matter so much in javascript because that’s how the language was designed to work, but actionscript is a statically typed language and we can take advantage of that to root out bugs at compile time before they occur unexpectedly in the runtime.

In order to deserialise into a concrete type in actionscript we must do some extra wrangling of the data.

package Code.System
{
	import flash.utils.*;
	import flash.geom.*;
	import flash.system.*;
	import flash.display.*;
	import flash.net.*;
 
	public class Helpers
	{
		/**
		 * 
		 * @param obj
		 * @return Class
		 *
		 */
		static public function GetClass( obj:Object ):Class
		{
			return Class( getDefinitionByName( getQualifiedClassName( obj ) ) );
		}
 
		/**
		 * 
		 * @param obj
		 * @return String
		 *
		 */
		static public function GetClassName( obj:Object ):String
		{
			return getQualifiedClassName( obj );
		}
 
		/**
		 * Take a annoymous type and try to copy the properies over into the given concrete type
		 * 
		 * @param source Annoymous type
		 * @param targetType Concrete type
		 *
		 */
		static public function CloneIntoR( source:Object, targetType:Class ):*
		{
			Assert( source!=null, "CloneIntoR(): source is null! Type=" + GetClassName(targetType) );
			var data:* = new targetType( );
 
			for ( var prop:Object in source )
			{
				Assert( data.hasOwnProperty( prop ), "Helpers.CloneInto(): supplied type didn't have required property " + prop );
 
				try
				{
					data[prop] = source[prop];
				}
				catch ( e:Error )
				{
					data[prop] = CloneIntoR( source[prop], GetClass( data[prop] ));
				}
			}
 
			return data;
		}
 
		/**
		 * Import an annoymous type from the server into a concrete type of the client
		 * 
		 * @param source Annoymous type
		 * @param targetType Concrete type
		 *
		 */
		static public function ImportServerDef( source:Object, targetType:Class ):*
		{
			var data:* = CloneIntoR( source, targetType );
			data.PostFixUp( );
			return data;
		}
	}
}

The above function Helpers.ImportServerDef() does just such a job. It will even work for recursively stored complex types. You can use it like this, for example:

var psd:PlayerSpawnDef = Helpers.ImportServerDef(data, PlayerSpawnDef);

Where PlayerSpawnDef in this case represents the data the client expects to receive from the server when a new player spawns in the game:

Message from server:

{"m_pos":{"m_x":44.179,"m_y":-426.812},"m_uid":10003,"m_name":"Guest10003"}

Concrete type:

public class PlayerSpawnDef extends BaseDef
{
	public var m_pos:Vector2;
	public var m_uid:uint;
	public var m_name:String;
 
	public function PlayerSpawnDef()
	{
		m_pos = new Vector2();
	}
}

This enables strict type checking on the individual names of each part of the message, which prevents a typo from resulting in a bug. Absolutely invaluable in anything larger than this simple prototype.

The reason the constructor creates an empty Vector2() is so that Helpers.ImportServerDef() can know the type it’s supposed to be importing – there is no way in the actionscript runtime to determine the type of variable if that variable is null.

Messages

Ok, now we’re able to send objects and data over our message protocol and have them deserialised into concrete types on the client. So how do the client and server actually know what type of message they are receiving? It might be a message to create a new player, or one to respond to player input, or any other kind. We need to pre-package our messages inside a special container class which describes the message type as well as the message data.

package Code.Messages
{
	public class MessageContainer
	{
		/** Message name */
		public var n:String;
 
		/** Message string */
		public var s:String;
 
		/** Message data - to be filled in by client */
		public var m_data:*;
	}	
}

Above is how I handle this. All messages are encoded into a container, which contains the name of the message and the message string itself. So for example message names might be defined as single letters like this, for the sake of bandwidth saving:

/**
 * Must be kept in sync with server!
 */
public class MessageNames
{
	static public const kTime:String = "a";
	static public const kCreatePlayer:String = "b";
	static public const kReady:String = "c";
	...
}

And a fully encoded message with a container might look like this:

"{"n":"c","s":"{\"m_pos\":{\"m_x\":362.597,\"m_y\":9.912},\"m_uid\":10005,\"m_name\":\"Guest10005\"}"}"

So, the message name is “c” and the message itself is:

"{\"m_pos\":{\"m_x\":362.597,\"m_y\":9.912},\"m_uid\":10005,\"m_name\":\"Guest10005\"}"

And when it comes to decoding the container string:

package Code.Messages
{
	import flash.net.*;
	import flash.utils.*;
 
	import Code.System.*;
	import Code.Maths.*;
 
	/**
	 * Static class members to handle message sending and reading
	 */
	public class Message
	{
		...
 
		/**
		 * Deserialise a message from a string
		 * 
		 * @param messageString
		 * @return MessageContainer
		 *
		 */
		static public function Get(messageString:String):MessageContainer
		{
			var containerA:Object = JSON.parse( messageString );
 
			if ( containerA.s!=undefined )
			{
				containerA.m_data = JSON.parse( containerA.s );
			}
 
			// clone into concrete type
			var container:MessageContainer = Helpers.CloneIntoR( containerA, MessageContainer );
 
			return container;
		}
	}
}

I can just call:

var messageContainer:MessageContainer = Message.Get(messageString);

Which gets the message container, containing the decoded message and its name which was picked up and separated into a message by the message protocol as previously described. I can then simply interrogate the type of message and deserialise the inner data appropriately:

switch (messageContainer.n)
{
	case MessageNames.kReady:
	{
		// unpack and create the world
		...
	}
	break;
 
	case MessageNames.kCreatePlayer:
	{
		// deserialise the message data into a concrete type
		var psd:PlayerSpawnDef = Helpers.ImportServerDef(messageContainer.m_data, PlayerSpawnDef);
                ...
	}
	break;
}

Note that the inner message data cannot be deserialised into the correct concrete type until the type of message has been understood by the game code via the message name, as shown above when creating a player.

Ok, now we have a socket server, a message protocol with serialisation and deserialisation, a way to determine the type of message received on either end. Now all we need is the actual game itself!

That’s the subject of the next article in this series!

Why do you make me wait?!

Buy the source

If you can’t wait for the next article and just have to find out how it all ends, or you simply want a decent base to build your own multi-player prototype on top of then you can buy the source code accompanying this article!

It will give you the complete prototype as shown in playable form above, with both server and client code. You will need either Flash Develop or Amethyst to build the client-side code and of course you will need node.js installed to run the server-side. You will also need Flex SDK version 4.5.1 or above. If you would like to edit the assets included with this demo, you will need Adobe Flash CS4+. Note that you cannot build the client side code with only the Flash IDE.

Your purchase will also contribute to my ability to be able to continue to write articles like this! :)

It comes in two versions, a personal edition which you are free to use for your own, non commercial purposes and a commercial version which allows you to use the code in any number of different commercial products or games:

Personal use licence – USD 49.99

Commercial use licence – USD 199.99

Subscribers can access the source here

If it seems expensive, bare in mind that it took a couple of solid weeks of programming to produce, which would have been around $3500 if I were contracted… Not to mention the many weeks and days it took to arrive at a solution powerful enough to handle a twitch reaction game over a 3G connection!

That’s all for now! Until next time, have fun!

Cheers, Paul.

Continue reading part 2 here

Submit to StumbleUpon

About Paul Firth

A games industry veteran of ten years, seven of which spent at Sony Computer Entertainment Europe, he has had key technical roles on triple-A titles like the Bafta Award Winning Little Big Planet (PSP), 24: The Game (PS2), special effects work on Heavenly Sword (PS3), some in-show graphics on the BBC’s version of Robot Wars, the TV show, as well as a few more obscure projects.   Now joint CEO of Wildbunny, he is able to give himself hiccups simply by coughing.   1NobNQ88UoYePFi5QbibuRJP3TtLhh65Jp
This entry was posted in AS3, Beginner, JSON, Multi-player, Server side, Technical and tagged , , , , , . Bookmark the permalink.

16 Responses to How to make a multi-player game – part 1

  1. Futaro says:

    I will try to do the same step by step. thanks!!

  2. Frank Carver says:

    Hi. Thanks for an excellent and thought-provoking article. I was pointed here by Andrew Scheller who left a comment on my blog at http://raspberryalphaomega.org.uk/?p=619 I’m also interested in making multi-player games but coming at it from a different kind of direction. I’m not much interested in graphical games, but the challenges of building an interactive game environment which consists of a loose federation of servers and clients rather than a centralised single “world”I find fascinating.

    As you point out in your article, the key to all this is the security and authority, but that becomes especially interesting in my case when untrusted participants can not only bring clients to the party, but also join their servers to the federation for other players to “visit”.

    • Paul Firth says:

      Hi Frank,

      Peer-to-peer, as its called is right on the cutting edge of what is possible; there are so many problems associated with it that it remains suitable only for cooperative games, and even then cheating will be very hard to avoid. On top of that you also have an increased problem with lag (if any of the clients are doing simulation and transmitting their results to peers), because one laggy internet connection will mean that all other players in the game will see the simulated object moving in a laggy fashion.

      Good luck! :)

      Cheers, Paul.

  3. Illidanek says:

    Great article, but at the beginning you show how to make the server wait for sockets, but you never show how to make the client connect to the server using actionscript.

    Could you please explain/show basic code for that part, I’m having some trouble.
    Thanks.

  4. Gary says:

    Great article. Do you perhaps have a followup article now that Air 3.8 supports UDP (DatagramSocket)?

    • Paul Firth says:

      I’m afraid not. In addition, I’m not sure I’d recommend using UDP over TCP anyway – the headache of managing reliability and all the things you need from TCP wont be worth the tiny performance improvement IMO.

  5. Gustavo says:

    Hi Paul,

    I have, let´s say, a begginner issue:

    I have been creating some “local” games in AS3 using either Flash Builder or Flash Pro CS6… Besides that I have installed Node.js and the Flex SDK. However I don´t know:
    a) What supposse to be the net package
    b) How do I make the Node.js work with Flash (builder or pro) since when I open it it looks like a command prompt window
    c) Copying the code supposses to initialize the server, but aren´t we missing the import files?

    Thanks a lot for your help (I am trying to learn and understand this process so I can develop my own MO game)

  6. niks says:

    Cool post, thanks :)

  7. venu says:

    Hi

    i have develop a rummy online malty player game its on live now,

    players also likes my games and the traffic is also good on my site,

    back server we are python,

    my flash game is disconnecting to server when i have good internet connection also,

    can please let me know why this disconnection is happening ?

    The problem with server side or flash side ?

    Thanks in advance

    venu

    • Paul Firth says:

      Could be a problem with either side; it’s impossible for me to say. You’ll need to make sure you gracefully handle reconnection should a client become disconnected.

      Cheers, Paul.

  8. venu says:

    Actually flash works with the SOCKET CONNECTION with game engine,

    after connection success there is an event (Heart beat) this event will goes from the flash for every 1 sec and waiting for the reply 5 sec

    in that 5 sec i dint receive the reply for that event i am displaying disconnects popup,

    is there any other way ? to check the connectivity with flash and engine,

    please help i am struggling for this from last 6 months,

    • Paul Firth says:

      Thats the only way to do it – there is no real way to be 100% that a socket in connected, so you have to poll like you are doing. Maybe the issue is elsewhere – are you using a VPS?

      • Venu says:

        Hi Paul,

        Sorry for the late reply, yes we are using VPS server,

        Can you please tell how to make XMLSocket connection secure…..

        • Paul Firth says:

          The problems you are having could be due to the CPU/memory load on the physical hardware that your VPS is hosted on. I had a lot of problems with this which all went away when I moved to a dedicated server. I don’t know how to make the socket connection secure I’m afraid.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

WP-SpamFree by Pole Position Marketing