Let’s make a multiplayer game (part 3)

Unified Client/Server Game Code

For this article series I want to have a single version of the game logic that can be applied to both the client and the server. For now I am going to make use of inversion of control via the INetworkManager interface to achieve the different networking behavior for the client and server. (This is by no means the only way, I just chose this approach because I prefer it in this case.)

The INetworkManager interface has the following members which basically wrap calls to the NetServer and NetClient classes that I will explain later.

public interface INetworkManager : IDisposable
{
    void Connect();

    void Disconnect();

    NetIncomingMessage ReadMessage();

    void Recycle(NetIncomingMessage im);

    NetOutgoingMessage CreateMessage();
}

Using the above interface my ExampleGame constructor is as follows:

public ExampleGame(INetworkManager networkManager)
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    this.networkManager = networkManager;
}

The client and server console applications can therefore instantiate the ExampleGame class as follows:

static void Main(string[] args)
{
    using (var game = new ExampleGame(new ServerNetworkManager()))
    {
        game.Run();
    }
}

static void Main(string[] args)
{
    using (var game = new ExampleGame(new ClientNetworkManager()))
    {
        game.Run();
    }
}

Setting up the Server

The code required to set up a server is contained in the ServerNetworkManager class. The interesting part is the Connect method which is as follows.

public void Connect()
{
    var config = new NetPeerConfiguration("Asteroid")
    {
        Port = Convert.ToInt32("14242"),
        //SimulatedMinimumLatency = 0.2f,
        //SimulatedLoss = 0.1f
    };
    config.EnableMessageType(NetIncomingMessageType.WarningMessage);
    config.EnableMessageType(NetIncomingMessageType.VerboseDebugMessage);
    config.EnableMessageType(NetIncomingMessageType.ErrorMessage);
    config.EnableMessageType(NetIncomingMessageType.Error);
    config.EnableMessageType(NetIncomingMessageType.DebugMessage);
    config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);

    netServer = new NetServer(config);
    netServer.Start();
}

The first thing we need to do is set up a NetPeerConfiguration. For the server the only thing required is the port that our server will be listening on. Depending on your usage scenario you can let the user customize the port. In this case we will use a hardcoded value.

The next set of config.EnableMessageType method calls are used to tell the NetServer what type of messages to expect. I have exposed all the debug, warning and error messages as well as the ConnectionApproval message type.

The ConnectionApproval message type allows you to control if a client is allowed to connect. This is useful if you want to limit the number of connections or do some kind of authentication.

Once we’ve set up the config information we can instantiate our NetServer and call the start method. The server is now up and running and listening on the configured port.

You will notice some commented lines where I instantiate the NetPeerConfiguration class. These lines allow us to simulate latency and losses over the network. This is very handy for testing purposes to ensure that your game will be able to handle the real-world issues of internet gameplay.

Setting up the Client

The code required to set up a client is contained in the ClientNetworkManager class. Once again the interesting stuff happens in the Connect method.

public void Connect()
{
    var config = new NetPeerConfiguration("Asteroid")
    {
        //SimulatedMinimumLatency = 0.2f,
        //SimulatedLoss = 0.1f
    };

    config.EnableMessageType(NetIncomingMessageType.WarningMessage);
    config.EnableMessageType(NetIncomingMessageType.VerboseDebugMessage);
    config.EnableMessageType(NetIncomingMessageType.ErrorMessage);
    config.EnableMessageType(NetIncomingMessageType.Error);
    config.EnableMessageType(NetIncomingMessageType.DebugMessage);
    config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);

    this.netClient = new NetClient(config);
    this.netClient.Start();

    this.netClient.Connect(new IPEndPoint(NetUtility.Resolve("127.0.0.1"), Convert.ToInt32("14242")));
}

You will notice that I don’t specify a client port in the configuration. You can do so if you want, but it’s not required.

The same message types as the server are enabled on the client and instead of a NetServer we instantiate a NetClient and call the Start method.

To make the connection to the server the client needs to connect to it. I have hardcoded the IP address and port in this case. In a real game you would allow the user to specify this information or provide some kind of server browser.

Calling the Connect method starts the handshaking process between the client and the server. Since we’ve enabled the ConnectionApproval message type we need to handle this message to complete the connection.

Processing Network Messages

Now that we have our client and server connection code we need to update the game to make use of it. I make the INetworkManager.Connect() call in the Initialize() method as follows:

protected override void Initialize()
{
    // TODO: Add your initialization logic here
    this.networkManager.Connect();

    base.Initialize();
}

The next step is to make sure our game handles the network messages. I typically process the network messages in the Update method as follows:

