Skip to main content

OnlineSubsystemNull

Unreals built in basic online system provides you with all you need to create a prototype, however once it gets into serious production you will want to switch to the OnlineSubsystem and its related Online Services.

You can also test your game with multiplayer features, although, once you exit the editor the simple action of joining a server is no longer so trivial. Within the editor, Unreal Engine provides an online subsystem that is no longer present once you are in a standalone game instance. What you need to do is implement this on your own, or use one of the provided, for development purposes the OnlineSubSystemNull is suggested to work for LAN gameplay only.

Prototype

For the sake of making life easier I've created an basic downloadable OnlineSubsystemNull project prototype which we will use as base from now on, it has Sessions implemented already too, it gives you a decent head start to begin tinkering.

Code

A list of various code snippets to simplify implementation.

Session

Create Session

void CreateSession(int32 NumPublicConnections, bool IsLANMatch);
void UCSSessionSubsystem::CreateSession(int32 NumPublicConnections, bool IsLANMatch)
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (!sessionInterface.IsValid())
{
OnCreateSessionCompleteEvent.Broadcast(false);
return;
}

LastSessionSettings = MakeShareable(new FOnlineSessionSettings());
LastSessionSettings->NumPrivateConnections = 0;
LastSessionSettings->NumPublicConnections = NumPublicConnections;
LastSessionSettings->bAllowInvites = true;
LastSessionSettings->bAllowJoinInProgress = true;
LastSessionSettings->bAllowJoinViaPresence = true;
LastSessionSettings->bAllowJoinViaPresenceFriendsOnly = true;
LastSessionSettings->bIsDedicated = false;
LastSessionSettings->bUsesPresence = true;
LastSessionSettings->bIsLANMatch = IsLANMatch;
LastSessionSettings->bShouldAdvertise = true;

LastSessionSettings->Set(SETTING_MAPNAME, FString("Your Level Name"), EOnlineDataAdvertisementType::ViaOnlineService);

CreateSessionCompleteDelegateHandle = sessionInterface->AddOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegate);

const ULocalPlayer* localPlayer = GetWorld()->GetFirstLocalPlayerFromController();
if (!sessionInterface->CreateSession(*localPlayer->GetPreferredUniqueNetId(), NAME_GameSession, *LastSessionSettings))
{
sessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegateHandle);

OnCreateSessionCompleteEvent.Broadcast(false);
}
}
void OnCreateSessionCompleted(FName SessionName, bool Successful);
void UCSSessionSubsystem::OnCreateSessionCompleted(FName SessionName, bool Successful)
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (sessionInterface)
{
sessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegateHandle);
}

OnCreateSessionCompleteEvent.Broadcast(Successful);
}

Update Session

void UpdateSession();
void UCSSessionSubsystem::UpdateSession()
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (!sessionInterface.IsValid())
{
OnUpdateSessionCompleteEvent.Broadcast(false);
return;
}

TSharedPtr<FOnlineSessionSettings> updatedSessionSettings = MakeShareable(new FOnlineSessionSettings(*LastSessionSettings));
updatedSessionSettings->Set(SETTING_MAPNAME, FString("Updated Level Name"), EOnlineDataAdvertisementType::ViaOnlineService);

UpdateSessionCompleteDelegateHandle =
sessionInterface->AddOnUpdateSessionCompleteDelegate_Handle(UpdateSessionCompleteDelegate);

if (!sessionInterface->UpdateSession(NAME_GameSession, *updatedSessionSettings))
{
sessionInterface->ClearOnUpdateSessionCompleteDelegate_Handle(UpdateSessionCompleteDelegateHandle);

OnUpdateSessionCompleteEvent.Broadcast(false);
}
else
{
LastSessionSettings = updatedSessionSettings;
}
}
void OnUpdateSessionCompleted(FName SessionName, bool Successful);
void UCSSessionSubsystem::OnUpdateSessionCompleted(FName SessionName, bool Successful)
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (sessionInterface)
{
sessionInterface->ClearOnUpdateSessionCompleteDelegate_Handle(UpdateSessionCompleteDelegateHandle);
}

OnUpdateSessionCompleteEvent.Broadcast(Successful);
}

Start Session

void StartSession();
void UCSSessionSubsystem::StartSession()
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (!sessionInterface.IsValid())
{
OnStartSessionCompleteEvent.Broadcast(false);
return;
}

StartSessionCompleteDelegateHandle =
sessionInterface->AddOnStartSessionCompleteDelegate_Handle(StartSessionCompleteDelegate);

if (!sessionInterface->StartSession(NAME_GameSession))
{
sessionInterface->ClearOnStartSessionCompleteDelegate_Handle(StartSessionCompleteDelegateHandle);

OnStartSessionCompleteEvent.Broadcast(false);
}
}
void OnStartSessionCompleted(FName SessionName, bool Successful);
void UCSSessionSubsystem::OnStartSessionCompleted(FName SessionName, bool Successful)
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (sessionInterface)
{
sessionInterface->ClearOnStartSessionCompleteDelegate_Handle(StartSessionCompleteDelegateHandle);
}

OnStartSessionCompleteEvent.Broadcast(Successful);
}

Destroy Session

void DestroySession();
void UCSSessionSubsystem::DestroySession()
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (!sessionInterface.IsValid())
{
OnDestroySessionCompleteEvent.Broadcast(false);
return;
}

DestroySessionCompleteDelegateHandle =
sessionInterface->AddOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteDelegate);

