Skip to main content

Relay

Netcode for GameObjects (Netcode) allows you to connect to a host by its IP and port. However, if the host isn't on the same network as you (that is, somewhere over the Internet), you will need some extra services to achieve a successful connection and thus a successful game.

Many factors impact how you connect to the remote host, and it can be tricky to implement. There are two ways of achieving such a connection:

  • NAT punching: an advanced technique that allows you to directly connect to the host computer even if it's on another network.
  • Usage of a Relay server: the server is on the Internet with a public-facing IP that you and the host can reach. After each side binds with the Relay, they can establish a connection and send data to each other via the Relay.

Netcode does not offer tools to help you do a successful punch through a NAT. But, Unity Services provides a Relay Service that can relay all Unity Transport based technology, like Netcode.

How does Relay work?

You need to install the Unity Relay SDK if you want to interact with the service. To install it, add com.unity.services.relay package to your project.

The host of your game needs to request a Relay allocation first. This process will reserve capacity on a Relay server that will allow the clients to bind to it. Once done, you will be able to get a join code for the allocation. The join code is a random string that your clients will provide to the SDK to join the correct server and allocation.

How do I enable Relay for my project?

The first thing that needs to be understood is that Relay is a Unity service. Thus, you need to add it to your organization in the Unity Dashboard (under the multiplayer section). After that, link your project to your organization (Project Settings -> Services).

How do I request an allocation?

To create an allocation on a Relay, you need to make an authenticated call to Unity backend using their SDK. On the host, call the CreateAllocationAsync method with the maximum number of expected peers. For example, a host requesting a maximum of 3 peer connections will reserve 4 slots for a 4 player game. This function can throw exceptions, and catching them can give you hints about the underlying error.

//Ask Unity Services to allocate a Relay server that will handle up to eight players: seven peers and the host.
Allocation allocation = await Unity.Services.Relay.RelayService.Instance.CreateAllocationAsync(7);

The Allocation class represents all the necessary data for a Host player to start hosting using the specific Relay server allocated. You don't need to understand each part of this allocation. You will feed them to your chosen transport that will handle the relay communication on its own. For the more curious and for reference, here is a simple overview of those parameters :

  • A RelayServer class containing the IP and port of your allocation's server
  • The allocation ID in a Base64 form and GUID form referred to as AllocationIdBytes and AllocationId A blob of encrypted bytes representing the connection data (known as ConnectionData) allows users to connect to this host.
  • A Base64 encoded Key for message signature.

Each allocation creates a unique join code which is a short string suitable for sharing over instant messages or other means. This join code will allow your clients to join your game. You can retrieve it by calling the Relay SDK like so :

string joinCode = await Unity.Services.Relay.RelayService.Instance.GetJoinCodeAsync(allocation.AllocationId);

With those two calls, you now have your Relay allocation ready and the associated join code. Pass the allocation parameters to your host transport and send the join code (a simple string) over the Internet by the mean of your choice to your clients. Finally, remember always to authenticate your users before using SDK methods. The easiest way is the anonymous one (shown in the following code snippet), but you can use more advanced techniques.

//Initialize the Unity Services engine
await UnityServices.InitializeAsync();
if (!AuthenticationService.Instance.IsSignedIn)
{
//If not already logged, log the user in
await AuthenticationService.Instance.SignInAnonymouslyAsync();
}

To sum up, here comes the whole code that allows working with the Relay server.

/// <summary>
/// RelayHostData represents the necessary information
/// for a Host to host a game on a Relay
/// </summary>
public struct RelayHostData
{
public string JoinCode;
public string IPv4Address;
public ushort Port;
public Guid AllocationID;
public byte[] AllocationIDBytes;
public byte[] ConnectionData;
public byte[] Key;
}

/// <summary>
/// HostGame allocates a Relay server and returns needed data to host the game
/// </summary>
/// <param name="maxConn">The maximum number of peer connections the host will allow</param>
/// <returns>A Task returning the needed hosting data</returns>
public static async Task<RelayHostData> HostGame(int maxConn)
{
//Initialize the Unity Services engine
await UnityServices.InitializeAsync();
//Always autheticate your users beforehand
if (!AuthenticationService.Instance.IsSignedIn)
{
//If not already logged, log the user in
await AuthenticationService.Instance.SignInAnonymouslyAsync();
}

//Ask Unity Services to allocate a Relay server
Allocation allocation = await Unity.Services.Relay.RelayService.Instance.CreateAllocationAsync(maxConn);

//Populate the hosting data
RelayHostData data = new RelayHostData
{
// WARNING allocation.RelayServer is deprecated
IPv4Address = allocation.RelayServer.IpV4
Port = (ushort) allocation.RelayServer.Port,

AllocationID = allocation.AllocationId,
AllocationIDBytes = allocation.AllocationIdBytes,
ConnectionData = allocation.ConnectionData,
Key = allocation.Key,
};

//Retrieve the Relay join code for our clients to join our party
data.JoinCode = await Unity.Services.Relay.RelayService.Instance.GetJoinCodeAsync(data.AllocationID);

return data;
}

