Skip to main content

Unity Lobbies

This guide will help you create a Unity project using Edgegap's lobby system and Mirror as its networking solution.

We will be using a sample game from our GitHub as a base project. You will also need an account on Egdgegap to follow along.

This guide was written using Unity 2021.3.16f1 LTS on Windows to produce all Unity builds.

Deploying the lobby system

To begin, you will need to deploy a service API to manage your lobbies by sending some requests to Edgegap's API. To do so, you will to get an API token from our dashboard.

info

If this is your first time using our platform, we strongly recommend following this documentation first and foremost, which will walk you through how to get an API token.

Once you have your token, we will be using Postman to send our requests. Once you have finished dowloading and setting it up, go in your Postman workspace, select the Collections tab and create a new blank collection named Edgegap API. Then, click on on your collection and select the Authorization tab; This is where we will put the API token, which will be added to every request in that collection. Make sure to set it up as follows:

  • Type: select the API Key option;
  • Key : write down "authorization";
  • Value : paste the API token value that you obtained from the Edgegap dashboard. Make sure to keep the token keyword;
  • Add to : select the Header option.

Save these settings by clicking the Save button, or use the ctrl + s keys.

Afterwards, right-click on your collection and click Add request. For our first request, we want to ask Edgegap to create a new Lobby Service, so name it accordingly. Then, set it up as follows:

  • URL : write down "https://api.edgegap.com/v1/lobbies";
  • Request type : From the dropdown next to the url, select POST;
  • Select the Body tab :
    • Select the raw option;
    • From the dropdown at the end of the radio buttons, select JSON;
    • In the text box, enter the following value, changing [LOBBY_SERVICE_NAME] to the name you want:
POST - /v1/lobbies
{
"name": "[LOBBY_SERVICE_NAME]"
}
warning

When selecting a unique name for your lobby service, we recommend using a short name that contains only lowercase letters.

Save your request settings by clicking the Save button, or use the ctrl + s keys. Click on the Send button, and you should get a successful 200 OK response. Next, we will create a second request to deploy the lobby service you just created; This will make it available to use in your game. Use the following settings and save them afterwards:

  • URL : write down "https://api.edgegap.com/v1/lobbies:deploy";
  • Request type : From the dropdown next to the url, select POST;
  • Select the Body tab :
    • Select the raw option;
    • From the dropdown at the end of the radio buttons, select JSON;
    • In the text box, enter the following value, changing [LOBBY_SERVICE_NAME] to the name of your service:
POST - /v1/lobbies:deploy
{
"name": "[LOBBY_SERVICE_NAME]"
}

When it has successfully been deployed after sending the request, you will be able to see it on your dashboard under the Relay tab, in Relay Profiles. It will be assigned a URL, which we will be using in our game to send requests to. To get the URL of your new service, you can create a third request with these settings and replacing [LOBBY_SERVICE_NAME] with the right value again:

  • URL : write down "https://api.edgegap.com/v1/lobbies/[LOBBY_SERVICE_NAME]";
  • Request type : From the dropdown next to the url, select GET;

After sending the request, the response should look as follows:

GET - /v1/lobbies/{service_name} - Response
{
"name": "string",
"url": "string",
"status": "string"
}
info

The lobby service URL links to a different API from Edgegap's. To see what requests can be sent to it, you can append /swagger/doc.json at the end of the service's URL and open it in your web browser. For a better viewing experience, you may want to paste the raw data of that page into a tool like Swagger Editor.

Editing the project

The next steps will take place within the Unity project; Among other things, we will be writing new code to send requests to the lobby service that we just deployed, and creating a new scene in our game that will allow players to view all available lobbies, join one, or even create their own before switching over to a match with everyone in the lobby. The latter will replace the current offline scene, GamesList.

info

The URL used in the following requests is the one you got after deploying the lobby service. You do not need the Edgegap API token for these either.

Open the project in the Unity Editor, then create a new scene called LobbiesList under Assets/FPS_Demo/Scenes. For the purposes of this guide, set up the scene to look and act as follows. Any script you may need to add can be put under a new folder placed in Assets/FPS_Demo/Scripts.

Network Manager

Add a NetworkManager prefab to your scene, which can be found under Assets/FPS_Demo/Prefabs. In the MyNetworkManager script component, set the Offline Scene to your new scene and Online Scene to the one named Main. Additionally, set the HUD to your scene's canvas.

warning

