[BLOG] C++ Session Create/Find/Join/Destroy

Hey there,

if you had trouble getting Sessions to work in C++ and the ShooterGame only made it worse, I have a solution for you! I took my time and learned how the ShooterGame and the Session BP Nodes work and build the most basic and working Session System for you.

Here is the link to my Blog Post:

Blog Post: UE4 Multiplayer Sessions in C++

Tell me if there is something wrong, or easier to make. Or if i’m missing something!

If you have questions: PLEASE POST THEM!

Kind regards

Cedric ‘eXi’

1 Like

Good start. I feel that it would be easier if we talked about it in a more bare bones way so that it’s clearly understood what exactly is required vs optional.

Dependencies (in [Project].build.cs):
Public: “OnlineSubsystem”, “OnlineSubsystemUtils”, “OnlineSubsystemNull”.
Dynamic: “OnlineSubsystemNull”.
Can substitute null with Steam if that’s what you want.

In DefaultEngine.ini:


[OnlineSubsystem]
DefaultPlatformService=Null

Can substitute null with Steam if that’s what you want.

Header include:
#include “OnlineSubsystemUtils.h”
Put this wherever you deal with the Online Subsystem directly.

For creating a game this is what I did for creating sessions:

.h



FOnCreateSessionCompleteDelegate CreateSessionCompleteD;
FDelegateHandle CreateSessionCompleteDH;
void CreateSessionComplete(FName SessionName, bool bWasSuccessful);
FOnlineSessionSettings SessionSettings;


.cpp



void ANetworkTest_GameSession::BeginPlay()
{
	IOnlineSubsystem* OSInst = IOnlineSubsystem::Get();
	IOnlineSessionPtr SessionInst = OSInst->GetSessionInterface();

	CreateSessionCompleteD = FOnCreateSessionCompleteDelegate::CreateUObject(this, &ANetworkTest_GameSession::CreateSessionComplete);
	CreateSessionCompleteDH = SessionInst->AddOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteD);
}

void ANetworkTest_GameSession::Host()
{
	SessionSettings.bIsLANMatch = true;
	SessionSettings.bAllowJoinInProgress = true;
	SessionSettings.bUsesPresence = true;   // What is presence?
	SessionSettings.bShouldAdvertise = true;

	IOnlineSubsystem* OSInst = IOnlineSubsystem::Get();
	IOnlineSessionPtr SessionInst = OSInst->GetSessionInterface();

	// If session isn't created then one might already exist so try to destroy it.	
	if (SessionInst->CreateSession(0, "cheese", SessionSettings))
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, "Yes: Create");
	else
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, "No: Create");

		if (SessionInst->DestroySession("cheese"))
			GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, "Yes: Delete");
		else
			GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, "No: Delete");
	}
}

void ANetworkTest_GameSession::CreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
	if (bWasSuccessful)
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, "Pass: Create");
	else
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, "Fail: Create");

	UGameplayStatics::OpenLevel(GetWorld(), "Sandbox", true, "listen");
}


I’ve tried to keep it as simple as possible so I know what’s required. My Host function for example takes no parameters and this makes it clear that it’s not directly an important function.

Edit:

I’ve asked before, but hopefully someone who knows will see it here:

I’m hoping someone could tell me why setting a delegate wasn’t made as simple as MySessionPtr->CreateSession(0, “SessionName”, this, &MyClass::CallThisWhenCreated, &MyClass::CallThisWhenFailed). The advantage here is that I don’t need to remember 4 or so extra steps for Unreal’s custom delegate system.

Also the way we set the game session is quite odd. Why isn’t it as simple as setting a variable for like DefaultPawnClass?

1 Like

