Назад к блогу

Почему ваш Steam Online Subsystem не может выполнить Join к Lobby в Unreal Engine 5

Опубликовано 15 июня 2026 г.
Почему ваш Steam Online Subsystem не может выполнить Join к Lobby в Unreal Engine 5

Коротко о главном

Руководство посвящено устранению сбоев Steam lobby connection в Unreal Engine 5, возникающих из-за несовпадений в конфигурациях network driver. В статье разбираются различия между legacy `SteamNetDriver` и современным плагином `SteamSockets`, а также приводятся корректные параметры для DefaultEngine.ini. Дополнительно рассматриваются методы обхода ограничений песочницы AppID 480 с помощью кастомных фильтров поиска session и решение конфликтов port bindings при локальном тестировании.

Вы упаковываете свой проект на Unreal Engine 5, запускаете два разных аккаунта Steam, открываете client и видите созданное вами хостом lobby в результатах поиска session. Вы нажимаете «Join», ждете пять секунд и... ничего не происходит. Экран зависает, в console log выводится стандартный socket warning, и вас выбрасывает обратно в главное меню.

Если ваш Steam Online Subsystem не может подключиться к lobby, вы столкнулись с одной из самых раздражающих проблем сетевой конфигурации в Unreal Engine 5. Это «тихий» сбой: Steam matchmaking API успешно регистрирует ваше lobby, но network driver движка не может завершить низкоуровневый connection handshake. В этом руководстве мы разберем архитектуру network driver, найдем типичные нестыковки в DefaultEngine.ini, обойдем ограничения песочницы AppID 480 и покажем, как настроить надежный pipeline для подключения к Steam в Unreal Engine 5.

Понимание Netcode-архитектуры Epic Games и Valve

Чтобы понять, почему не удается подключиться, нужно разобраться, как netcode-слой Unreal Engine преобразует результаты поиска Steam lobby в сетевое соединение. Когда вы вызываете JoinSession из кода игры, интерфейс session в OnlineSubsystemSteam преобразует lobby в connection string. Для Steam эта строка отформатирована как steam.STEAM_ID (например, steam.76561198000000000), что представляет собой уникальный Steam ID host.

Фабрика network driver в Unreal Engine (GameNetDriver) получает эту connection string и парсит префикс схемы (steam.). Затем она проверяет конфигурацию вашего DefaultEngine.ini в секции [/Script/Engine.GameEngine], чтобы найти класс, настроенный для работы со Steam-соединениями. Если этот маппинг отсутствует, не совпадает или если нужный класс net connection не загружен, движок откатывается к IpNetDriver.

Класс IpNetDriver не умеет распознавать Steam ID. Он попытается обработать steam.76561198000000000 как стандартное DNS-имя или IP-адрес, не сможет его разрешить и вызовет network timeout. Если ваши network drivers настроены неправильно или connection handshakes завершаются ошибкой, вы столкнетесь с теми же network driver timeouts, которые преследуют разработчиков, пытающихся устранить Unreal Engine network driver timeouts. Понимание того, как определения NetDriver соотносятся со схемой подключения, — это первый шаг к устранению этого несоответствия.

Конфликт SteamSockets и SteamNetDriver

Самая частая причина этой проблемы в Unreal Engine 5 — конфликт между устаревшим SteamNetDriver и современным плагином SteamSockets. Исторически Unreal Engine использовал устаревший SteamNetDriver (на базе старого P2P API от Valve). Современные проекты на UE5 используют плагин SteamSockets, который задействует Steam Networking Sockets API от Valve (с поддержкой Steam Datagram Relay, или SDR, для защиты от DDoS и оптимизации маршрутизации).

Многие разработчики добавляют "SteamSockets" в зависимости модулей проекта на C++, но забывают обновить определения классов network driver в конфигурационных файлах. Или наоборот: они указывают устаревшие классы драйвера, в то время как движок пытается инициализировать Steam Sockets. Давайте посмотрим на правильную конфигурацию для каждого из подходов.

Конфигурация устаревшего SteamNetDriver

Если вы используете устаревший network driver в OnlineSubsystemSteam, ваш DefaultEngine.ini должен связывать GameNetDriver с устаревшим классом:

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

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

Кроме того, убедитесь, что параметр bUseSteamNetworking=true задан в категории [OnlineSubsystemSteam] вашего DefaultEngine.ini.

Конфигурация современного SteamSockets

Если вы включили плагин SteamSockets в файле .uproject, вам необходимо изменить имена классов driver и connection. Обратите внимание на изменения в именах классов и заголовках секций:

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

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

Если вы настроите Build.cs на включение SteamSockets, но будете использовать конфигурации устаревшего OnlineSubsystemSteam.SteamNetDriver в вашем INI-файле, движок инициализирует не тот класс net connection. Это помешает network handshake со стороны client разрешить Steam ID host, из-за чего попытка join зависнет и завершится по timeout.

Проверка Build Dependencies

Убедитесь, что .Build.cs вашего проекта соответствует выбранному подходу. Например, если вы ориентируетесь на современную реализацию Steam Sockets, файл Build.cs вашего основного модуля игры должен явно объявлять как OnlineSubsystemSteam, так и 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"
        });
    }
}

Ловушка Sandbox в Steam AppID 480

Если ваши конфигурации верны, но вы все равно не можете выполнить join, скорее всего, вы стали жертвой ограничений песочницы Steam Dev AppID 480. По умолчанию разработчики используют AppID 480 (Spacewar) для тестирования интеграции со Steam без регистрации коммерческого приложения в Valve. Однако AppID 480 используется тысячами разработчиков по всему миру.