We also set the Timeout value of the EdgegapTransporter script component to a higher value. This ensures that the players are able to connect to the relay after the host has done so first. However, it is not the best way to do so, and you should instead attempt to connect again after getting a Timeout warning message.

img

Lobby list display

img

  1. A toggle that switches the displayed UI to the list of available lobbies.
    • Switching to it from another display will refresh the list of lobbies.
  2. A toggle that switches the displayed UI to the creation of a new lobby.
  3. A textInput for the player to enter their name.
    • A value must be set to be able to join a lobby.
warning

In this sample project, we use the player's display name as an ID during API requests. In an actual project, you should use a value that is unique to each player instead.

  1. A scrollView to list all available lobbies.
  2. A specific lobby.
    • We display its name, current player count and maximum player capacity.
    • Clicking on it sends a POST request to /lobbies:join with the appropriate body, then switches the displayed UI to that of a joined lobby.
  3. A button to update the list of lobbies.
    • Clicking on it calls a function that sends a GET request to /lobbies, then updates the list of lobbies with the response.
  4. A space to display error messages and the likes (ie. Unable to join the lobby, etc).
  5. A button to close the application.

Creating lobby display

In this example, we will only be setting the lobby's name and maximum player capacity, although it is possible to set other values such as a list of tags.

img

  1. A textInput for the lobby's name.
  2. A textInput and buttons for the lobby's capacity
    • The buttons decrease/increase the value in the input.
    • A validation is made to ensure the value is equal to at least 2.
  3. A button to create the lobby.
    • It will not be interactable if either the player or the lobby's names are empty.
    • Clicking on it calls a function that sends sends a POST request to /lobbies with the appropriate body, then switches the displayed UI to that of a joined lobby.

Joined lobby display

When switching over to this display, a GET request to /lobbies/{lobby_id} is immediately called.

note

There is no way as of yet to automatically detect when the lobby updates or when players join or leave, so this display automatically calls the API and refreshes itself every few seconds.

img

  1. These toggles/buttons will show a prompt asking if the player wishes to leave the lobby while in this display.
    • Confirming the choice calls a function that sends a POST request to lobbies:leave with the appropriate body, then switches the displayed UI to the corresponding toggle/close the application.
  2. Another button to exit the lobby, which works the same way as the options in #1.
note

If the host leaves the lobby, a DELETE request to lobbies/{lobby_id} will instead need to be sent. Every other player that had joined it will then need to be disconnected by sending a lobbies:leave request.

  1. The lobby's name.
  2. The lobby's ID.
  3. The player's name cannot be modified while in this display.
  4. A scrollView to list all players who joined.
  5. A specific player.
    • If the player is the lobby's host, an icon is displayed and the player appears at the top of the list.
  6. A button to start the match.
    • Only the host may click it.
    • Clicking it calls a function that sends sends a POST request to /lobbies:start with the appropriate body. Afterwards, certain steps need to be taken to connect the players and switch over the game scene.

At this point, your class that contains all the request functions should look like this:

info

Note that these functions only retrieve the information from the lobby service. Updating the HUD should be done in the function these were called from, such as clicking the Refresh button in the List Display.

...
{
private readonly HttpClient _httpClient = new();
public EdgegapTransport _EdgegapTransport = EdgegapTransport.GetInstance();
public static string lobbyApiUrl = "YOUR_SERVICE_URL"; //subject to change, make sure its up to date

/// <summary>
/// Get List
/// </summary>
/// <returns>List of lobbies</returns>
public async Task<Lobbies> RequestGet()
{
var response = await _httpClient.GetAsync($"{lobbyApiUrl}/lobbies");

if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Error code: {response.StatusCode}");
}

string responseContent = await response.Content.ReadAsStringAsync();
Lobbies content = JsonConvert.DeserializeObject<Lobbies>(responseContent);

return content;
}

/// <summary>
/// Get Lobbby
/// </summary>
/// <param name="lobbyId">Id of lobby</param>
/// <returns>Lobby</returns>
public async Task<Lobby> RequestGet(string lobbyId)
{
var response = await _httpClient.GetAsync($"{lobbyApiUrl}/lobbies/{lobbyId}");

if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Error code: {response.StatusCode}");
}

var responseContent = await response.Content.ReadAsStringAsync();
Lobby content = JsonConvert.DeserializeObject<Lobby>(responseContent);

return content;
}

