NetCode - GameDevWeek/CodeBase GitHub Wiki

NetCode is a huge topic, and much more than I can cover here right now.

  • If you have not worked with Network Code before, this is probably not for you.
  • This will give you an overview of the code used in the CodeBase.

Before you write any code

Never let anyone outside of the NetCode Team work on NetCode. Keep things separate. Let them use Events and listen for them.

Examples

First of all, take a look at the NetCode Examples

Short description

The most important parts:

  • Datagram: This is a packet send over the network, containing everything needed to execute code on the other side.
    • NetMessageOut/In: Classes to write and read datagram messages.
  • DatagramFactory: Used to identify Datagrams and recreate them automatically.
  • NetConnection: A connection (server to client or vice versa)
  • NetServerSimple: Wraps code together to easily create a server
    • NetServerSimple.Listener: Callback interface
  • NetClientSimple: Wraps code together to easily create a client
    • NetClientSimple.Listener: Callback interface
  • NetDatagramHandler: Callback interface to handle arriving packets
  • Reliable vs Unreliable:
    • TCP is used to send reliable packets, which arrive in order.
    • UDP is used for unreliable packets, which may arrive out of order or not at all.

Everything else is not required. You can dig into the netcode to understand it if needed.

Detailed description

Datagram

One class per network packet. Take a look at this example.

The public static create() method is just a helper and is optional. Create these as needed.

Create private (or protected) attributes which contain your RAW data without any references to running code. I.e. do not store Entities directly, instead store their id. Create getters for them (avoid setters!).

Then there are overridable methods:

  • getMessageType(): Return NORMAL if you need to write data, and NONE if you just want a Datagram as sort of event without data
  • writeToMessage(): Called before sending the Datagram to the receiver.
  • readFromMessage(): Called after receiving a Datagram from the sender, but before it is handled by your listeners (see further below).
  • reset(): Called when the Datagram gets recycled. Set all attributes back to their default values.

A real-world example: link

NetMessageOut/In

These classes allow to write data into a datagrams message part or read data from the datagram. Just keep in mind, that you need to write and read in the same order and the same amount! For example, if you write n integers into the message, don't forget, that the other side needs to know how big n is.

DatagramFactory

This is probably the easiest class of all. Just copy the example and supply all Datagram classes to the NetDatagramPool constructor.

It will be used to create and recycle datagrams and you only need one class in your game.

NetConnection

This class represents a connection between server and client. Both sides have one instance of this. It can be used to send reliable and unreliable datagrams, check the status, get statistics, disconnect and even store an attachment associated with the connection (for example the server might attach some extra player information to the connection).

NetServerSimple

This class simplifies a lot of the connection code for you.

Example:

NetServerSimple netServer = new NetServerSimple(DatagramFactory.POOL);
// Start at port 9090 with max 10 clients
if (!netServer.start(9090, 10)) {
    System.exit(-1);
}
netServer.setHandler(handler);
netServer.setListener(listener);
...
// somewhere in the main loop:
netServer.update();

NetServerSimple.Listener

This interface is used to notify you about clients connecting and disconnecting to the server:

    public interface Listener {
        // return true to allow the connection
        public boolean onConnect(NetConnection connection);
        public void onDisconnect(NetConnection connection);
    }

NetClientSimple

NetClientSimple netClient = new NetClientSimple(DatagramFactory.POOL);
// connect to localhost at port 9090
if (!netClient.connect("localhost", 9090)) {
    System.exit(-1);
}
// get the connection from client to server
connection = netClient.getConnection();
netClient.setHandler(handler);

NetClientSimple.Listener

This interface is used to notify when the server disconnected.

    public interface Listener {
        public void onDisconnect();
    }

NetDatagramHandler

This is just a marker interface, but the class implementing this interface will be used to handle datagrams. You need to create handler methods in the implementing class like so:

    public void handle(MyDatagram datagram) {
        // Todo: handle
    }

These methods are called automatically when new datagrams arrive, so you don't have to check the datagram type before handling the code. They need to be public. If a method for an arriving datagram does not exist, a warning will be printed. Of course, you only need to create methods for datagrams you actually receive (for example some packets might only go from server to client).

You'll create at least one handler class for the client and one for the server. You can switch these at runtime, so having a different handler class for some sort of pre-game connection lobby is possible.

Reliable vs Unreliable

TCP is used to send reliable packets, which arrive in order:

ChatDatagram dg = ChatDatagram.create(message);
connection.sendReliable(dg);

Keep in mind, that TCP is really slow, so if you have datagrams, which get send so often, it doesn't matter if some are lost, you can use unreliable packets instead to gain speed.

UDP is used for unreliable packets, which may arrive out of order or not at all:

MoveIntentDatagram dg = MoveIntentDatagram.create(x, y);
connection.sendUnreliable(dg);

// on the receiving end, check if the sequence id is newer
public void handle(MoveIntentDatagram datagram) {
    NetConnection connection = datagram.getConnection();
    Player player = (Player) connection.getAttachment();
    if(player.lastSequenceId < datagram.getSequenceId()) {
        player.lastSequenceId = datagram.getSequenceId();
        // Todo: set movement direction
    }
}