Это создает две разные проблемы:

  1. Засорение Lobby: поиск session на AppID 480 вернет lobbies, созданные играми других разработчиков. Когда ваш client попытается выполнить join к случайному lobby, это приведет к ошибке из-за несовпадения версий игры или build ID.
  2. Региональная изоляция: по умолчанию видимость Steam lobbies ограничена регионом для поддержания низкого пинга. Если вы тестируете multiplayer с удаленным коллегой (например, один в Нью-Йорке, а другой в Лондоне), стандартные запросы session не найдут друг друга и не смогут выполнить connection, пока вы не измените фильтры расстояния поиска.

Чтобы обойти эти ограничения, вам нужно явно настроить параметры поиска session в C++, используя глобальный фильтр расстояния (worldwide distance filter) и пользовательские параметры запроса.

Вот как написать собственный поисковый запрос session на C++, чтобы находить глобальные Steam lobbies и отфильтровывать посторонний трафик 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());
    }
}

При создании session на стороне hosting client обязательно добавьте тот же пользовательский ключ (GAME_VERSION_KEY со значением MyTPSGame_v1.0.4) в карту FOnlineSessionSettings::Settings. Это гарантирует, что ваш поисковый запрос вернет только lobbies вашей игры, полностью исключая засорение AppID 480. Как только вы пройдете этап первоначального connection к lobby, вам также нужно будет убедиться, что ваши RPC реплицируются надежно, не вызывая Unreal Engine RPC replication desyncs, которые могут нарушить состояния gameplay сразу после join.

Конфликты Port Bindings и тестирования на одной машине

Если вы тестируете multiplayer локально на одном компьютере с использованием двух аккаунтов Steam (например, через Sandboxie или второй скомпилированный экземпляр, запущенный через командную строку), вы, скорее всего, столкнетесь с конфликтами привязки портов (port bindings). При хостинге Steam Subsystem в Unreal Engine пытается зарегистрировать локальный игровой сервер. Параметр bInitServerOnClient=true в DefaultEngine.ini указывает client инициализировать API игрового сервера Steamworks.

Если оба экземпляра на одной машине попытаются привязаться к стандартным портам Steam Query Port (27015) и Game Port (7777), второй экземпляр не сможет открыть свои sockets. В результате второй client найдет lobby через поиск, но не сможет инициализировать входящее соединение через socket.

Для исправления этого:

  1. Измените порт для второго экземпляра: при запуске второго экземпляра игры из терминала или батника принудительно привяжите его к другому порту, добавив -port=7778 к параметрам командной строки.
  2. Исправьте смещения портов запроса (Query Port Offsets): в секции [OnlineSubsystemSteam] проверьте конфигурацию портов запроса и игры:
    [OnlineSubsystemSteam]
    bEnabled=true
    SteamDevAppId=480
    bInitServerOnClient=true
    bUseSteamNetworking=true
    GameServerQueryPort=27015
    
    При тестировании в локальной сети, убедитесь, что брандмауэр вашего роутера не блокирует UDP-трафик на этих портах.

Устранение проблем с Matchmaking с помощью современного Game Backend

Ручная настройка ini-файлов Steam, фильтров расстояния и ограничений AppID — это огромная трата времени для инди-команд. Создание готовой к релизу системы лобби с региональными резервными узлами, правилами matchmaking и надежными network driver handshakes может легко потребовать от 4 до 6 недель чистой backend engineering.

Именно здесь на помощь приходит выделенный игровой backend. horizOn — это Backend-as-a-Service (BaaS), разработанный специально для разработчиков игр. Вместо того чтобы заставлять вас отлаживать специфичные для Steam библиотеки сокетов или писать сложные модули-обертки на C++, horizOn предлагает единый SDK для управления lobbies, matchmaking и player sessions.

Перенеся логику matchmaking в horizOn, вам больше не придется беспокоиться о маппинге network driver в конфигурационных файлах движка. Player sessions координируются с помощью глобально распределенной серверной инфраструктуры, предоставляя мгновенный matchmaking и DDoS-protected relays прямо «из коробки».

Best Practices по работе со Steam Multiplayer в Unreal Engine

Чтобы создать надежный multiplayer pipeline, способный масштабироваться за рамки прототипа, следуйте этим best practices:

  1. Перейдите на Steam Sockets как можно раньше: избегайте устаревших конфигураций legacy P2P network driver. Привязывайтесь к современному net driver SteamSockets, чтобы использовать маршрутизацию Steam Datagram Relay (SDR) и предотвратить ошибки NAT punch-through.
  2. Применяйте пользовательские ключи фильтрации: при тестировании на AppID 480 добавляйте к настройкам session уникальный идентификатор проекта. Это не даст вашему client попытаться выполнить join к другим играм в этой же sandbox.
  3. Корректно обрабатывайте откат к NULL Subsystem: убедитесь, что ваш netcode может откатываться к NULL subsystem, если Steam не запущен. Это позволит проводить offline LAN testing без нарушения логики connection.
  4. Оптимизируйте таймауты подключения к сессии (session join timeouts): установите значение ConnectionTimeout в секции [ActiveNetDriver] как минимум на 15.0 секунд. Выполнение P2P handshake в Steam может занять несколько секунд, особенно при маршрутизации через global SDR relays.

Заключение

Устранение сбоев Steam lobby connection в Unreal Engine 5 сводится к правильной синхронизации конфигураций движка с сетевыми плагинами, на базе которых строится проект. Корректно настроив маппинг NetDriverDefinitions и переопределив фильтры поиска Spacewar, вы сможете обеспечить стабильные connections для ваших playtests.

Если вы хотите избавить себя от головной боли с настройкой backend infrastructure и полностью сосредоточиться на gameplay, рассмотрите интеграцию с выделенным backend. Готовы масштабировать свой multiplayer backend? Попробуйте horizOn бесплатно или изучите API docs.


Источник: Steam Online Subsystem can't join lobby