/// <summary>
/// Create new Lobby
/// </summary>
/// <param name="capacity">How many players can join lobby</param>
/// <param name="name">Name of lobby</param>
/// <param name="player">Player who created lobby</param>
/// <param name="annotations">List of key/value pairs that can be injected</param>
/// <param name="isJoinable">If lobby can be joined</param>
/// <param name="tags">List of tags</param>
/// <returns>Newly created Lobby</returns>
public async Task<Lobby> RequestPost(int capacity, string name, Player player,
List<Annotation> annotations = null, bool isJoinable = true, List<string> tags = null)
{
CreatePayload objectToSerialize = new()
{
capacity = capacity,
is_joinable = isJoinable,
name = name,
player = player
};
objectToSerialize.annotations = annotations is not null ? annotations : new List<Annotation>();
objectToSerialize.tags = tags is not null ? tags : new List<string>();

var jsonContent = new StringContent(JsonConvert.SerializeObject(objectToSerialize), Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync($"{lobbyApiUrl}/lobbies", jsonContent);

if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Error code: {response.StatusCode}");
}

var responseContent = await response.Content.ReadAsStringAsync();
Lobby content = JsonConvert.DeserializeObject<Lobby>(responseContent);

return content;
}

/// <summary>
/// Start Lobby
/// </summary>
/// <param name="lobbyId">Id of Lobby</param>
/// <returns>completed task</returns>
public async Task RequestPost(string lobbyId)
{
StartPayload objectToSerialize = new()
{
lobby_id = lobbyId
};

var jsonContent = new StringContent(JsonConvert.SerializeObject(objectToSerialize), Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync($"{lobbyApiUrl}/lobbies:start", jsonContent);

if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Error code: {response.StatusCode}");
}
}

/// <summary>
/// Either Join or Leave Lobby
/// </summary>
/// <param name="action">join or leave</param>
/// <param name="lobbyId">Id of Lobby</param>
/// <param name="player">Player that joins/leaves the Lobby</param>
/// <returns>completed task</returns>
public async Task RequestPost(string action, string lobbyId, Player player)
{
JoinOrLeavePayload objectToSerialize = new()
{
lobby_id = lobbyId,
player = player
};

var jsonContent = new StringContent(JsonConvert.SerializeObject(objectToSerialize), Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync($"{lobbyApiUrl}/lobbies:{action}", jsonContent);

if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Error code: {response.StatusCode}");
}
}

/// <summary>
/// Delete Lobby
/// </summary>
/// <param name="lobbyId">Id of Lobby</param>
/// <returns>ccompleted task</returns>
public async Task RequestDelete(string lobbyId)
{
var response = await _httpClient.DeleteAsync($"{lobbyApiUrl}/lobbies/{lobbyId}");

if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"Error code: {response.StatusCode}");
}
}

...
}

You will also need to create the classes for each type of request response, based on the lobby service's API's documentation that you viewed earlier with swagger. Those should be as follows:

public class Lobbies
{
public int count { get; set; }
public List<Lobby> data { get; set; }
}

Starting the match

After calling the API route to start the lobby, you will need to wait for the lobby to set up its assignment, which allows the players to be connected together through a relay. It will set up a port for the server (used by the host), a port for the clients (used by the other players), an ID for the session created for the lobby, as well as an ID for every player in it. Until those are available, you will need to refresh the lobby every few seconds.

Once these have been set, you can put these values into the EdgegapTransporter instance, then have the NetworkManager call the right function to start the match depending on whether the player is the host or not. Your code should look like this, and can be placed in the same class as the other request functions:

{
...

public void StartMatch(Lobby lobby, Player player)
{
Assignment assign = lobby.assignment;
ushort clientPort = (ushort)assign.ports.Find(port => port.name == "client").port;
ushort serverPort = (ushort)assign.ports.Find(port => port.name == "server").port;

_EdgegapTransport.ChangeValue(
assign.ip,
clientPort,
serverPort,
lobby.lobby_id,
assign.authorization_token,
(uint)player.authorization_token
);

if (player.is_host)
{
NetworkManager.singleton.StartHost();
}
else
{
NetworkManager.singleton.StartClient();
}
}
}

The game will then switch over to the scene you set as the Online Scene in the NetworkManager GameObject, and everyone in the lobby will be able to play together It is possible for a player to join after the lobby has started, if the capacity allows it.

You can get the final version of this sample with all of the edits on our GitHub.