Powrót do Bloga

Dlaczego Twój Steam Online Subsystem nie może dołączyć do lobby w Unreal Engine 5

Opublikowano 15 czerwca 2026
Dlaczego Twój Steam Online Subsystem nie może dołączyć do lobby w Unreal Engine 5

W skrócie

Artykuł omawia przyczyny problemów z dołączaniem do lobby w Unreal Engine 5 przy użyciu Steam Online Subsystem oraz sposoby ich rozwiązania. Wyjaśnia konflikt pomiędzy starszym `SteamNetDriver` a nowoczesnym pluginem `SteamSockets` oraz wskazuje prawidłową konfigurację plików ini i zależności kompilacji w C++. Przedstawia również pułapki związane z testowaniem na współdzielonym AppID 480 i konfliktami portów na jednej maszynie. Na koniec sugeruje wykorzystanie dedykowanego Backendu [horizOn](https://horizon.pm) w celu uproszczenia architektury sieciowej gry.

Pakujesz swój projekt w Unreal Engine 5, uruchamiasz dwa osobne konta Steam, włączasz klienta i widzisz, jak Twoje hostowane lobby pojawia się w wynikach wyszukiwania sesji. Klikasz „Join”, czekasz pięć sekund i... nic się nie dzieje. Ekran zamarza, log konsoli wyświetla generyczne ostrzeżenie o socketach, a Ty zostajesz wyrzucony z powrotem do menu głównego.

Jeśli Twoje połączenia steam online subsystem cant join lobby nie mogą zostać nawiązane, masz do czynienia z jednym z najbardziej frustrujących problemów z konfiguracją sieciową w Unreal Engine 5. To cichy błąd (silent failure), w którym API Steam do Matchmaking poprawnie rejestruje Twoje lobby, ale network driver silnika nie może ukończyć niskopoziomowego handshake'u połączenia. W tym poradniku przeanalizujemy architekturę leżącą u podstaw network drivera, zidentyfikujemy typowe niezgodności w DefaultEngine.ini, rozwiążemy ograniczenia piaskownicy AppID 480 i pokażemy, jak skonfigurować niezawodny pipeline połączenia ze Steam w Unreal Engine 5.

Zrozumienie architektury Netcode: Epic Games vs. Valve

Aby zrozumieć, dlaczego dołączanie się nie udaje, musisz pojąć, jak warstwa Netcode w Unreal Engine tłumaczy wynik wyszukiwania lobby Steam na połączenie sieciowe. Kiedy wywołujesz JoinSession z kodu gry, interfejs sesji OnlineSubsystemSteam tłumaczy lobby na connection string. W przypadku Steam, ten connection string ma format steam.STEAM_ID (na przykład steam.76561198000000000), co reprezentuje unikalny Steam ID hosta.

Fabryka network driverów w Unreal Engine (GameNetDriver) otrzymuje ten connection string i analizuje prefiks schematu (steam.). Następnie sprawdza konfigurację w DefaultEngine.ini w sekcji [/Script/Engine.GameEngine], aby znaleźć klasę skonfigurowaną do obsługi połączeń Steam. Jeśli tego mapowania brakuje, jest ono niedopasowane lub odpowiednia klasa połączenia sieciowego nie została załadowana, silnik powraca do IpNetDriver.

IpNetDriver nie potrafi rozwiązać Steam ID. Spróbuje potraktować steam.76561198000000000 jako standardową nazwę hosta DNS lub adres IP, nie zdoła jej rozwiązać i wywoła network timeout. Jeśli Twoje network drivery są błędnie skonfigurowane lub handshake'i połączeń kończą się niepowodzeniem, napotkasz te same limity czasu (network driver timeouts), które nękają deweloperów próbujących rozwiązać problem Unreal Engine network driver timeouts. Zrozumienie, jak definicje NetDriver odpowiadają schematowi połączenia, to pierwszy krok do rozwiązania tego niedopasowania.

Konflikt SteamSockets vs. SteamNetDriver

Najczęstszą przyczyną tego błędu w Unreal Engine 5 jest konflikt między przestarzałym SteamNetDriver a nowoczesną wtyczką SteamSockets. Historycznie Unreal Engine używał starszego SteamNetDriver (opartego na dawnym API P2P od Valve). Współczesne projekty UE5 korzystają z wtyczki SteamSockets, która wykorzystuje API Steam Networking Sockets od Valve (obsługujące Steam Datagram Relay, czyli SDR, w celu ochrony przed DDoS i optymalizacji routingu).

Wielu deweloperów dodaje „SteamSockets” do modułów zależności (dependency modules) swojego projektu w C++, ale zapomina o zaktualizowaniu definicji klas network driverów w plikach konfiguracyjnych. Lub odwrotnie – wskazują klasy przestarzałego drivera, podczas gdy silnik próbuje zainicjalizować Steam Sockets. Przyjrzyjmy się poprawnej konfiguracji dla każdego z tych podejść.

Konfiguracja przestarzałego SteamNetDriver

Jeśli używasz przestarzałego network drivera OnlineSubsystemSteam, Twój plik DefaultEngine.ini musi mapować GameNetDriver na starszą klasę:

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

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

Dodatkowo upewnij się, że opcja bUseSteamNetworking=true jest ustawiona w sekcji [OnlineSubsystemSteam] w pliku DefaultEngine.ini.

Konfiguracja nowoczesnego SteamSockets

Jeśli włączyłeś wtyczkę SteamSockets w swoim pliku .uproject, musisz zmienić nazwy klas drivera i połączeń. Zwróć uwagę na zmianę nazw klas oraz nagłówków sekcji:

[/Script/Engine.GameEngine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="SteamSockets.SteamSocketsNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")

[/Script/SteamSockets.SteamSocketsNetDriver]
NetConnectionClassName="SteamSockets.SteamSocketsNetConnection"

Jeśli skonfigurujesz plik Build.cs tak, aby zawierał SteamSockets, ale użyjesz konfiguracji przestarzałego OnlineSubsystemSteam.SteamNetDriver w swoim pliku INI, silnik zainicjalizuje niewłaściwą klasę połączenia sieciowego. Uniemożliwi to handshake'owi sieciowemu klienta rozwiązanie Steam ID hosta, co sprawi, że próba dołączenia zawiesi się i nastąpi timeout.

Weryfikacja zależności kompilacji (Build Dependencies)

Upewnij się, że plik .Build.cs Twojego projektu pasuje do wybranej konfiguracji. Na przykład, jeśli celujesz w nowoczesną implementację Steam Sockets, plik Build.cs Twojego głównego modułu gry musi jawnie deklarować zarówno OnlineSubsystemSteam, jak i SteamSockets:

using UnrealBuildTool;

public class MyTPSGame : ModuleRules
{
    public MyTPSGame(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(new string[] {
            "Core",
            "CoreUObject",
            "Engine",
            "InputCore",
            "EnhancedInput",
            "OnlineSubsystem",
            "OnlineSubsystemUtils",
            "OnlineSubsystemSteam",
            "SteamSockets",
            "UMG",
            "Slate",
            "SlateCore"
        });
    }
}

Pułapka piaskownicy (Sandbox) Steam AppID 480

Jeśli konfiguracja jest poprawna, ale nadal nie możesz dołączyć, prawdopodobnie padasz ofiarą ograniczeń piaskownicy Steam Dev AppID 480. Domyślnie deweloperzy używają AppID 480 (Spacewar) do testowania integracji ze Steam bez rejestrowania komercyjnej aplikacji w Valve. Jednak AppID 480 jest współdzielone przez tysiące deweloperów na całym świecie.

Stwarza to dwa wyraźne problemy:

  1. Zanieczyszczenie Lobby (Lobby Pollution): Wyszukiwanie sesji na AppID 480 zwróci lobby hostowane przez gry innych deweloperów. Kiedy Twój klient spróbuje dołączyć do losowego lobby, zakończy się to niepowodzeniem z powodu niezgodności wersji gry lub ID kompilacji (build ID).
  2. Izolacja Regionalna: Lobby Steam domyślnie mają widoczność regionalną, aby utrzymać niski ping. Jeśli testujesz grę ze współpracownikiem w innej lokalizacji (na przykład jeden w Nowym Jorku, a drugi w Londynie), standardowe zapytania o sesje nie znajdą się nawzajem ani nie połączą, dopóki filtry odległości wyszukiwania nie zostaną zmodyfikowane.

Aby obejść te ograniczenia, musisz jawnie skonfigurować ustawienia wyszukiwania sesji w C++, używając globalnego filtra odległości (worldwide distance filter) oraz niestandardowych parametrów zapytania.

Oto jak napisać niestandardowe zapytanie wyszukiwania sesji w C++, aby celować w globalne lobby Steam, jednocześnie odfiltrowując niepowiązany ruch z AppID 480:

#include "OnlineSubsystem.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "OnlineSessionSettings.h"

void UMultiplayerSessionSubsystem::FindSteamLobbies(int32 MaxResults)
{
    IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();
    if (!Subsystem)
    {
        UE_LOG(LogTemp, Warning, TEXT("Failed to get OnlineSubsystem."));
        return;
    }

    IOnlineSessionPtr SessionInterface = Subsystem->GetSessionInterface();
    if (!SessionInterface.IsValid())
    {
        UE_LOG(LogTemp, Warning, TEXT("Session interface is invalid."));
        return;
    }

    // Allocate a new session search configuration
    SessionSearch = MakeShareable(new FOnlineSessionSearch());
    SessionSearch->MaxSearchResults = MaxResults;
    SessionSearch->bIsLanQuery = false;

    // Use presence to ensure we look for Steam lobbies rather than dedicated servers
    SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
    SessionSearch->QuerySettings.Set(SEARCH_LOBBIES, true, EOnlineComparisonOp::Equals);

    // CRITICAL: Set Steam Lobby Search Distance Filter
    // 0 = Close, 1 = Default, 2 = Far, 3 = Worldwide
    // Worldwide is necessary if testing across different regions on AppID 480
    SessionSearch->QuerySettings.Set(SEARCH_LOBBY_SEARCH_DISTANCE_FILTER, 3, EOnlineComparisonOp::Equals);

    // Apply a unique game identifier key to filter out other developers' Spacewar lobbies
    // Replace "MY_UNIQUE_GAME_ID_KEY" with a unique string specific to your prototype
    SessionSearch->QuerySettings.Set(TEXT("GAME_VERSION_KEY"), FString("MyTPSGame_v1.0.4"), EOnlineComparisonOp::Equals);

    // Bind callback to handle search completion
    OnFindSessionsCompleteDelegateHandle = SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(
        OnFindSessionsCompleteDelegate,
        FOnFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnFindSessionsComplete)
    );

    ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
    if (LocalPlayer)
    {
        SessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(), SessionSearch.ToSharedRef());
    }
}

