My project is going to make a multi-play game using the Listen server and release it on Steam.
I’d like to right-click my friend on Steam’s overlay and invite him to join the session using the Invite the game, Join the game feature.
I want to make the player play without port forwarding or port-related settings.
If possible, I would like to implement without using external plug-ins such as Advanced Session Plugin.
However, I used an external plug-in for most YouTube videos and posts.
As a result of looking into it, I found out the information that it uses the steam relay function.
I set it up in relation to it, but there is no response when I receive the invitation and accept it.
The result value of the JoinSession in the OnSessionInviteAccepted function is outputted as false and does not appear to be participating in the session.
Session creation is also normal and the session owner nickname of InviteResult is also properly imported from the OnSessionInviteAccepted function.
The test was do by packaging the project.
I used two computers.
Q1) Is the SteamworksSDK installation mandatory to implement the game invitation and game participation functionality with Steam Relay and Steam Overlay?
The Epic Games document says
“Using Steam against the precompiled version of the engine should only require copying some of the dynamically linked libraries from Valve’s SDK into the appropriate folders” it says.
Q2) Do I not have to build Unreal Engine full source code?
Has anyone implemented a session invitation using the Game Invite/Game Join feature on Steam overlay?
Below is the example code I wrote.
[DefaultEngine.ini]
;==============Steam Online Subsysyem Setting
[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
[OnlineSubsystem]
DefaultPlatformService=Steam
[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480
bUseSteamNetworking=true
bInitServerOnClient=true
bAllowP2PPacketRelay=true ;
NetConnectionType=SteamSockets ; SteamRelay
;GameServerQueryPort=27015
[/Script/OnlineSubsystemSteam.SteamNetDriver]
NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"
;==============Steam Online Subsysyem Setting
[SessionGameInstance.h]
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "SessionGameInstance.generated.h"
UCLASS()
class STEAMCONNECTTEST_API USessionGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
virtual void Init() override;
public:
UFUNCTION(BlueprintCallable)
void CreateGameSession();
public:
//======================Online Subsystem
TSharedPtr<class IOnlineSession, ESPMode::ThreadSafe> OnlineSessionInterface;
private:
FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate;
FOnSessionUserInviteAcceptedDelegate OnSessionInviteAcceptedDelegate;
FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate;
public:
void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);
void OnSessionInviteAccepted(const bool bWasSuccessful, int32 ControllerId, TSharedPtr<const FUniqueNetId> UserId, const FOnlineSessionSearchResult& InviteResult);
void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
//======================Online Subsystem
};
[SessionGameInstance.cpp]
// Fill out your copyright notice in the Description page of Project Settings.
#include "SessionGameInstance.h"
#include "GameFramework/GameSession.h"
#include "OnlineSubsystem.h"
#include "OnlineSessionSettings.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/Engine.h"
void USessionGameInstance::Init()
{
IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();
if (OnlineSubsystem == nullptr)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red,
FString::Printf(TEXT("Init() - Online Sub System Is Not Valid")));
}
return;
}
OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();
if (OnlineSessionInterface.IsValid())
{
// Session Invite Accept Binding
OnSessionInviteAcceptedDelegate = FOnSessionUserInviteAcceptedDelegate::CreateUObject(this, &USessionGameInstance::OnSessionInviteAccepted);
OnlineSessionInterface->AddOnSessionUserInviteAcceptedDelegate_Handle(OnSessionInviteAcceptedDelegate);
// Session Join Binding
OnJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &USessionGameInstance::OnJoinSessionComplete);
OnlineSessionInterface->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);
}
else
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red,
FString::Printf(TEXT("Init() - Session Interface Is Not Valid")));
}
return;
}
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red,
FString::Printf(TEXT("Subsystem %s"), *OnlineSubsystem->GetSubsystemName().ToString()));
}
CreateGameSession();
}
void USessionGameInstance::CreateGameSession()
{
if (!OnlineSessionInterface.IsValid())
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage( -1, 15.f, FColor::Red,
FString::Printf(TEXT("CreateGameSession() - SessionInterface Is Not Valid")));
}
return;
}
FNamedOnlineSession* ExistingSession = OnlineSessionInterface->GetNamedSession(NAME_GameSession);
if (ExistingSession)
OnlineSessionInterface->DestroySession(NAME_GameSession);
// Session Create Binding
OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &USessionGameInstance::OnCreateSessionComplete);
OnlineSessionInterface->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
// Session Setting
TSharedPtr<FOnlineSessionSettings> SessionSettings = MakeShareable(new FOnlineSessionSettings());
SessionSettings->bIsLANMatch = false; // Steam 사용 시 false
SessionSettings->NumPublicConnections = 5;
SessionSettings->bAllowJoinInProgress = true;
SessionSettings->bAllowJoinViaPresence = true;
SessionSettings->bShouldAdvertise = true;
SessionSettings->bUsesPresence = true;
SessionSettings->bUseLobbiesIfAvailable = true;
OnlineSessionInterface->CreateSession(0, NAME_GameSession, *SessionSettings);
}
void USessionGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
if (bWasSuccessful)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Blue,
FString::Printf(TEXT("%s Session Create Succese"), *SessionName.ToString()));
}
UWorld* World = GetWorld();
if (World)
{
bool bOpenListen = World->ServerTravel(TEXT("/Game/Maps/ThirdPersonMap?listen"));
if (bOpenListen)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Blue,
FString::Printf(TEXT("Open Listen Server")));
}
}
else
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red,
FString::Printf(TEXT("Cant Open Listen Server")));
}
}
}
}
else
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage( -1, 15.f, FColor::Red,
FString::Printf(TEXT("%s Session Create Failed!!"), *SessionName.ToString()));
}
}
}
void USessionGameInstance::OnSessionInviteAccepted(const bool bWasSuccessful, int32 ControllerId, TSharedPtr<const FUniqueNetId> UserId, const FOnlineSessionSearchResult& InviteResult)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Green,
TEXT("OnSessionInviteAccepted() - Session Accepted Func Call"));
}
if (bWasSuccessful && OnlineSessionInterface.IsValid())
{
if (!InviteResult.IsValid())
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red,
TEXT("OnSessionInviteAccepted() - InviteResult is Not Valid"));
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("ControllerId : %d"), ControllerId));
FString SessionNameString = FName(NAME_GameSession).ToString();
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("NAME_GameSession: %s"), *SessionNameString));
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("OwningUserName: %s"), *InviteResult.Session.OwningUserName));
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("Ping: %d ms"), InviteResult.PingInMs));
const FOnlineSessionSettings& Settings = InviteResult.Session.SessionSettings;
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("NumPublicConnections: %d"), Settings.NumPublicConnections));
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("bIsLANMatch: %s"), Settings.bIsLANMatch ? TEXT("True") : TEXT("False")));
}
bool bJoinStarted = OnlineSessionInterface->JoinSession(ControllerId, NAME_GameSession, InviteResult);
if (bJoinStarted == false)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red,
TEXT("OnSessionInviteAccepted() - Session Join Failed"));
}
return;
}
else
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red,
TEXT("Session Join Succese"));
}
FString ConnectString;
if (OnlineSessionInterface->GetResolvedConnectString(NAME_GameSession, ConnectString))
{
APlayerController* Controller = UGameplayStatics::GetPlayerController(this, 0);
if (Controller)
Controller->ClientTravel(ConnectString, ETravelType::TRAVEL_Absolute);
}
}
}
}
void USessionGameInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Green,
TEXT("OnJoinSessionComplete Func"));
}
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 30.f, FColor::Yellow,
FString::Printf(TEXT("OnJoinSessionComplete() - SessionName: %s, Result: %d"),
*SessionName.ToString(), Result)
);
}
FString ResultStr;
switch (Result)
{
case EOnJoinSessionCompleteResult::Success: ResultStr = "Success"; break;
case EOnJoinSessionCompleteResult::SessionIsFull: ResultStr = "Session Is Full"; break;
case EOnJoinSessionCompleteResult::SessionDoesNotExist: ResultStr = "Session Does Not Exist"; break;
case EOnJoinSessionCompleteResult::CouldNotRetrieveAddress: ResultStr = "Could Not Retrieve Address"; break;
case EOnJoinSessionCompleteResult::AlreadyInSession: ResultStr = "Already In Session"; break;
default: ResultStr = "Unknown"; break;
}
GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red,
FString::Printf(TEXT("JoinSession Result: %s"), *ResultStr));
if (!OnlineSessionInterface.IsValid())
return;
if (Result != EOnJoinSessionCompleteResult::Success)
return;
FString ConnectString;
if (OnlineSessionInterface->GetResolvedConnectString(SessionName, ConnectString))
{
APlayerController* Controller = UGameplayStatics::GetPlayerController(this, 0);
if (Controller)
Controller->ClientTravel(ConnectString, ETravelType::TRAVEL_Absolute);
}
}