How do I join an existing allocation?

The host of your game created a Relay allocation, and your client has received its join code. You now need to request all the allocation parameters from the join code to join the game. To do that, call the JoinAllocationAsync method with your join code like so :

//Ask Unity Services to join a Relay allocation based on our join code
JoinAllocation allocation = await Unity.Services.Relay.RelayService.Instance.JoinAllocationAsync(joinCode);

The JoinAllocation type is similar to the Allocation one used before with the host. This type includes an additional blob of bytes which is the HostConnectionData that you will need to give to the transport of your choice so you can connect to the host. Finally, like for the hosting part, always remember to authenticate your user beforehand. Here is the code often used to join a Relay :

/// <summary>
/// RelayHostData represents the necessary information
/// for a Host to host a game on a Relay
/// </summary>
public struct RelayJoinData
{
public string IPv4Address;
public ushort Port;
public Guid AllocationID;
public byte[] AllocationIDBytes;
public byte[] ConnectionData;
public byte[] HostConnectionData;
public byte[] Key;
}

/// <summary>
/// Join a Relay server based on the JoinCode received from the Host or Server
/// </summary>
/// <param name="joinCode">The join code generated on the host or server</param>
/// <returns>All the necessary data to connect</returns>
public static async Task<RelayJoinData> JoinGame(string joinCode)
{
//Initialize the Unity Services engine
await UnityServices.InitializeAsync();
//Always authenticate your users beforehand
if (!AuthenticationService.Instance.IsSignedIn)
{
//If not already logged, log the user in
await AuthenticationService.Instance.SignInAnonymouslyAsync();
}

//Ask Unity Services for allocation data based on a join code
JoinAllocation allocation = await Unity.Services.Relay.RelayService.Instance.JoinAllocationAsync(joinCode);

//Populate the joining data
RelayJoinData data = new RelayJoinData
{
// WARNING allocation.RelayServer is deprecated. It's best to read from ServerEndpoints.
IPv4Address = allocation.RelayServer.IpV4
Port = (ushort) allocation.RelayServer.Port,

AllocationID = allocation.AllocationId,
AllocationIDBytes = allocation.AllocationIdBytes,
ConnectionData = allocation.ConnectionData,
HostConnectionData = allocation.HostConnectionData,
Key = allocation.Key,
};
return data;
}

Picking a connection type (UDP, DTLS)

The sample code above uses the deprecated allocation.RelayServer field which is always the unsecure udp connection type.

The preferred approach is to iterate allocation.ServerEndpoints and select the appropriate RelayServerEndpoint for the desired ConnectionType (for example, "dtls").

How do I pass allocation data to my transport?

Now that you have an allocation (either by joining or hosting), you need to make all traffic coming from Netcode go through the Relay. And to achieve that, you need to pass allocation parameters to your transport. For now, only the UnityTransport supports the Relay protocol. To pass allocation parameters to it, you first need to retrieve it from your NetworkManager like so :

//Retrieve the Unity transport used by the NetworkManager
UnityTransport transport = NetworkManager.Singleton.gameObject.GetComponent<UnityTransport>();

You can now call the method SetRelayServerData on the retrieved transport with all the allocation parameters in this order :

  1. The Relay server endpoint host -- RelayServerEndpoint.Host or RelayServer.IpV4 (deprecated)
  2. The Relay server endpoint port -- RelayServerEndpoint.Port or RelayServer.Port (deprecated)
  3. The allocation ID in Base64 encoded bytes.
  4. The key in Base64 encoded bytes.
  5. The ConnectionData for the allocation (this is unique for the host and every other player)
  6. If you are joining a game: the HostConnectionData retrieved during your call to join, which identifies the host. Make special attention since the host connection data is an optional parameter but must be passed when joining.

Your transport is now correctly configured. You can call StartClient or StartHost and use the Netcode library as usual.

For more information see Unity Relay