Metric Panda Games

One pixel at a time.

UDP Networking for Multiplayer

Rival Fortress Update #19

This week has been dedicated to more multiplayer development for Rival Fortress.

I’ve fleshed out a networking model based on the one described in the original Tribes paper with a few twists of my own.

The model is built on top of the User Datagram Protocol(UDP), a very flexible and simple protocol that, is one of the most common choices when it comes to online multiplayer games (the other being the Transmission Control Protocol(TCP)).

UDP, unlike it’s “bigger brother” TCP, is an unreliable protocol, meaning that when sending a packet to a destination computer there is no guarantee or notification of delivery. This may look like a downside, but as Glenn Fiedler explains very well in his post UDP vs. TCP, UDP is the ideal protocol for most games because it provides the flexibility needed for responsive online multiplayer.

Ideal packet size

Optimal networking code, among other things, tends to be about cramming as much information as possible into the least possible space. This is because, although you can theoretically construct UDP packets as large as 65,535 bytes, the ideal size is dependent on the Maximum transmission unit(MTU) of the Internet that is around 1500-bytes per packet.

Packets larger than this will cause routers to split your packet into multiple parts (packet fragmentation), complicating things for you.

Optimal Network replication

Packet size constraints require a smart approach to serializing game objects for replication. You can’t just naïvely memcpy your objects into UDP datagrams and hope for the best; most of the time you will be sending bloated data over the wire, when you could have serialized only relevant bits. For example sending 4 bytes for an integer that only needs 5 bits to represent it’s full range used in the game it’s rather wasteful, it may not seem like much in isolation, but it can add up pretty quickly over the course of a multiplayer session.

There are many ways to approach compact object serialization and replication, but the ideal way depends on the type of game you are building and the type of data you need to send over the wire. Take a look at the excellent Building a Game Network Protocol series for a primer on how to go about implementing your own solution.

Networking code generation

For Rival Fortress I extended the reflection system of the engine to generate most of the serialization and replication code.

For example the following snippet of code represents a message sent by the client to initiate a connection to the server:

MREFLECT(NetworkMessage, Reliability=Guaranteed, Priority=High)
struct MPEConnectionRequestMessage
{
  MREFLECT(NetworkValue, Size=NameLength)
  char Name[MPE_NETWORK_MAX_NAME_LENGTH+1];

  MPEVersion Version;

  MREFLECT(NetworkValue, Size=5bits)
  u32 NameLength;
};

As you can see, the struct is annotated with a no-op MREFLECT macro that gets parsed by the reflection preprocessor. In this case the engine will generate the code to send a guaranteed packet (i.e. it will retry up to a maximum amount of times until a confirmation reply is received from the destination) with a high priority (i.e. it will prioritize this message when constructing the packet over others with lower priority).

Also, you can see that the struct members are annotated to indicate how much space they should occupy in the final UDP packet: 5 bits for the NameLength and NameLength for the Name string.

Every struct like this generates a send/receive pair of functions that handle the serialization and deserialization.

The engine tries to pool multiple messages into a single UDP packet until the packet size is reached or a timer threshold is surpassed (10 milliseconds) in order to not waste bandwidth on packet overhead.