That’s pretty similar, if not the same to how i’ve done it. I only added the “Start Session” part.
If someone knows for what we need the “StartSession” thing, then i would be happy (:

@MaxL: Would be nice if you could keep general delegate questions away from here. I want to focus on the session system (:

Added “Finding Sessions” and also added comments to “creating a session”. And i changed some code of the “Creating a Sessions” Parts.
This is all not final, just gathering. The final thing will be in the wiki and will contain way more information and help. Please keep this in mind!

Question:



SessionSearch = MakeShareable(new FOnlineSessionSearch());


this line was giving me a lot of trouble yesterday, looks like Unreal did not like the way that I declared it.
I used:



TSharedPtr<class FOnlineSessionSearch> SessionSearch;


I don’t remember exactly what the error was, but looks like Unreal wasn’t a big fan of it.

1 Like

Hey there (:

i took this out of the ShooterGame. TSharedPtr have some extra things to them. You are better of reading about it here:

I’m not that good in these extra pointers from epic, but the “MakeShareable” is the way to initialize the a new “object” for that TSharedPtr i guess (:

Thanks, I’ll look deeper into it. But thank you a ton for coming up with this amazing tutorial! Feels like my life will be much easier now :slight_smile:

Its not ready yet though. There is missing a ton and it is also not yet a tutorial xD
Just writing down what i learn. Will either continue this night or tomorrow.

I’ve looked trough it, looks like you guys might have forgotten to include few things:

First:
In MyGame.Build.cs you have to include:



 PublicDependencyModuleNames.AddRange(
			new string] {
				"Core",
				"CoreUObject",
				"Engine",
				"OnlineSubsystem",
				"OnlineSubsystemUtils",
			}
		);


Make sure to regenerate project files after adding this

To essentially include OnlineSubststem to project.

Second
Thanks to MaxL, I know that.

Overload GetGameSessionClass() function in AGameMode class for game mode to actually use your game session (If you’re extending AGameSession class):



TSubclassOf<AGameSession> MyGameGameMode::GetGameSessionClass() const
{
	return MyGameGameSession::StaticClass();
}


Please correct me if I’m wrong.

No you are correct, but you need to read my text correctly:

As i stated, the includes etc are not listed and i will add them with the wiki entry. They are not important for me at this point, since they are pretty straight forward.
This thread is only for the sessions “nodes”.

The second is pretty cool to know. Thanks for sharing that. This will fit into the page where we gather information about extending the system. Thanks!

eXi, my bad :slight_smile: I should have read it properly.

So, let me say it in a picture, combined with 2 “words”:

WUB WUB!

Will update the main post with the code to join a session.

Then only destroying is left!

EDIT: Updated it. Will make a break and do the destroying later.

Hi guys,

I’m having trouble getting both creating and finding sessions to work in C++ at the same time.
Whenever I’m doing one of that via blueprint and the other one via C++, it work’s just fine, so for example creating session via blueprint and finding via c++ works perfect, as well as creating via c++ and finding via blueprint.

This is a mystery for me, because this tells me that my c++ functions are actually working just fine.

This is what I did:

  1. Creating a UMyGameInstance and setting that as default game instance in the project settings.
  2. UMyGameInstance.h as follows:

public:
	UMyGameInstance(const FObjectInitializer& ObjectInitializer);

	// Test button click
	UFUNCTION(BlueprintCallable, Category = "C++ Functions")
	void TestButton();

	// Test button click
	UFUNCTION(BlueprintCallable, Category = "C++ Functions")
	void TestButtonHost();

	// Test button delegates
	void TestButtonDelegate1(bool bWasSuccessful);
	void TestButtonHostDelegate1(FName SessionName, bool bWasSuccessful);

	FOnFindSessionsCompleteDelegate OnFindSessionsCompleteDelegate;
	FDelegateHandle OnFindSessionsCompleteDelegateHandle;

	FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate;
	FDelegateHandle OnCreateSessionCompleteDelegateHandle;

	TSharedPtr<class FOnlineSessionSearch> SearchSettings;
	TSharedPtr<class FOnlineSessionSettings> HostSettings;

  1. UMyGameInstance.cpp as follows:

UMyGameInstance::UMyGameInstance(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// create Online session delegates:
	OnFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::CreateUObject(this, &UMyGameInstance::TestButtonDelegate1);
	OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &UMyGameInstance::TestButtonHostDelegate1);
}


void UMyGameInstance::TestButton()
{
	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub)
	{
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
		if (Sessions.IsValid())
		{
			SearchSettings = MakeShareable(new FOnlineSessionSearch());

			SearchSettings->bIsLanQuery = true;
			SearchSettings->MaxSearchResults = 10;

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

			// Set search delegate:
			OnFindSessionsCompleteDelegateHandle = Sessions->AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegate);

			TSharedPtr<class FUniqueNetId> NetId = GetWorld()->GetFirstPlayerController()->PlayerState->UniqueId.GetUniqueNetId();

			// start searching for sessions:
			Sessions->FindSessions(*NetId, SearchSettings.ToSharedRef());
		}
	}
}

void UMyGameInstance::TestButtonHost()
{
	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub)
	{
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
		if (Sessions.IsValid())
		{
			HostSettings = MakeShareable(new FOnlineSessionSettings());

			HostSettings->NumPublicConnections = 2;
			HostSettings->bShouldAdvertise = true;
			HostSettings->bAllowJoinInProgress = true;
			HostSettings->bIsLANMatch = true;
			HostSettings->bUsesPresence = true;
			HostSettings->bAllowJoinViaPresence = true;

			// Set complete delegate:
			OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);

			// start creating a session:
			FName SessionName = FName("Test");
			bool Status = Sessions->CreateSession(0, SessionName, *HostSettings);

			if (Status) GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("UMyGameInstance::TestButtonHost Create OK"));
		}
	}
}

void UMyGameInstance::TestButtonDelegate1(bool bWasSuccessful)
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("UMyGameInstance::TestButtonDelegate1"));

	IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub)
	{
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
		if (Sessions.IsValid())
		{

			Sessions->ClearOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegateHandle);

			GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("Num Search Results: " + FString::FromInt(SearchSettings->SearchResults.Num())));
		}
	}
}

