Skip to main content

Creating a session

To start a multiplayer game, you'll need to have your dedicated server or hosting player create a session that other players can join.

Prerequisites

If you are using peer-to-peer networking and you're having players host games, you'll need to make sure your game client has the "Game Server" role in the Developer Portal. Only client IDs with the "Game Server" role have permission to create sessions.

Create the session

Before you can create a session, you should ensure Unreal Engine has started listening for connections on the map you want to play on. You can do this by executing the console command:

open MapName?listen

This can be done through the "Execute Console Command" blueprint node.

Once the new level has loaded, get the online session interface:

#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "Interfaces/OnlineSessionInterface.h"

// ...

IOnlineSubsystem* Subsystem = Online::GetSubsystem(this->GetWorld());
IOnlineSessionPtr Session = Subsystem->GetSessionInterface();

Register the event handler so you know when the session has been created. CreateSessionDelegateHandle is declared as an FDelegateHandle.

this->CreateSessionDelegateHandle =
Session->AddOnCreateSessionCompleteDelegate_Handle(FOnCreateSessionComplete::FDelegate::CreateUObject(
this,
&UMyClass::HandleCreateSessionComplete));

Then call CreateSession with your session settings:

TSharedRef<FOnlineSessionSettings> SessionSettings = MakeShared<FOnlineSessionSettings>();
SessionSettings->NumPublicConnections = 4; // The number of players.
SessionSettings->bShouldAdvertise = true; // Set to true to make this session discoverable with FindSessions.
SessionSettings->bUsesPresence = false; // Set to true if you want this session to be discoverable by presence (Epic Social Overlay).

// You *must* set at least one setting value, because you can not run FindSessions without any filters.
SessionSettings->Settings.Add(
FName(TEXT("SessionSetting")),
FOnlineSessionSetting(FString(TEXT("SettingValue")), EOnlineDataAdvertisementType::ViaOnlineService));

// Create a session and give the local name "MyLocalSessionName". This name is entirely local to the current player and isn't stored in EOS.
if (!Session->CreateSession(0, FName(TEXT("MyLocalSessionName")), *SessionSettings))
{
// Call didn't start, return error.
}

When your callback fires, you'll want to handle any errors (check bWasSuccessful), and then deregister the event handler:

void UMyClass::HandleCreateSessionComplete(
FName SessionName,
bool bWasSuccessful)
{
// TODO: Check bWasSuccessful to see if the session was created.

// Deregister the event handler.
IOnlineSubsystem *Subsystem = Online::GetSubsystem(this->GetWorld());
IOnlineSessionPtr Session = Subsystem->GetSessionInterface();
Session->ClearOnCreateSessionCompleteDelegate_Handle(this->CreateSessionDelegateHandle);
this->CreateSessionDelegateHandle.Reset();
}

Register players

When players connect to your dedicated or listen server, you need to register those incoming players so that Epic Online Services is aware of how many players are currently connected, and how many free slots there are on the server.

There are two steps to registering players when the game session is first created:

  • Registering players that had already joined the server when the session was created (e.g. local players for a listen server), and
  • Handling new players in PostLogin so they are registered as they join the server

To track when all existing players have been registered, define a boolean field in AMyGameMode:

class AMyGameMode // : ...
{
// ...

private:
bool bAllExistingPlayersRegistered;

// ...
};

Ensure that bAllExistingPlayersRegistered is set to false when the game mode is constructed.

Registering players that are already connected to the server

After you create the session, you need to iterate through all of the local player controllers and register them:

void AMyGameMode::RegisterExistingPlayers()
{
for (auto It = this->GetWorld()->GetPlayerControllerIterator(); It; ++It)
{
APlayerController* PlayerController = It->Get();

FUniqueNetIdRepl UniqueNetIdRepl;
if (PlayerController->IsLocalPlayerController())
{
ULocalPlayer *LocalPlayer = PlayerController->GetLocalPlayer();
if (IsValid(LocalPlayer))
{
UniqueNetIdRepl = LocalPlayer->GetPreferredUniqueNetId();
}
else
{
UNetConnection *RemoteNetConnection = Cast<UNetConnection>(PlayerController->Player);
check(IsValid(RemoteNetConnection));
UniqueNetIdRepl = RemoteNetConnection->PlayerId;
}
}
else
{
UNetConnection *RemoteNetConnection = Cast<UNetConnection>(PlayerController->Player);
check(IsValid(RemoteNetConnection));
UniqueNetIdRepl = RemoteNetConnection->PlayerId;
}

// Get the unique player ID.
TSharedPtr<const FUniqueNetId> UniqueNetId = UniqueNetIdRepl.GetUniqueNetId();
check(UniqueNetId != nullptr);

// Get the online session interface.
IOnlineSubsystem *Subsystem = Online::GetSubsystem(PlayerController->GetWorld());
IOnlineSessionPtr Session = Subsystem->GetSessionInterface();

// Register the player with the "MyLocalSessionName" session; this name should match the name you provided in CreateSession.
if (!Session->RegisterPlayer(FName(TEXT("MyLocalSessionName")), *UniqueNetId, false))
{
// The player could not be registered; typically you will want to kick the player from the server in this situation.
}
}

this->bAllExistingPlayersRegistered = true;
}