Podczas tworzenia sesji na kliencie hostującym upewnij się, że dołączasz ten sam niestandardowy klucz (GAME_VERSION_KEY z wartością MyTPSGame_v1.0.4) do mapy FOnlineSessionSettings::Settings. Dzięki temu Twoje zapytanie wyszukiwania zwróci wyłącznie lobby Twojej gry, całkowicie eliminując problem zanieczyszczenia AppID 480. Gdy przejdziesz już wstępne połączenie z lobby, musisz również upewnić się, że Twoje wywołania RPC replikują się niezawodnie, nie powodując problemów z desynchronizacją Unreal Engine RPC replication desyncs, które mogą zepsuć stany rozgrywki natychmiast po dołączeniu.

Konflikty bindowania portów i testowania na jednej maszynie

Jeśli testujesz Multiplayer lokalnie na jednym komputerze przy użyciu dwóch kont Steam (np. korzystając z programu Sandboxie lub drugiej skompilowanej instancji uruchomionej za pomocą skryptu w wierszu poleceń), prawdopodobnie napotkasz konflikty bindowania portów. Podczas hostowania Steam Subsystem w Unreal Engine próbuje zarejestrować lokalny serwer gry. Ustawienie bInitServerOnClient=true w DefaultEngine.ini instruuje klienta, aby zainicjalizował API serwera gry Steamworks.

