Creating and managing game lobbies
Tutorial
·
intermediate
·
+10XP
·
20 mins
·
Unity Technologies
In this tutorial, you will learn how to create and manage a game lobby within your Unity project.
1. What are game lobbies?
With a friends list and Rich Presence set up, you've almost finished the framework that will allow players to invite their friends into your game. Creating and managing game lobbies through the Social SDK is the next step towards a fully functional invite system that will allow a player’s friends to see the lobby they’ve created, receive an invite to it through the Discord client, or request an invite through the Discord client.
Lobbies in the Discord Social SDK are virtual spaces where players can gather before starting a game session together. A lobby can support both voice and text chat. When a lobby is created by the Discord Social SDK, it will generate a unique lobby secret that can be used to invite other Discord users into that lobby. Lobby information can also be reflected into the user’s Rich Presence, making lobby information visible to friends on Discord.
2. Set up Lobby Code
To create and manage lobbies, you need a script that tracks the lobby state, handles player interactions through UI buttons, and communicates with the Discord Social SDK. Follow these instructions to create this script:
1. In the Unity Editor, in the Project window, open the Assets folder.
2. Right-click and select Create > MonoBehavior Script, and name it "LobbyManager".
Note: Depending on your Unity version, the path to create a MonoBehaviour script might be Create > C# Script or some other variation.
The lobby needs references to UI buttons, configuration for maximum lobby size, and variables to track the current lobby state.
3. Open the LobbyManager script and add the following lines of code:
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using Discord.Sdk;
public class LobbyManager : MonoBehaviour
{
[SerializeField]
private Button createLobbyButton;
[SerializeField]
private Button leaveLobbyButton;
[SerializeField]
private int maxLobbySize = 4;
private string lobbySecret;
private ulong currentLobby;
private RichPresence richPresence;
private Client client;
}• The LobbyManager class manages the creation and lifecycle of game lobbies.
• The createLobbyButton and leaveLobbyButton will toggle visibility based on the player's lobby state.
• The maxLobbySize variable defines how many players can join the lobby, which will be displayed within the Discord client via Rich Presence.
• The lobbySecret is a unique identifier for the lobby used with Rich Presence.
• The currentLobby stores the Discord lobby ID once created.
The lobby buttons need to be connected to their functions and hidden until the player authenticates with Discord.
4. To connect lobby buttons to their functions and hide them until player authentication, add the following functions after the variable declarations:
void Start()
{
richPresence = FindFirstObjectByType<RichPresence>();
createLobbyButton.onClick.AddListener(CreateLobby);
leaveLobbyButton.onClick.AddListener(LeaveLobby);
createLobbyButton.gameObject.SetActive(false);
leaveLobbyButton.gameObject.SetActive(false);
}
public void InitializeLobbyCreation(Client client)
{
this.client = client;
createLobbyButton.gameObject.SetActive(true);
}• The Start() method sets up button listeners and hides both buttons initially. The buttons will only become visible after the player has authenticated with Discord.
• InitializeLobbyCreation() will be called by the DiscordManager once the Discord client status is ready, enabling the create lobby button and storing a reference to the Discord client for future lobby operations.
Players need a way to create new lobbies and join existing ones through invites.
5. To enable lobby creation, add the following functions to the end of the LobbyManager class:
public void CreateLobby()
{
StopAllCoroutines();
createLobbyButton.gameObject.SetActive(false);
lobbySecret = System.Guid.NewGuid().ToString();
client.CreateOrJoinLobby(lobbySecret, OnCreateOrJoinLobby);
}
public void JoinLobby(string lobbySecret)
{
StopAllCoroutines();
createLobbyButton.gameObject.SetActive(false);
StartCoroutine(JoinLobbyCoroutine(lobbySecret));
}
private IEnumerator JoinLobbyCoroutine(string lobbySecret)
{
yield return new WaitUntil(() => { return client.GetStatus() == Client.Status.Ready; });
this.lobbySecret = lobbySecret;
client.CreateOrJoinLobby(this.lobbySecret, OnCreateOrJoinLobby);
}• CreateLobby() generates a new unique lobby secret using a GUID and calls client.CreateOrJoinLobby(). This method will either create a new lobby with the given secret or join an existing one if a lobby with that secret already exists.
• JoinLobby() allows a player to join an existing lobby using a lobby secret. This is needed when implementing game invites, which you'll add in the next section.
• The JoinLobbyCoroutine() ensures the client is ready before attempting to join.
When a lobby is created or joined, the UI and Rich Presence need to update to reflect the player’s new lobby status.
6. To add the Rich Presence status update functionality, add the following callback function to the end of the LobbyManager class:
private void OnCreateOrJoinLobby(ClientResult clientResult, ulong lobbyId)
{
if (clientResult.Successful())
{
currentLobby = lobbyId;
leaveLobbyButton.gameObject.SetActive(true);
if(richPresence != null)
{
richPresence.UpdateRichPresenceLobby(client, "In Lobby", "Waiting for players", lobbySecret, lobbyId.ToString(), maxLobbySize);
}
Debug.Log($"Successfully created or joined lobby {lobbyId}");
}
else
{
createLobbyButton.gameObject.SetActive(true);
Debug.LogError($"Failed to create or join lobby: {clientResult}");
}
}OnCreateOrJoinLobby() is called when the lobby creation or join operation completes. If successful, it stores the lobby ID, shows the leave lobby button, and updates the player's Rich Presence to display the lobby information. This allows friends to see that the player is in a lobby and enables them to request to join.
Players need a way to exit a lobby and return to the main menu state.
7. To add the functionality to leave a lobby, add the following functions at the end of the LobbyManager class:
public void LeaveLobby()
{
leaveLobbyButton.gameObject.SetActive(false);
client.LeaveLobby(currentLobby, OnLeaveLobby);
}
private void OnLeaveLobby(ClientResult clientResult)
{
if (clientResult.Successful())
{
currentLobby = 0;
lobbySecret = string.Empty;
createLobbyButton.gameObject.SetActive(true);
if(richPresence != null)
{
richPresence.UpdateRichPresence(client);
}
Debug.Log($"Successfully left lobby {currentLobby}");
}
else
{
leaveLobbyButton.gameObject.SetActive(true);
Debug.LogError($"Failed to leave lobby: {clientResult}");
}
}• LeaveLobby() calls client.LeaveLobby() to remove the player from the current lobby.
• OnLeaveLobby() handles the callback, clearing the lobby state, resetting the lobby UI, and restoring the Rich Presence to its default state.
3. Update RichPresence for lobbies
To display lobby information in Rich Presence, you'll need to add an additional function to the RichPresence script that handles lobby-specific data.
1. Open the RichPresence script and add the following function to the end of the class:
public void UpdateRichPresenceLobby(Client client, string state, string details, string lobbySecret, string lobbyId, int maxLobbySize)
{
Activity activity = new Activity();
activity.SetType(ActivityTypes.Playing);
activity.SetState(state);
activity.SetDetails(details);
var activityTimestamp = new ActivityTimestamps();
activityTimestamp.SetStart(startTimestamp);
activity.SetTimestamps(activityTimestamp);
var activityParty = new ActivityParty();
activityParty.SetId(lobbyId);
activityParty.SetCurrentSize(1);
activityParty.SetMaxSize(maxLobbySize);
activity.SetParty(activityParty);
var activitySecrets = new ActivitySecrets();
activitySecrets.SetJoin(lobbySecret);
activity.SetSecrets(activitySecrets);
client.UpdateRichPresence(activity, OnUpdateRichPresence);
}This function builds on your existing Rich Presence functionality by adding lobby-specific information. The key additions are ActivityParty, which displays the current party size (set to 1 for the current player) and maximum party size, and ActivitySecrets, which contains the join secret that allows friends to join the lobby directly from Discord. When a player is in a lobby, this information appears in their Discord profile, making it easy for friends to see the lobby status and request to join.
4. Update DiscordManager
The lobby system needs to know when the Discord client is ready so it can enable lobby creation. In order to do this, you need to connect the lobby system to the DiscordManager to activate lobbies after successful authentication.
To add this functionality, follow these instructions:
1. Open the DiscordManager script and add the following variable to the top of the class:
[SerializeField]
private LobbyManager lobbyManager;2. At the end of the OnStatusChanged() method, inside the if (status == Client.Status.Ready) code block, add the following line of code:
lobbyManager.InitializeLobbyCreation(client);Now when the player successfully authenticates with Discord, the lobby creation button will become visible.
5. Set up a lobby in the scene
The LobbyManager script needs UI buttons to allow players to create and leave lobbies. To keep your scene organized, you'll keep all UI elements under the Canvas GameObject and the lobby management logic under a separate GameObject.
To set up the lobby in your scene, follow these instructions:
1. In the Hierarchy window, right-click the Canvas GameObject, select Create Empty, and name it "Lobby".
2. Right-click the Lobby GameObject, select UI > Button - TextMeshPro, and name it "Create Lobby".
3. In the Hierarchy window, use the foldout (triangle) to expand the Create Lobby button and select the Text (TMP) GameObject. In the Inspector window, in the Text component box, enter "Create Lobby".
4. Right-click the Lobby GameObject, select UI > Button - TextMeshPro, and name it "Leave Lobby".
5. In the Hierarchy window, use the foldout (triangle) to expand the Leave Lobby button and select the Text (TMP) GameObject. In the Inspector window, in the Text component box, enter "Leave Lobby".
6. Right-click in the Hierarchy window and select Create Empty. Name this GameObject "LobbyManager" and attach the LobbyManager script to it.
Note: By keeping the Lobby UI container under the Canvas GameObject and the LobbyManager GameObject separate, you follow a clean separation of concerns: UI elements stay organized under the Canvas GameObject, while game logic components remain independent under the LobbyManager GameObject. This makes your scene hierarchy cleaner and easier to manage as your project grows.
7. In the Hierarchy window, select the LobbyManager GameObject.
8. In the Hierarchy window, click and drag the Create Lobby button GameObject into the Create Lobby Button property box. Do the same action to drag the Leave Lobby button GameObject into the Leave Lobby Button property box.
9. In the Hierarchy window, select the DiscordManager GameObject and drag the LobbyManager GameObject into the LobbyManager property box on the Discord Manager script.
The lobby UI is now connected to the LobbyManager script. When a player authenticates with Discord, the DiscordManager script will enable lobby creation through the LobbyManager script.
6. Run the project
To test the lobby functionality, follow these instructions:
1. Select the Play button to enter Play mode.
2. Select the Connect to Discord button in the UI.
3. Select the Authorize button in the Discord Application.
Once authenticated, the Create Lobby button will appear in the Game view.
4. Select the Create Lobby button.
You should see a message in the Unity Console window confirming the lobby was created successfully. The Create Lobby button will be replaced by the Leave Lobby button.
5. Open the Discord Application and view your profile.
You should see your Rich Presence updated to show "Waiting for players" with "In Lobby" and the party size displayed.
6. Select the Leave Lobby button to exit the lobby.
Your Rich Presence will return to the default state, and the Create Lobby button will reappear. You can verify that you’ve left the lobby by opening the Discord Application and viewing your profile: you’ll no longer see lobby information.
Note: In the next section, you'll implement game invitations that allow players to send direct invites to friends, making it even easier to fill lobbies and start playing together.
7. Check your scripts
Before moving on, take a moment to check that your scripts are correct.
The full LobbyManager script:
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using Discord.Sdk;
public class LobbyManager : MonoBehaviour
{
[SerializeField]
private Button createLobbyButton;
[SerializeField]
private Button leaveLobbyButton;
[SerializeField]
private int maxLobbySize = 4;
private string lobbySecret;
private ulong currentLobby;
private RichPresence richPresence;
private Client client;
void Start()
{
richPresence = FindFirstObjectByType<RichPresence>();
createLobbyButton.onClick.AddListener(CreateLobby);
leaveLobbyButton.onClick.AddListener(LeaveLobby);
createLobbyButton.gameObject.SetActive(false);
leaveLobbyButton.gameObject.SetActive(false);
}
public void InitializeLobbyCreation(Client client)
{
this.client = client;
createLobbyButton.gameObject.SetActive(true);
}
public void CreateLobby()
{
StopAllCoroutines();
createLobbyButton.gameObject.SetActive(false);
lobbySecret = System.Guid.NewGuid().ToString();
client.CreateOrJoinLobby(lobbySecret, OnCreateOrJoinLobby);
}
public void JoinLobby(string lobbySecret)
{
StopAllCoroutines();
createLobbyButton.gameObject.SetActive(false);
StartCoroutine(JoinLobbyCoroutine(lobbySecret));
}
private IEnumerator JoinLobbyCoroutine(string lobbySecret)
{
yield return new WaitUntil(() => { return client.GetStatus() == Client.Status.Ready; });
this.lobbySecret = lobbySecret;
client.CreateOrJoinLobby(this.lobbySecret, OnCreateOrJoinLobby);
}
private void OnCreateOrJoinLobby(ClientResult clientResult, ulong lobbyId)
{
if (clientResult.Successful())
{
currentLobby = lobbyId;
leaveLobbyButton.gameObject.SetActive(true);
if(richPresence != null)
{
richPresence.UpdateRichPresenceLobby(client, "In Lobby", "Waiting for players", lobbySecret, lobbyId.ToString(), maxLobbySize);
}
Debug.Log($"Successfully created or joined lobby {lobbyId}");
}
else
{
createLobbyButton.gameObject.SetActive(true);
Debug.LogError($"Failed to create or join lobby: {clientResult}");
}
}
public void LeaveLobby()
{
leaveLobbyButton.gameObject.SetActive(false);
client.LeaveLobby(currentLobby, OnLeaveLobby);
}
private void OnLeaveLobby(ClientResult clientResult)
{
if (clientResult.Successful())
{
currentLobby = 0;
lobbySecret = string.Empty;
createLobbyButton.gameObject.SetActive(true);
if(richPresence != null)
{
richPresence.UpdateRichPresence(client);
}
Debug.Log($"Successfully left lobby {currentLobby}");
}
else
{
leaveLobbyButton.gameObject.SetActive(true);
Debug.LogError($"Failed to leave lobby: {clientResult}");
}
}
}The full RichPresence script:
using UnityEngine;
using Discord.Sdk;
public class RichPresence : MonoBehaviour
{
[SerializeField]
private string details = "In Unity";
[SerializeField]
private string state = "Building a game";
private ulong startTimestamp;
void Start()
{
startTimestamp = (ulong)System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
public void UpdateRichPresence(Client client)
{
Activity activity = new Activity();
activity.SetType(ActivityTypes.Playing);
activity.SetDetails(details);
activity.SetState(state);
var activityTimestamp = new ActivityTimestamps();
activityTimestamp.SetStart(startTimestamp);
activity.SetTimestamps(activityTimestamp);
client.UpdateRichPresence(activity, OnUpdateRichPresence);
}
private void OnUpdateRichPresence(ClientResult result)
{
if (result.Successful())
{
Debug.Log("Rich presence updated!");
}
else
{
Debug.LogError($"Failed to update rich presence {result.Error()}");
}
}
public void UpdateRichPresenceLobby(Client client, string state, string details, string lobbySecret, string lobbyId, int maxLobbySize)
{
Activity activity = new Activity();
activity.SetType(ActivityTypes.Playing);
activity.SetState(state);
activity.SetDetails(details);
var activityTimestamp = new ActivityTimestamps();
activityTimestamp.SetStart(startTimestamp);
activity.SetTimestamps(activityTimestamp);
var activityParty = new ActivityParty();
activityParty.SetId(lobbyId);
activityParty.SetCurrentSize(1);
activityParty.SetMaxSize(maxLobbySize);
activity.SetParty(activityParty);
var activitySecrets = new ActivitySecrets();
activitySecrets.SetJoin(lobbySecret);
activity.SetSecrets(activitySecrets);
client.UpdateRichPresence(activity, OnUpdateRichPresence);
}
}The full DiscordManager script:
using UnityEngine;
using UnityEngine.UI;
using Discord.Sdk;
public class DiscordManager : MonoBehaviour
{
[SerializeField]
private ulong applicationId;
[SerializeField]
private RichPresence richPresence;
[SerializeField]
private FriendsList friendsList;
[SerializeField]
private LobbyManager lobbyManager;
private Client client;
private string codeVerifier;
void Awake()
{
client = new Client();
client.AddLogCallback(OnLog, LoggingSeverity.Error);
client.SetStatusChangedCallback(OnStatusChanged);
client.SetUserUpdatedCallback(OnUserUpdated);
}
private void OnDestroy()
{
client.Disconnect();
}
private void OnLog(string message, LoggingSeverity severity)
{
Debug.Log($"Log: {severity} - {message}");
}
private void OnStatusChanged(Client.Status status, Client.Error error, int errorCode)
{
Debug.Log($"Status changed: {status}");
if (error != Client.Error.None)
{
Debug.LogError($"Error: {error}, code: {errorCode}");
}
if (status == Client.Status.Ready)
{
richPresence.UpdateRichPresence(client);
friendsList.LoadFriends(client);
lobbyManager.InitializeLobbyCreation(client);
}
}
public void StartOAuthFlow()
{
var authorizationVerifier = client.CreateAuthorizationCodeVerifier();
codeVerifier = authorizationVerifier.Verifier();
var args = new AuthorizationArgs();
args.SetClientId(applicationId);
args.SetScopes(Client.GetDefaultCommunicationScopes());
args.SetCodeChallenge(authorizationVerifier.Challenge());
client.Authorize(args, OnAuthorizeResult);
}
private void OnAuthorizeResult(ClientResult result, string code, string redirectUri)
{
if (!result.Successful())
{
Debug.Log($"Authorization result: [{result.Error()}]");
return;
}
GetTokenFromCode(code, redirectUri);
}
private void GetTokenFromCode(string code, string redirectUri)
{
client.GetToken(applicationId, code, codeVerifier, redirectUri, OnGetToken);
}
private void OnGetToken(ClientResult result, string token, string refreshToken, AuthorizationTokenType tokenType, int expiresIn, string scope)
{
if (token == null || token == string.Empty)
{
Debug.Log("Failed to retrieve token");
}
else
{
client.UpdateToken(AuthorizationTokenType.Bearer, token, OnUpdateToken);
}
}
private void OnUpdateToken(ClientResult result)
{
if (result.Successful())
{
client.Connect();
}
else
{
Debug.LogError($"Failed to update token: {result.Error()}");
}
}
private void OnUserUpdated(ulong userId)
{
friendsList.UpdateFriends();
friendsList.SortFriends();
}
}