Then, in the HandleCreateSessionComplete function from above, call RegisterExistingPlayers.

Registering players for new connections via PostLogin

In your GameMode, override the OnPostLogin event and add the following code:

void AMyGameMode::PostLogin(APlayerController* InPlayerController)
{
if (!this->bAllExistingPlayersRegistered)
{
// RegisterExistingPlayers has not run yet. When it does, it will register this incoming player
// controller.
Super::PostLogin(InPlayerController);
return;
}

check(IsValid(InPlayerController));

// This code handles logins for both the local player (listen server) and remote players (net connection).
FUniqueNetIdRepl UniqueNetIdRepl;
if (InPlayerController->IsLocalPlayerController())
{
ULocalPlayer *LocalPlayer = InPlayerController->GetLocalPlayer();
if (IsValid(LocalPlayer))
{
UniqueNetIdRepl = LocalPlayer->GetPreferredUniqueNetId();
}
else
{
UNetConnection *RemoteNetConnection = Cast<UNetConnection>(InPlayerController->Player);
check(IsValid(RemoteNetConnection));
UniqueNetIdRepl = RemoteNetConnection->PlayerId;
}
}
else
{
UNetConnection *RemoteNetConnection = Cast<UNetConnection>(InPlayerController->Player);
check(IsValid(RemoteNetConnection));
UniqueNetIdRepl = RemoteNetConnection->PlayerId;
}

// Get the unique player ID.
TSharedPtr<const FUniqueNetId> UniqueNetId = UniqueNetIdRepl.GetUniqueNetId();
check(UniqueNetId != nullptr);

// Get the online session interface.
IOnlineSubsystem *Subsystem = Online::GetSubsystem(InPlayerController->GetWorld());
IOnlineSessionPtr Session = Subsystem->GetSessionInterface();

// Register the player with the "MyLocalSessionName" session; this name should match the name you provided in CreateSession.
if (!Session->RegisterPlayer(FName(TEXT("MyLocalSessionName")), *UniqueNetId, false))
{
// The player could not be registered; typically you will want to kick the player from the server in this situation.
}

Super::PostLogin(InPlayerController);
}

Unregister players

When a player disconnects from your server, you need to unregister the player so that Epic Online Services can be aware that there is a new free slot on the server.

This is a little more complicated than registering players, as the Logout function on GameMode gets called after the player controller has cleared it's net connection.

We'll create a custom PreLogout function on our custom game mode, and override APlayerController::OnNetCleanup to call it before the net connection is cleared.

info

If a player unexpectedly disconnects from the server (e.g. their Internet gets interrupted or their game client crashes), you may not want to unregister them as a player. Players can find and reconnect to a session they were in, as long as the server does not deregister them from the session.

If you want to support players reconnecting to game sessions after disconnect, you'll need to distinguish between a user disconnecting due to network timeout, and legitimately leaving the game or match. Only unregister players who will not need to reconnect to the server.

On your APlayerController implementation, override OnNetCleanup:

void AMyPlayerController::OnNetCleanup(UNetConnection *Connection)
{
if (GetLocalRole() == ROLE_Authority && PlayerState != NULL)
{
AMyGameModeBase *GameMode = Cast<AMyGameModeBase>(GetWorld()->GetAuthGameMode());
if (GameMode)
{
GameMode->PreLogout(this);
}
}

Super::OnNetCleanup(Connection);
}

On your AGameModeBase implementation, add a custom PreLogout function:

void AMyGameModeBase::PreLogout(APlayerController *InPlayerController)
{
check(IsValid(InPlayerController));

// This code handles logins for both the local player (listen server) and remote players (net connection).
FUniqueNetIdRepl UniqueNetIdRepl;
if (InPlayerController->IsLocalPlayerController())
{
ULocalPlayer *LocalPlayer = InPlayerController->GetLocalPlayer();
if (IsValid(LocalPlayer))
{
UniqueNetIdRepl = LocalPlayer->GetPreferredUniqueNetId();
}
else
{
UNetConnection *RemoteNetConnection = Cast<UNetConnection>(InPlayerController->Player);
check(IsValid(RemoteNetConnection));
UniqueNetIdRepl = RemoteNetConnection->PlayerId;
}
}
else
{
UNetConnection *RemoteNetConnection = Cast<UNetConnection>(InPlayerController->Player);
check(IsValid(RemoteNetConnection));
UniqueNetIdRepl = RemoteNetConnection->PlayerId;
}

// Get the unique player ID.
TSharedPtr<const FUniqueNetId> UniqueNetId = UniqueNetIdRepl.GetUniqueNetId();
check(UniqueNetId != nullptr);

// Get the online session interface.
IOnlineSubsystem *Subsystem = Online::GetSubsystem(InPlayerController->GetWorld());
IOnlineSessionPtr Session = Subsystem->GetSessionInterface();

// Unregister the player with the "MyLocalSessionName" session; this name should match the name you provided in CreateSession.
if (!Session->UnregisterPlayer(FName(TEXT("MyLocalSessionName")), *UniqueNetId))
{
// The player could not be unregistered.
}
}