Jeśli dwie instancje na tej samej maszynie spróbują powiązać się z domyślnym portem zapytań Steam Query Port (27015) oraz Game Port (7777), druga instancja nie zdoła otworzyć swoich socketów. W rezultacie drugi klient wyszuka i znajdzie lobby, ale nie będzie w stanie zainicjalizować socketu dla połączenia przychodzącego.

Aby to naprawić:

  1. Zmień port dla drugiej instancji: Przy uruchamianiu drugiej instancji gry z terminala lub skryptu wsadowego (batch script), wymuś bindowanie do innego portu, dodając -port=7778 do parametrów wiersza poleceń.
  2. Popraw offsety portów zapytań: W sekcji [OnlineSubsystemSteam] zweryfikuj konfigurację portów zapytań i gry:
    [OnlineSubsystemSteam]
    bEnabled=true
    SteamDevAppId=480
    bInitServerOnClient=true
    bUseSteamNetworking=true
    GameServerQueryPort=27015
    
    Jeśli testujesz w tej samej sieci lokalnej, upewnij się, że zapora (firewall) routera nie blokuje ruchu UDP na tych portach.

Eliminacja problemów z Matchmakingiem dzięki nowoczesnemu Backendowi gry

Ręczne zmaganie się z konfiguracjami ini Steam, filtrami odległości i ograniczeniami AppID to ogromna strata czasu dla zespołów indie. Stworzenie gotowego do wdrożenia produkcyjnego systemu lobby z regionalnymi fallbackami, regułami Matchmakingu i niezawodnymi handshake'ami network driverów może z łatwością kosztować od 4 do 6 tygodni dedykowanej pracy nad Backendem.

