Create and join lobbies
Bring players together in a lobby and keep its members up to date.
Sign in first. Choose the lobby provider and any mirrors in Betide → Crossplay. A lobby groups players; hosting the match is a separate step.
Create a lobby
Loading the interactive viewer.
Game module dependencies: UCIKCore, BetideCore.
#include "Engine/GameInstance.h"
#include "Lobbies/UCIKLobbySubsystem.h"
#include "UCIKCore.h"
void CreateParty(UGameInstance &GameInstance)
{
auto *Service = GameInstance.GetSubsystem<UUCIKLobbySubsystem>();
if (!Service)
return;
FUCIK_LobbySettings Settings;
Settings.MaxMembers = 4;
Settings.JoinPolicy = EUCIK_JoinPolicy::Public;
FUCIK_Attribute Mode;
Mode.StringValue = TEXT("Coop");
Settings.Attributes.Add(TEXT("Mode"), Mode);
Service->CreateLobby(
TEXT("GameLobby"), Settings,
FBetideCallback<FUCIK_Lobby>::CreateWeakLambda(
&GameInstance, [](const TBetideResult<FUCIK_Lobby> &Result) {
if (!Result.bOk)
{
UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
return;
}
UE_LOG(LogUCIK, Log, TEXT("Lobby: %s"), *Result.GetValue().Name.ToString());
}));
}Use the same Lobby Name for later operations. It is a local slot, not a public lobby ID. Mirror creation can leave a usable primary lobby: check Has Mirrors and Mirror Error before promising cross-store discovery.
Discord can only mirror. Add Betide.Discord to Lobbies → Mirrors and every crossplay lobby also becomes a Discord lobby keyed by a secret UCIK generates; the secret rides the primary lobby’s metadata so joiners follow it without a search. Discord lobbies cannot be listed or searched, so Discord is never the primary. Because the Discord client SDK cannot change lobby metadata after creation, UCIK stores the mirror’s metadata in a marked lobby message; those messages never reach On Lobby Message. Discord reports no owner and no kick or promote, so host election and moderation stay with the primary provider.
Find lobbies
Loading the interactive viewer.
Game module dependencies: UCIKCore, BetideCore.
#include "Engine/GameInstance.h"
#include "Lobbies/UCIKLobbySubsystem.h"
#include "UCIKCore.h"
void FindCoopParties(UGameInstance &GameInstance)
{
auto *Service = GameInstance.GetSubsystem<UUCIKLobbySubsystem>();
if (!Service)
return;
FUCIK_LobbySearch Search;
Search.MaxResults = 20;
Search.MinOpenSlots = 1;
FUCIK_SearchFilter Filter;
Filter.Key = TEXT("Mode");
Filter.Value.StringValue = TEXT("Coop");
Search.Filters.Add(Filter);
Service->FindLobbies(
Search, FBetideCallback<TArray<FUCIK_Lobby>>::CreateWeakLambda(
&GameInstance, [](const TBetideResult<TArray<FUCIK_Lobby>> &Result) {
if (!Result.bOk)
{
UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
return;
}
UE_LOG(LogUCIK, Log, TEXT("Lobbies: %d"), Result.GetValue().Num());
}));
}Filters are combined with AND. Attribute types must match the host’s values. Keep the complete search result for joining. Search results carry a member count without the member list; show it with Get Crossplay Lobby Member Count and Get Crossplay Lobby Open Slots (sessions: Get Crossplay Session Open Slots), which report -1 when the provider gave no capacity.
Join a lobby
Loading the interactive viewer.
Game module dependencies: UCIKCore, BetideCore.
#include "Engine/GameInstance.h"
#include "Lobbies/UCIKLobbySubsystem.h"
#include "UCIKCore.h"
void JoinParty(UGameInstance &GameInstance, const FUCIK_Lobby &SelectedLobby)
{
auto *Service = GameInstance.GetSubsystem<UUCIKLobbySubsystem>();
if (!Service)
return;
Service->JoinLobby(
SelectedLobby, false,
FBetideCallback<FUCIK_Lobby>::CreateWeakLambda(
&GameInstance, [](const TBetideResult<FUCIK_Lobby> &Result) {
if (!Result.bOk)
{
UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
return;
}
UE_LOG(LogUCIK, Log, TEXT("Joined: %s"), *Result.GetValue().Name.ToString());
}));
}Leave Auto Travel off for a waiting room. Use On Lobby Game Connection Available when the host starts the match; see listen servers.
Manage the lobby
Expand a party
Loading the interactive viewer.
Read lobby members
Loading the interactive viewer.
Set your ready state
Loading the interactive viewer.
Send lobby chat
Loading the interactive viewer.
Transfer ownership
Loading the interactive viewer.
Remove a member
Loading the interactive viewer.
Leave a lobby
Loading the interactive viewer.
#include "Engine/GameInstance.h"
#include "Lobbies/UCIKLobbySubsystem.h"
#include "UCIKCore.h"
namespace LobbyExamples
{
UUCIKLobbySubsystem *Service(UGameInstance &Game)
{
auto *Lobbies = Game.GetSubsystem<UUCIKLobbySubsystem>();
if (!Lobbies)
UE_LOG(LogUCIK, Warning, TEXT("Lobby subsystem is unavailable"));
return Lobbies;
}
template <typename T> FBetideCallback<T> Report(UGameInstance &Game, FString Operation)
{
return FBetideCallback<T>::CreateWeakLambda(
&Game, [Operation = MoveTemp(Operation)](const TBetideResult<T> &Result) {
if (!Result.bOk)
{
UE_LOG(LogUCIK, Warning, TEXT("%s: %s"), *Operation, *Result.Error.GetLogString());
return;
}
UE_LOG(LogUCIK, Log, TEXT("%s completed"), *Operation);
});
}
void ExpandParty(UGameInstance &Game)
{
auto *Lobbies = Service(Game);
if (!Lobbies)
return;
FUCIK_LobbyUpdateSettings Update;
Update.MaxMembers = 6;
Update.bUpdateMaxMembers = true;
Lobbies->UpdateLobby(TEXT("GameLobby"), Update,
Report<FUCIK_Lobby>(Game, TEXT("Expand party")));
}
void RefreshPartyMembers(UGameInstance &Game)
{
auto *Lobbies = Service(Game);
if (!Lobbies)
return;
Lobbies->QueryLobbyMembers(
TEXT("GameLobby"), FBetideCallback<TArray<FUCIK_Member>>::CreateWeakLambda(
&Game, [](const TBetideResult<TArray<FUCIK_Member>> &Result) {
if (!Result.bOk)
{
UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
return;
}
UE_LOG(LogUCIK, Log, TEXT("Members: %d"), Result.GetValue().Num());
}));
}
void SetReady(UGameInstance &Game, bool bReady)
{
auto *Lobbies = Service(Game);
if (!Lobbies)
return;
FUCIK_Attribute Value;
Value.Type = EUCIK_AttributeType::Bool;
Value.BoolValue = bReady;
TMap<FString, FUCIK_Attribute> Attributes;
Attributes.Add(TEXT("Ready"), Value);
Lobbies->UpdateLobbyMemberAttributes(TEXT("GameLobby"), Attributes, {}, Report<bool>(Game, TEXT("Set ready")));
}
void SendPartyChat(UGameInstance &Game, const FString &Message)
{
auto *Lobbies = Service(Game);
if (!Lobbies)
return;
Lobbies->SendLobbyMessage(TEXT("GameLobby"), Message, Report<bool>(Game, TEXT("Send chat")));
}
void TransferPartyOwnership(UGameInstance &Game, const FUCIK_PlayerHandle &Member)
{
auto *Lobbies = Service(Game);
if (!Lobbies)
return;
Lobbies->PromoteLobbyMember(TEXT("GameLobby"), Member, Report<bool>(Game, TEXT("Transfer ownership")));
}
void RemovePartyMember(UGameInstance &Game, const FUCIK_PlayerHandle &Member)
{
auto *Lobbies = Service(Game);
if (!Lobbies)
return;
Lobbies->KickLobbyMember(TEXT("GameLobby"), Member, Report<bool>(Game, TEXT("Request member removal")));
}
void LeaveParty(UGameInstance &Game)
{
if (auto *Lobbies = Service(Game))
Lobbies->LeaveLobby(TEXT("GameLobby"), Report<bool>(Game, TEXT("Leave party")));
}
} // namespace LobbyExamplesBind member, owner, message, and lobby-update events before joining. Search results may omit member details; query members after joining. Your game decides when everyone is ready.
Updates, ownership, and cleanup
FUCIK_LobbyUpdateSettings separates changed and removed attributes. Set bUpdateJoinPolicy when changing access; a zero MaxMembers leaves capacity unchanged. Keep UCIK_ metadata reserved for the plugin.
Owner actions require ownership. Steam removes a member through a marker that the member’s client observes and obeys; use server-side kicks for authoritative match removal.
Destroy Crossplay Lobby leaves the local membership. Do not assume it disconnects every other member. See recovery for clearing saved rejoin state after an intentional exit.