In this devlog, I’ll show how we built a simple yet robust lobby (party) system using Unity Lobby. The goal was to allow players in Zoofite to create a party, join one via a code, and leave it – perfect for playing with friends or fixed teams.
Features
Create a party (lobby)
Join a party using a code
Display current teammate in the UI
Leave the party
Error handling via a central Result<T>
system we use project-wide for structured error reporting
Foundation: Unity Lobby Service
We’re using Unity Lobby as the backbone. On startup, the player authenticates and receives a unique ID via AuthenticationService.Instance.PlayerId
.
Example: Creating a Party
public async Task<Lobby> CreateOwnLobbyAsync()
{
var options = new CreateLobbyOptions
{
IsPrivate = true,
Player = new LobbyPlayer { Data = CreatePlayerData() },
Data = new Dictionary<string, DataObject>
{
{ "LobbySettings", new DataObject(DataObject.VisibilityOptions.Public, "DefaultMode") }
}
};
var lobby = await LobbyService.Instance.CreateLobbyAsync("Zoofite Lobby", 2, options);
CurrentLobby = lobby;
await SubscribeToLobbyEventsAsync();
return lobby;
}
The lobby object is stored, and event subscriptions are set up to react to changes in the lobby.
Joining a Party via Code
public async Task<bool> JoinLobbyByCodeAsync(string code)
{
var joinOptions = new JoinLobbyByCodeOptions
{
Player = new LobbyPlayer { Data = CreatePlayerData() }
};
var lobby = await LobbyService.Instance.JoinLobbyByCodeAsync(code, joinOptions);
CurrentLobby = lobby;
await SubscribeToLobbyEventsAsync();
return true;
}
After joining, the UI displays the party code and the teammate’s name.
UI Handling: GUIParty Script
The GUIParty
script handles the UI logic: buttons, input field, and teammate visibility.
private void OnCodeChanged(string code)
{
bool hasInput = !string.IsNullOrEmpty(code);
m_BTN_StartParty.gameObject.SetActive(!hasInput);
m_BTN_JoinParty.gameObject.SetActive(hasInput);
}
A useful trick: m_INP_Partycode.SetTextWithoutNotify("")
prevents unintended value change triggers when resetting the input field.
Leaving a Lobby (with Error Handling)
This is where our Result<T>
system shines:
public async Task<Result> LeaveLobbyAsync()
{
if (CurrentLobby == null)
return Result.Failure("No lobby joined", ErrorReason.NoDataAvailable);
string lobbyId = CurrentLobby.Id;
CurrentLobby = null;
try
{
await LobbyService.Instance.RemovePlayerAsync(lobbyId, AuthenticationService.Instance.PlayerId);
}
catch (LobbyServiceException e)
{
return Result.Failure($"Lobby error: {e.Message}", ErrorReason.NetworkError);
}
OnLobbyLeft?.Invoke();
return Result.Success();
}
Showing Teammate in the UI
When another player joins, we instantly reflect that in the UI:
private void OnPlayerJoinToLobby(LobbyPlayer _player)
{
if(_player.Data.TryGetValue("name", out var name))
{
m_teammateName.text = name.Value;
m_teammateName.SetActive(true);
}
}
Lobby Events Overview
Lobby changes (join, leave, kick) are handled through Unity’s LobbyEventCallbacks
:
callbacks.LobbyChanged += async changes =>
{
switch (changes)
{
case { PlayerJoined.Changed: true }:
foreach (var joined in changes.PlayerJoined.Value)
Debug.Log($"Player joined: {joined.Player.Id}");
break;
case { PlayerLeft.Changed: true }:
foreach (var left in changes.PlayerLeft.Value)
Debug.Log($"Player left: {left}");
break;
}
};
Summary
The party system works reliably and feels intuitive:
- Join and create logic is clearly separated
- All error cases are handled via
Result<T>
- The UI is responsive and player-friendly
What’s Next
Next, we plan to expand the system with:
- Lobby timeouts
- Error detection for connection loss
- Allowing the host to kick players from the lobby
- Support for more players: possibly scaling up to 3–4 player parties
Stay tuned for more devlogs – we’re just getting started!