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.
- C++
- Blueprints
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.
- C++
- Blueprints
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
- C++
- Blueprints
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.
- C++
- Blueprints
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.
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.
}
}