W tym miejscu z pomocą przychodzi dedykowany Backend gry. horizOn to Backend-as-a-Service (BaaS) zaprojektowany specjalnie dla twórców gier. Zamiast zmuszać Cię do debugowania specyficznych dla Steam bibliotek socketów czy pisania skomplikowanych modułów opakowujących (wrapperów) w C++, horizOn dostarcza spójne SDK do zarządzania lobby, Matchmakingiem i sesjami graczy.

Przenosząc logikę Matchmakingu do horizOn, nie musisz martwić się o mapowanie network driverów w plikach silnika. Sesje graczy są pośredniczone za pomocą globalnie rozproszonej infrastruktury serwerowej, oferując błyskawiczny Matchmaking i zabezpieczone przed DDoS przekaźniki (relays) bezpośrednio po uruchomieniu.

Najlepsze praktyki dla Multiplayer na Steam w Unreal Engine

Aby zbudować niezawodny pipeline Multiplayer, który skaluje się poza fazę testów prototypu, postępuj zgodnie z poniższymi najlepszymi praktykami:

  1. Przejdź na Steam Sockets na wczesnym etapie: Unikaj konfiguracji przestarzałych driverów sieciowych P2P. Powiąż się z nowoczesnym network driverem SteamSockets, aby wykorzystać routing Steam Datagram Relay (SDR) i zapobiec problemom z przebijaniem NAT (NAT punch-through).
  2. Zastosuj niestandardowe klucze filtrowania: Podczas testowania na AppID 480 dodaj wysoce specyficzny identyfikator projektu do ustawień sesji. Zapobiegnie to próbom łączenia się klienta z innymi grami współdzielącymi piaskownicę.
  3. Prawidłowo obsługuj fallback do subsystemu NULL: Upewnij się, że Twój Netcode może awaryjnie przełączyć się na subsystem NULL, gdy Steam nie jest uruchomiony. Pozwala to na testowanie sieci LAN offline bez zakłócania logiki połączenia.
  4. Zoptymalizuj limity czasu dołączania do sesji (Session Join Timeouts): Ustaw ConnectionTimeout w sekcji [ActiveNetDriver] na co najmniej 15.0 sekund. Handshake P2P w Steam może zająć kilka sekund, szczególnie w przypadku routingu przez globalne przekaźniki SDR.

Podsumowanie

Rozwiązanie problemów z połączeniem z lobby Steam w Unreal Engine 5 sprowadza się do dopasowania konfiguracji silnika do wtyczek sieciowych, z których korzystasz. Poprzez poprawne zmapowanie NetDriverDefinitions i nadpisanie filtrów wyszukiwania Spacewar, możesz ustanowić stabilne połączenia na potrzeby swoich testów (playtests).

Jeśli chcesz uniknąć bólów głowy związanych z infrastrukturą Backend i skupić się wyłącznie na rozgrywce, rozważ integrację z dedykowanym rozwiązaniem. Gotowy na skalowanie swojego Backendu Multiplayer? Wypróbuj horizOn za darmo lub zapoznaj się z API docs.


Źródło: Steam Online Subsystem can't join lobby