SteamAPI in Unreal Engine

Okay so I have had some troubles getting Steam multiplayer working in Unreal Engine 5.6

I am going to share my current configuration and what is working for me, for reference to anyone else trying to get this working.

There is still a lingering issue when trying to join sessions hosted on the MacOS from windows, at this stage I am guessing something like firewall blocking the client inbound connections. I don’t have two Mac’s to test Mac -> Mac Connection, however the current state of play is that Windows -> Windows lobbies work fine, MacOS -> Windows lobbies work fine, but Windows -> MacOS lobbies fail to join and timeout.

DefaultEngine.ini Relevant settings:

[OnlineSubsystem]
DefaultPlatformService=Steam
PollingIntervalInMs=20
bUseBuildIdOverride=True
BuildIdOverride=1

[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=3488710
bInitServerOnClient=true
bAllowP2PPacketRelay=true
bVACEnabled=false
bRelaunchInSteam=false
GameServerQueryPort=7777
bUseSteamNetworking=true ; enables SteamSockets path

SteamSockets must be enabled in Project Plugins.

I am also using AdvancedSession/AdvancedSteamSessions, I have a derivative AdvancedFriendsGameInstance class that is then also Derived in Blueprints.

The reason for a derived class was to add a new Join Session function, I was having some trouble with joining that may have been unrelated, but nevertheless, here is where I have ended up. For science I could go back and test the built in Join Session (maybe later), for now find my full class overwrite below:

RW_AdvancedGameInstanceCPP.h

#pragma once

#include "CoreMinimal.h"
#include "AdvancedFriendsGameInstance.h"                // AdvancedSessions base GI
#include "Interfaces/OnlineSessionInterface.h"
#include "RW_AdvancedGameInstanceCPP.generated.h"

UCLASS()
class RACEWARS_GREEN_API URW_AdvancedGameInstanceCPP : public UAdvancedFriendsGameInstance
{
    GENERATED_BODY()

public:
    /** Call this from BP with the selected FBlueprintSessionResult */
    UFUNCTION(BlueprintCallable, Category = "Online|Sessions")
    void JoinSessionFromResult(const FBlueprintSessionResult& SessionResult);

private:
    FOnJoinSessionCompleteDelegate JoinSessionCompleteDelegate;
    FDelegateHandle JoinSessionCompleteHandle;

    void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);

    IOnlineSessionPtr GetSessions() const;
};

And RW_AdvancedGameInstanceCPP.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "RW_AdvancedGameInstanceCPP.h"

#include "OnlineSubsystem.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/Engine.h"

IOnlineSessionPtr URW_AdvancedGameInstanceCPP::GetSessions() const
{
    if (IOnlineSubsystem* OSS = IOnlineSubsystem::Get())
    {
        return OSS->GetSessionInterface();
    }
    return nullptr;
}

void URW_AdvancedGameInstanceCPP::JoinSessionFromResult(const FBlueprintSessionResult& SessionResult)
{
    IOnlineSessionPtr Sessions = GetSessions();
    if (!Sessions.IsValid())
    {
        UE_LOG(LogTemp, Error, TEXT("[JoinSessionFromResult] No SessionInterface."));
        return;
    }

    // Bind join complete
    JoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(
        this, &URW_AdvancedGameInstanceCPP::OnJoinSessionComplete
    );
    JoinSessionCompleteHandle = Sessions->AddOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegate);

    const FName SessionName = NAME_GameSession; // Must match your CreateSession name
    const int32 LocalUserNum = 0;

    const bool bJoinStarted = Sessions->JoinSession(LocalUserNum, SessionName, SessionResult.OnlineResult);
    UE_LOG(LogTemp, Log, TEXT("[JoinSessionFromResult] JoinSession started: %s"), bJoinStarted ? TEXT("true") : TEXT("false"));

    if (!bJoinStarted)
    {
        Sessions->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteHandle);
    }
}

void URW_AdvancedGameInstanceCPP::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
    IOnlineSessionPtr Sessions = GetSessions();
    if (Sessions.IsValid())
    {
        Sessions->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteHandle);
    }

    UE_LOG(LogTemp, Log, TEXT("[OnJoinSessionComplete] Result: %d (0=Success)"), (int32)Result);

    if (Result != EOnJoinSessionCompleteResult::Success)
    {
        UE_LOG(LogTemp, Warning, TEXT("[OnJoinSessionComplete] Join failed."));
        return;
    }

    // Resolve Steam connect string and force ABSOLUTE travel
    FString ConnectString;
#if ENGINE_MAJOR_VERSION >= 5
    const bool bHasConnect = Sessions->GetResolvedConnectString(SessionName, ConnectString, NAME_GamePort);
#else
    const bool bHasConnect = Sessions->GetResolvedConnectString(SessionName, ConnectString);
#endif

    if (!bHasConnect || ConnectString.IsEmpty())
    {
        UE_LOG(LogTemp, Error, TEXT("[OnJoinSessionComplete] No ConnectString resolved."));
        return;
    }

    UE_LOG(LogTemp, Log, TEXT("[OnJoinSessionComplete] Resolved ConnectString: %s"), *ConnectString);

    if (APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0))
    {
        // Critical: ABSOLUTE travel prevents appending your current MainMenu map path
        PC->ClientTravel(ConnectString, TRAVEL_Absolute);
    }
    else
    {
        UE_LOG(LogTemp, Error, TEXT("[OnJoinSessionComplete] No PlayerController."));
    }
}

Beyond this, I am using the standard CreateAdvancedSession blueprint node, along with the FindSessionsAdvanced and my custom JoinSessionfromResult function. I also call the custom join function for “Event OnSessionInviteAccepted”

One crucial point to make is that session destruction is not being handled well by default, particularly as a client in a session, if the host quits/ends session the client does not seem to destroy the session, so if the client was then to try host or join a session they may fail due to not being able to do so while already in a game session.

I have added a “Destroy Session” infront of all the create/join sessions but suspect there is a cleaner method that can destroy the sessions when leaving. Will continue looking into that.

Other notes would be to download the correct version of AdvancedSession’s that you need from Advanced Sessions Binaries – VR Expansion Plugin

My DefaultEngine.ini is referencing a custom BuildID but this may be unnecessary, it was added for testing, but if it can be used as a way to prevent different build’s from accessing each other’s sessions it would remove one of my session filters requirements.

That’s all for now. Race Wars v0.0.961 released, tested working Windows -> Windows for all multiplayer sessions.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *