P2P Networking from Top to Bottom

August 31, 2017

Working with P2P Networking in Unity, C#, and Facepunch.Steamworks

Networking feels like a massive task to take on as an indie and the truthfulness of it is that it becomes harder and harder the later you start trying to implement it. This is to say that, if you plan on having your game be networked, you ideally start implementing network functionality from day one.

The reason for this is that, when you’re working with networked code in a P2P context, you’ll want to be able to use the same code that executes local game logic to be the same code that you call when you need to execute an event that you receive from the network.

Otherwise, you’ll end up having two functions for every call, one that works locally and one that the network calls that then calls the local function. The second method is totally viable but the former allows for much cleaner code. When you receive a call from the network, you just call the same function that you would normally call to move something locally.

To elaborate a bit more, here’s how I’m doing this in my game.

To start, I just write down what the action I went sent over the network. These are usually“big actions” that affect moment to moment play like moving, shooting, spawning, etc. Once you know the action, you need to come up with your own network packet type that your game understands.“Network Packet” sounds both scary and archaic, but it loosely translates to“data structure that your game understands and can be easily sent across a network”.

The goal here is to send the minimum amount of information required to still successfully produce the required action on the side of the person you’re send to. You want your packets to be small so you can ensure that you are using a lot of data over the network due to any sort of interruptions.

Thinking about what this is can be difficult depending on the action you’re implementing, but, if you’re reusing client code of network code it’s pretty easy — it’s likely just the function parameters! So, let’s assume I’m trying to move something, and get started with my move function.

When I move something, I do the moving (in the omitted code), then send out an event that states that something has moved. This event is subscribed to by my class P2PHandler, and when it receives this event calls the requisite function that bundles up the network packet to be sent:

A few notes on this before moving on. At points NOTE A/B, you can see that I’m kind of“casting” my function parameters to different types. I’m converting a list of Tiles to a List of Vector2’s and converting the moved interactable to just be an int. What I’m doing here is serializing my parameters into a format that can be serialized by Unity.

Serialization is a whole topic in and of itself, but the core idea is that Unity cannot remember every detail about any type you declare, and instead only“knows” about a specific set of types outlined here. It is possible to make it so that something like Tile could just be easily sent across the wire as is, but often time optimizing for serialization can lead to a lot of excess code and headaches as you may have to jump through extra hoops to get at simple parameters.

What I prefer doing for bigger types like this is to just send a integer reference that points to the object on the client side, and make sure that those tables mirror each other on initialization.

To actually send this data, I use P2PMessage which is defined like so:

So a P2PMessage is basically a key, and then a P2PAction that is serialized in the P2PMessage constructor. P2PMove in this case is the container for the data that is necessary to properly interpret the move action on the client side. So here’s the last bit of code from above that does the packing and sending:

So you package up your parameters and send off the message. I’m using Facepunch.Steamworks to do this, which makes sending P2P data really easy. Here’s the code for that:

public void SendP2PMessage(P2PMessage message)
{
//serialize the whole message
string serializedMessage = JsonUtility.ToJson(message);
//convert to bytes
var data = Encoding.UTF8.GetBytes( serializedMessage );
//loop through all members of the current lobby and send the data
//lobbies in my game act as a sort of server
foreach (ulong id in GetLobbyMemberIDs())
{
// don't send the P2P message to the player who sent the message
if(id == PlayerID){continue;}
//call the Facepunch.Steamworks DLL
Native.Networking.SendP2PPacket( id, data, data.Length );
}
}

Once you send the packet, how do you get it? In the same class that sends the P2PMessage, I’m also subscribing to P2PEvents:

Which brings us back to the same class that sent the message originally, but on the other side of the wire! For now, the P2PHandler class both sends and parses events, though it may change if things become to bulky. When it recieves a message, it is parsed like this:

This code calls the move function for the proper intractable, and now both clients are in sync! I didn’t realize this would be as long-winded when I set out to write about some P2P stuff, but I hope this helps out anyone looking to implement P2P networking in their (Unity) games! I will say that this envato tuts post was a godsend, and most of the ideas here are an outgrowth of that. I highly recommend people who are interested in this read over that, because it provides a great high level overview of P2P networking. Until next time!