[UE4]Networking in Basic - Sessions
Keywords: UE4, Networking
The previous article explained how to connect Dedicated Server via IP address:
[UE4]Networking in Basic - Simple Replication Example
https://dawnarc.com/2017/02/ue4networking-in-basic-simple-replication-example/
Current article explained how to connect Server via Sessions(without IP).
Engine API
OnlineSubsystem
BP Node CreateSession
in native:
Source Path:
\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\CreateSessionCallbackProxy.cpp
Native Function:
// Creates a session with the default online subsystem
UCreateSessionCallbackProxy* UCreateSessionCallbackProxy::CreateSession(UObject* WorldContextObject, class APlayerController* PlayerController, int32 PublicConnections, bool bUseLAN)
{
UCreateSessionCallbackProxy* Proxy = NewObject<UCreateSessionCallbackProxy>();
Proxy->PlayerControllerWeakPtr = PlayerController;
Proxy->NumPublicConnections = PublicConnections;
Proxy->bUseLAN = bUseLAN;
Proxy->WorldContextObject = WorldContextObject;
return Proxy;
}
BP Node FindSessions
in native:
Source Path:
\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\FindSessionsCallbackProxy.cpp
Native Function:
// Searches for advertised sessions with the default online subsystem
UFindSessionsCallbackProxy* UFindSessionsCallbackProxy::FindSessions(UObject* WorldContextObject, class APlayerController* PlayerController, int MaxResults, bool bUseLAN)
{
UFindSessionsCallbackProxy* Proxy = NewObject<UFindSessionsCallbackProxy>();
Proxy->PlayerControllerWeakPtr = PlayerController;
Proxy->bUseLAN = bUseLAN;
Proxy->MaxResults = MaxResults;
Proxy->WorldContextObject = WorldContextObject;
return Proxy;
}
BP Node JoinSession
in native:
Source Path:
\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\JoinSessionCallbackProxy.cpp
Native Function:
UJoinSessionCallbackProxy* UJoinSessionCallbackProxy::JoinSession(UObject* WorldContextObject, class APlayerController* PlayerController, const FBlueprintSessionResult& SearchResult)
{
UJoinSessionCallbackProxy* Proxy = NewObject<UJoinSessionCallbackProxy>();
Proxy->PlayerControllerWeakPtr = PlayerController;
Proxy->OnlineSearchResult = SearchResult.OnlineResult;
Proxy->WorldContextObject = WorldContextObject;
return Proxy;
}
Other BP Nodes of Session:
\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\ConnectionCallbackProxy.cpp
\Engine\Plugins\Online\OnlineSubsystemUtils\Source\OnlineSubsystemUtils\Private\DestroySessionCallbackProxy.cpp
Usage
How to create session using C++
// Fill out your copyright notice in the Description page of Project Settings.
#include "UNWGameInstance.h"
#include "EngineGlobals.h"
#include "Engine/Engine.h"
#include "Kismet/GameplayStatics.h"
UNWGameInstance::UNWGameInstance(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
/** Bind function for CREATING a Session */
OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnCreateSessionComplete);
OnStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnStartOnlineGameComplete);
/** Bind function for FINDING a Session */
OnFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnFindSessionsComplete);
/** Bind function for JOINING a Session */
OnJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnJoinSessionComplete);
/** Bind function for DESTROYING a Session */
OnDestroySessionCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &UNWGameInstance::OnDestroySessionComplete);
}
bool UNWGameInstance::HostSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers)
{
// Get the Online Subsystem to work with
IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Called Host Session")));
if (OnlineSub)
{
// Get the Session Interface, so we can call the "CreateSession" function on it
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid() && UserId.IsValid())
{
/*
Fill in all the Session Settings that we want to use.
There are more with SessionSettings.Set(...);
For example the Map or the GameMode/Type.
*/
SessionSettings = MakeShareable(new FOnlineSessionSettings());
SessionSettings->bIsLANMatch = bIsLAN;
SessionSettings->bUsesPresence = bIsPresence;
SessionSettings->NumPublicConnections = MaxNumPlayers;
SessionSettings->NumPrivateConnections = 0;
SessionSettings->bAllowInvites = true;
SessionSettings->bAllowJoinInProgress = true;
SessionSettings->bShouldAdvertise = true;
SessionSettings->bAllowJoinViaPresence = true;
SessionSettings->bAllowJoinViaPresenceFriendsOnly = false;
SessionSettings->Set(SETTING_MAPNAME, FString("Red Hot"), EOnlineDataAdvertisementType::ViaOnlineService);
// Set the delegate to the Handle of the SessionInterface
OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
// Our delegate should get called when this is complete (doesn't need to be successful!)
return Sessions->CreateSession(*UserId, SessionName, *SessionSettings);
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, TEXT("No OnlineSubsytem found!"));
}
return false;
}
void UNWGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnCreateSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));
// Get the OnlineSubsystem so we can get the Session Interface
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
// Get the Session Interface to call the StartSession function
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
// Clear the SessionComplete delegate handle, since we finished this call
Sessions->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
if (bWasSuccessful)
{
// Set the StartSession delegate handle
OnStartSessionCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);
// Our StartSessionComplete delegate should get called after this
Sessions->StartSession(SessionName);
}
}
}
}
void UNWGameInstance::OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnStartSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));
// Get the Online Subsystem so we can get the Session Interface
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
// Get the Session Interface to clear the Delegate
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
// Clear the delegate, since we are done with this call
Sessions->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
}
}
// If the start was successful, we can open a NewMap if we want. Make sure to use "listen" as a parameter!
if (bWasSuccessful)
{
UGameplayStatics::OpenLevel(GetWorld(), "StageLevel", true, "listen");
}
}
void UNWGameInstance::FindSessions(TSharedPtr<const FUniqueNetId> UserId, bool bIsLAN, bool bIsPresence)
{
UE_LOG(LogTemp, Log, TEXT("Finding sessions"));
// Get the OnlineSubsystem we want to work with
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
// Get the SessionInterface from our OnlineSubsystem
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid() && UserId.IsValid())
{
/*
Fill in all the SearchSettings, like if we are searching for a LAN game and how many results we want to have!
*/
SessionSearch = MakeShareable(new FOnlineSessionSearch());
SessionSearch->bIsLanQuery = bIsLAN;
SessionSearch->MaxSearchResults = 20;
SessionSearch->PingBucketSize = 1000;
// We only want to set this Query Setting if "bIsPresence" is true
if (bIsPresence)
{
SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, bIsPresence, EOnlineComparisonOp::Equals);
}
TSharedRef<FOnlineSessionSearch> SearchSettingsRef = SessionSearch.ToSharedRef();
// Set the Delegate to the Delegate Handle of the FindSession function
OnFindSessionsCompleteDelegateHandle = Sessions->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegate);
// Finally call the SessionInterface function. The Delegate gets called once this is finished
Sessions->FindSessions(*UserId, SearchSettingsRef);
}
}
else
{
// If something goes wrong, just call the Delegate Function directly with "false".
OnFindSessionsComplete(false);
}
}
void UNWGameInstance::OnFindSessionsComplete(bool bWasSuccessful)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OFindSessionsComplete bSuccess: %d"), bWasSuccessful));
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Blue, FString::Printf(TEXT("OFindSessionsComplete bSuccess: %d"), bWasSuccessful));
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, FString::Printf(TEXT("OFindSessionsComplete bSuccess: %d"), bWasSuccessful));
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Ping Bucket size: %d"), SessionSearch->PingBucketSize));
// Get OnlineSubsystem we want to work with
IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
// Get SessionInterface of the OnlineSubsystem
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
// Clear the Delegate handle, since we finished this call
Sessions->ClearOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegateHandle);
// Just debugging the Number of Search results. Can be displayed in UMG or something later on
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Num Search Results: %d"), SessionSearch->SearchResults.Num()));
// If we have found at least 1 session, we just going to debug them. You could add them to a list of UMG Widgets, like it is done in the BP version!
if (SessionSearch->SearchResults.Num() > 0)
{
// "SessionSearch->SearchResults" is an Array that contains all the information. You can access the Session in this and get a lot of information.
// This can be customized later on with your own classes to add more information that can be set and displayed
for (int32 SearchIdx = 0; SearchIdx < SessionSearch->SearchResults.Num(); SearchIdx++)
{
// OwningUserName is just the SessionName for now. I guess you can create your own Host Settings class and GameSession Class and add a proper GameServer Name here.
// This is something you can't do in Blueprint for example!
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Session Number: %d | Sessionname: %s "), SearchIdx + 1, *(SessionSearch->SearchResults[SearchIdx].Session.OwningUserName)));
}
}
}
}
}
bool UNWGameInstance::JoinSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, const FOnlineSessionSearchResult& SearchResult)
{
// Return bool
bool bSuccessful = false;
// Get OnlineSubsystem we want to work with
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
// Get SessionInterface from the OnlineSubsystem
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid() && UserId.IsValid())
{
// Set the Handle again
OnJoinSessionCompleteDelegateHandle = Sessions->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);
// Call the "JoinSession" Function with the passed "SearchResult". The "SessionSearch->SearchResults" can be used to get such a
// "FOnlineSessionSearchResult" and pass it. Pretty straight forward!
bSuccessful = Sessions->JoinSession(*UserId, SessionName, SearchResult);
}
}
return bSuccessful;
}
void UNWGameInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnJoinSessionComplete %s, %d"), *SessionName.ToString(), static_cast<int32>(Result)));
// Get the OnlineSubsystem we want to work with
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
// Get SessionInterface from the OnlineSubsystem
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
// Clear the Delegate again
Sessions->ClearOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegateHandle);
// Get the first local PlayerController, so we can call "ClientTravel" to get to the Server Map
// This is something the Blueprint Node "Join Session" does automatically!
APlayerController * const PlayerController = GetFirstLocalPlayerController();
// We need a FString to use ClientTravel and we can let the SessionInterface contruct such a
// String for us by giving him the SessionName and an empty String. We want to do this, because
// Every OnlineSubsystem uses different TravelURLs
FString TravelURL;
if (PlayerController && Sessions->GetResolvedConnectString(SessionName, TravelURL))
{
// Finally call the ClienTravel. If you want, you could print the TravelURL to see
// how it really looks like
PlayerController->ClientTravel(TravelURL, ETravelType::TRAVEL_Absolute);
}
}
}
}
void UNWGameInstance::OnDestroySessionComplete(FName SessionName, bool bWasSuccessful)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnDestroySessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));
// Get the OnlineSubsystem we want to work with
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
// Get the SessionInterface from the OnlineSubsystem
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
// Clear the Delegate
Sessions->ClearOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegateHandle);
// If it was successful, we just load another level (could be a MainMenu!)
if (bWasSuccessful)
{
UGameplayStatics::OpenLevel(GetWorld(), "MenuLevel", true);
}
}
}
}
void UNWGameInstance::StartOnlineGame()
{
UE_LOG(LogTemp, Log, TEXT("Inside Start Online Game"));
// Creating a local player where we can get the UserID from
ULocalPlayer* const Player = GetFirstGamePlayer();
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("Called StartOnlineGame")));
// Call our custom HostSession function. GameSessionName is a GameInstance variable
HostSession(Player->GetPreferredUniqueNetId(), "RedHot", true, true, 4);
}
void UNWGameInstance::FindOnlineGames()
{
ULocalPlayer* const Player = GetFirstGamePlayer();
FindSessions(Player->GetPreferredUniqueNetId(), true, true);
}
void UNWGameInstance::JoinOnlineGame()
{
ULocalPlayer* const Player = GetFirstGamePlayer();
// Just a SearchResult where we can save the one we want to use, for the case we find more than one!
FOnlineSessionSearchResult SearchResult;
// If the Array is not empty, we can go through it
if (SessionSearch->SearchResults.Num() > 0)
{
for (int32 i = 0; i < SessionSearch->SearchResults.Num(); i++)
{
// To avoid something crazy, we filter sessions from ourself
if (SessionSearch->SearchResults[i].Session.OwningUserId != Player->GetPreferredUniqueNetId())
{
SearchResult = SessionSearch->SearchResults[i];
// Once we found sounce a Session that is not ours, just join it. Instead of using a for loop, you could
// use a widget where you click on and have a reference for the GameSession it represents which you can use
// here
JoinSession(Player->GetPreferredUniqueNetId(), "RedHot", SearchResult);
break;
}
}
}
else {
UE_LOG(LogTemp, Log, TEXT("No session to join"));
}
}
void UNWGameInstance::DestroySessionAndLeaveGame()
{
IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
Sessions->AddOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegate);
Sessions->DestroySession("RedHot");
}
}
}
You’d better test it in Launch game
: Right click .uproject -> Launch game.
How to set customized GameSession Class
Reference
https://answers.unrealengine.com/questions/260999/creating-a-session-on-dedicated-server-launch.html
Docs Reference
Official Docs
Online Session Nodes
https://docs.unrealengine.com/en-US/Engine/Blueprints/UserGuide/OnlineNodes/index.html
How To Use Sessions In C++
https://wiki.unrealengine.com/How_To_Use_Sessions_In_C%2B%2B
YouTube Tutorials
Unreal Engine 4 | Server browser | Host & Join Session | SESSION SERIES
https://www.youtube.com/watch?v=NW4QHiK6YbQ
Steam Multiplayer - Advanced Steam Session - Unreal Engine
https://www.youtube.com/watch?v=ke3kmK6bvT0
Blueprint Multiplayer
https://www.youtube.com/watch?v=abmzWUWxy1U&list=PLZlv_N0_O1gYqSlbGQVKsRg6fpxWndZqZ
Blueprint Multiplayer: Game Instance | 03 | v4.11 Tutorial Series | Unreal Engine
https://www.youtube.com/watch?v=78XJYBfWXAA
[ue4] Setup Advanced Session - Steam Multiplayer
https://www.youtube.com/watch?v=rWs6SyyVpTE&list=PL3TrrCsmmxllnBvhucQ4c683a2VltXBx6
[UE4 - BP Tutorial] Multiplayer Session
https://www.youtube.com/watch?v=so0-0dafVsE&list=PLLiI_CRDNPL-FUUPcViHe6Ypabo1ghXh3
请原谅我不敢祝愿每一位毕业生都成功、都幸福;因为历史不幸地记载着:有人的成功代价是丧失良知;有人的幸福代价是损害他人。 ----饶毅教授2015北大毕业典礼演讲