protected override void Update(GameTime gameTime)
{
    // Allows the game to exit
    if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Escape))
        this.Exit();

    // TODO: Add your update logic here
    this.ProcessNetworkMessages();

    base.Update(gameTime);
}

private void ProcessNetworkMessages()
{
    NetIncomingMessage im;

    while ((im = this.networkManager.ReadMessage()) != null)
    {
        switch (im.MessageType)
        {
            case NetIncomingMessageType.VerboseDebugMessage:
            case NetIncomingMessageType.DebugMessage:
            case NetIncomingMessageType.WarningMessage:
            case NetIncomingMessageType.ErrorMessage:
                Console.WriteLine(im.ReadString());
                break;
            case NetIncomingMessageType.StatusChanged:
                switch ((NetConnectionStatus)im.ReadByte())
                {
                    case NetConnectionStatus.Connected:
                        Console.WriteLine("{0} Connected", im.SenderEndpoint);
                        break;
                    case NetConnectionStatus.Disconnected:
                        Console.WriteLine("{0} Disconnected", im.SenderEndpoint);
                        break;
                    case NetConnectionStatus.RespondedAwaitingApproval:
                        im.SenderConnection.Approve();
                        break;
                }
                break;
        }

        this.networkManager.Recycle(im);
    }
}

The above ProcessNetworkMessages() method is a typical example of how you would process network messages. The above code will continue to process messages until there are no more messages.

Each message will have a type that we can test for and then handle appropriately. You will notice that the types of messages I am checking for matches the types I enabled when I configured both the NetServer and NetClient instances.

When we’re done with a message we recycle it. You don’t have to recycle it, but according to the Lidgren documentation it is more efficient to do so.

The interesting case in the above example is the RespondedAwaitingApproval status type. This is where we can either Approve or Deny a connection attempt. Since we enabled the ConnectionApproval message type we have to call the im.SenderConnection.Approve() method to complete the connection process.

When you run the code you should see the following output in the client and server console windows.

image

The source code for this article series can be found on google code.

In the next article I will demonstrate how to get some asteroids flying around the screen synchronized on both the client and server.

Let’s make a multiplayer game (part 2)

Game Overview

Asteroid Belt Assault, the game I’m using as foundation for this series is basically a combination of Galaga and Asteroids. The player controls a spaceship that is flying through an asteroid belt. The asteroids themselves fly across the game screen and can bounce off each other. In addition to the asteroids the player is faced with waves of enemies that follow a pre-determined path and fire shots at the player. The player can move around the screen and fire his cannon at the enemies and asteroids.

Networking Architecture

There are various network topology choices available when making a multiplayer game. In this article series I will make use of a client-server architecture. I find that thinking about game networking in terms of a dedicated client and server helps me to better understand (and explain) what happens when.

In a pure client-server model the server typically handles all the game logic with a client being a dumb terminal only concerned with rendering the game state. This would be the easiest to program, but unfortunately in reality this model is not suitable due to latency and bandwidth constraints.

In order to mask the effects of network latency we need to do some processing of game logic on the client. There is therefore a lot of similarity between the game logic on the client and server, in fact they could essentially be the same code. So why do we need a server then?

The server is responsible for the game state, it needs to coordinate starting a game, ending a game and everything that happens in between. What this means is that the server in this game is responsible for generating our enemies, asteroids and making sure that the whole simulation remains in sync.

Solution Structure

The following screenshot shows the solution structure for our game.

image

The actual game code is contained in the XNA (windows) project called MultiplayerGame with its associated content contained in the MultiplayerGameContent project.

The Client and Server projects are console projects used to start up separate instances of the MultiplayerGame project. The reason for this is to allow us to have both a client and server instance of the game while we’re debugging. To achieve this we set multiple startup projects as follows:

image

From the above solution structure you will notice that I didn’t create a separate client XNA game project and a separate server XNA game project. The reasoning is that the game itself is identical on both the server and the client except for who owns the game state i.e. the server.

In the next article I will cover setting up the connection between the client and the server.

Let’s make a multiplayer game (part 1)

Last year I discovered the indie game called Terraria, which inspired me to try and make a multiplayer game. This proved to be a challenging undertaking due to various factors, with the foremost being the lack of information with regards to multiplayer game programming to be found on the internet.

The purpose of this article series is to document some of my experiences with the hope of it being useful to someone wanting to make a multiplayer game. I will be making use of XNA as rendering platform and the Lidgren networking library.

Please note: I am no XNA or networking expert. What I can promise is that by the end of the series I will have covered the basics and leave you with a working multiplayer game.

The game I am going to make (Asteroid Belt Assault) comes from the book XNA 4.0 Game Development by Example written by Kurt Jaegers. The emphasis will therefore not be on making a game but rather on making a game multiplayer.