void UMyGameInstance::TestButtonHostDelegate1(FName SessionName, bool bWasSuccessful)
{
	if (bWasSuccessful)
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("UMyGameInstance::TestButtonHostDelegate1 Name: " + SessionName.ToString()));
	else
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("UMyGameInstance::TestButtonHostDelegate1 ERROR"));

	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub)
	{
		IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
		Sessions->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);

		//StartCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(StartCompleteDelegate);
		Sessions->StartSession(SessionName); // for testing
	}
}

  1. Added two button which just calls UMyGameInstance::TestButton() and UMyGameInstance::TestButtonHost()
  2. in my project build cs:

        // Uncomment if you are using online features
	PrivateDependencyModuleNames.Add("OnlineSubsystem");

        // Adding Subsystem utils:
        PublicDependencyModuleNames.AddRange(new string] { "OnlineSubsystem", "OnlineSubsystemUtils" });

        // Adding Subsystem service implementation:
        DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam");
        DynamicallyLoadedModuleNames.AddRange(new string] { "OnlineSubsystemNull" });

  1. In my DefaultEngine.ini:


[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")

[OnlineSubsystem]
;DefaultPlatformService=Null
DefaultPlatformService=Steam
PollingIntervalInMs=20

[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480
bVACEnabled=0

[/Script/OnlineSubsystemSteam.SteamNetDriver]
NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"

[OnlineSubsystemNull]


And that’s it - Both creating and finding sessions in C++ works, but not at the same time. But when i either create a session or search for sessions via blueprint, the respective C++ counterpart works perfect.
For testing i simply use two PIE windows: one for hosting, one for joining.

eXi, can you maybe send me your project so I can check that out? Hopefully I find my problem here and can contribute to the documentation.

Greetings!

Yes sure, but i would like to finish this first and tidy it up, so i can directly upload it for everyone. Do you have some time to spent on other things
until i finish this here?

Questions for so far:
First:
On OnStartOnlineGameComplete() delegate you use GetWorld()->ServerTravel(“SomeMapURL”) which sends Hosting Client to desired map and kicks off Game Session.
For OnJoinSessionComplete() you use PlayerController->ClientTravel(TravelURL, ETravelType::TRAVEL_Absolute); which sends client to connect to the Hosting Client.

Hosting Client is a Server at that time, or I’m getting that wrong?
I guess my main question is: Would that logic work with Dedicated Server?

Second
In ShooterGame example they use something like this



ABWGameSession* UBWGameInstance::GetGameSession() const
{
	UWorld* const World = GetWorld();
	if (World)
	{
		AGameMode* const Game = World->GetAuthGameMode();
		if (Game)
		{
			return Cast<ABWGameSession>(Game->GameSession);
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Game Instance - GetGameSession - Game* is NULL"));
			return nullptr;
		}
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("Game Instance - GetGameSession - World* is NULL"));
		return nullptr;
	}
	return nullptr;
}


to get the reference of current game session(if i’m getting it right) before they host the game. And you do not use it.
My Question is: Do you know why they use it? What is the benefit?

Hey, i don’t want to start with something different, this way i can focus on a single mind only. But i’ll wait, no problem and thanks!

First:

Yeah, i already changed the first one to “OpenLevel” that is also used this way in the Blueprint Version. ServerTravel is for moving the clients with you.
This would also be possible and is working, but somewhat wrong at this state.
That was my bad and i forgot to update it in the Post above.

The Dedicated Server Question: I have no idea. I guess this will be an additional part for this. ):

Second:

I have no idea. I guess that is just a shortcut to get it from the GameMode. This function only works for the Server or before you host/join. While playing,
the clients can’t get this, because the GameMode class has no reference on clients.

I don’t get the session with this, but with the SessionInterface. They are a bit different. You may want to read this up here under “Session Interface”:

https://docs.unrealengine.com/latest/INT/Programming/Online/Interfaces/Session/index.html#sessioninterface

I will try to get this ready this night or tomorrow. :X Need to rest a bit, since this is my second day off today. Still a bit down from the last few weeks.

They use GetAuthGameMode to get a runtime pointer to their own AGameSession derived class. This class is used to manage the online interface I believe so they’ve put the code for it there, but it’s not strictly required.

It’s similar to APlayerController in the sense that you can use the controller to manage the player, but if you weren’t bothered you could just use the Pawn directly to do player input management.

Just added the “last” part: Destroying the Session.

So we are good to go to comment everything and pack it onto the Wiki (:

Nice one! :slight_smile:

If I’m right you forgot the part where you are using OnDestroySessionCompleteDelegateHandle.
I would expect something like OnDestroySessionCompleteDelegateHandle= Sessions->AddOnDestroySessionCompleteDelegate_Handle(MyDelegateFunction);

Greetings!