if (!sessionInterface->DestroySession(NAME_GameSession))
{
sessionInterface->ClearOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteDelegateHandle);

OnDestroySessionCompleteEvent.Broadcast(false);
}
}
void OnDestroySessionCompleted(FName SessionName, bool Successful);
void UCSSessionSubsystem::OnDestroySessionCompleted(FName SessionName, bool Successful)
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (sessionInterface)
{
sessionInterface->ClearOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteDelegateHandle);
}

OnDestroySessionCompleteEvent.Broadcast(Successful);
}

Find Sessions

void FindSessions(int32 MaxSearchResults, bool IsLANQuery);
void UCSSessionSubsystem::FindSessions(int32 MaxSearchResults, bool IsLANQuery)
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (!sessionInterface.IsValid())
{
OnFindSessionsCompleteEvent.Broadcast(TArray<FOnlineSessionSearchResult>(), false);
return;
}

FindSessionsCompleteDelegateHandle =
sessionInterface->AddOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegate);

LastSessionSearch = MakeShareable(new FOnlineSessionSearch());
LastSessionSearch->MaxSearchResults = MaxSearchResults;
LastSessionSearch->bIsLanQuery = IsLANQuery;

LastSessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);

const ULocalPlayer* localPlayer = GetWorld()->GetFirstLocalPlayerFromController();
if (!sessionInterface->FindSessions(*localPlayer->GetPreferredUniqueNetId(), LastSessionSearch.ToSharedRef()))
{
sessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegateHandle);

OnFindSessionsCompleteEvent.Broadcast(TArray<FOnlineSessionSearchResult>(), false);
}
}
void OnFindSessionsCompleted(bool Successful);
void UCSSessionSubsystem::OnFindSessionsCompleted(bool Successful)
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (sessionInterface)
{
sessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegateHandle);
}

if (LastSessionSearch->SearchResults.Num() <= 0)
{
OnFindSessionsCompleteEvent.Broadcast(TArray<FOnlineSessionSearchResult>(), Successful);
return;
}

OnFindSessionsCompleteEvent.Broadcast(LastSessionSearch->SearchResults, Successful);
}

Join Session

void JoinGameSession(const FOnlineSessionSearchResult& SessionResult);
void UCSSessionSubsystem::JoinGameSession(const FOnlineSessionSearchResult& SessionResult)
{

const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (!sessionInterface.IsValid())
{
OnJoinGameSessionCompleteEvent.Broadcast(EOnJoinSessionCompleteResult::UnknownError);
return;
}

JoinSessionCompleteDelegateHandle =
sessionInterface->AddOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegate);

const ULocalPlayer* localPlayer = GetWorld()->GetFirstLocalPlayerFromController();
if (!sessionInterface->JoinSession(*localPlayer->GetPreferredUniqueNetId(), NAME_GameSession, SessionResult))
{
sessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle);

OnJoinGameSessionCompleteEvent.Broadcast(EOnJoinSessionCompleteResult::UnknownError);
}
}
void OnJoinSessionCompleted(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
bool TryTravelToCurrentSession();
void UCSSessionSubsystem::OnJoinSessionCompleted(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (sessionInterface)
{
sessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle);
}

OnJoinGameSessionCompleteEvent.Broadcast(Result);
}

bool UCSSessionSubsystem::TryTravelToCurrentSession()
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (!sessionInterface.IsValid())
{
return false;
}

FString connectString;
if (!sessionInterface->GetResolvedConnectString(NAME_GameSession, connectString))
{
return false;
}

APlayerController* playerController = GetWorld()->GetFirstPlayerController();
playerController->ClientTravel(connectString, TRAVEL_Absolute);
return true;
}

Be aware that EOnJoinSessionCompleteResult cannot be exposed to Blueprints, so you cannot make your own Callback Delegate BlueprintAssignable unless you make your own UENUM and convert back and forth between Unreals type and yours.

Same goes for the JoinSession function itself, as the FOnlineSessionSearchResult struct can’t be exposed.

If you want to make the function BlueprintCallable, you’ll have to wrap the struct into your own USTRUCT as stated before. In addition to the default Session code, you’ll also find a new function called TryTravelToCurrentSession in the above gist, which is for actually joining the Server behind that Session.

End Session

void EndSession();
void UCSSessionSubsystem::EndSession()
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());
if (!sessionInterface.IsValid())
{
OnEndSessionCompleteEvent.Broadcast(false);
return;
}

EndSessionCompleteDelegateHandle =
sessionInterface->AddOnEndSessionCompleteDelegate_Handle(EndSessionCompleteDelegate);

if (!sessionInterface->EndSession(NAME_GameSession))
{
sessionInterface->ClearOnEndSessionCompleteDelegate_Handle(EndSessionCompleteDelegateHandle);

OnEndSessionCompleteEvent.Broadcast(false);
}
}
void OnEndSessionCompleted(FName SessionName, bool Successful);
void UCSSessionSubsystem::OnEndSessionCompleted(FName SessionName, bool Successful)
{
const IOnlineSessionPtr sessionInterface = Online::GetSessionInterface(GetWorld());;
if (sessionInterface)
{
sessionInterface->ClearOnEndSessionCompleteDelegate_Handle(EndSessionCompleteDelegateHandle);
}

OnEndSessionCompleteEvent.Broadcast(Successful);
}

Learning

It's suggested to go through ShooterGame by Epic Games, lots of OnlineSubsystemNull code snippets.