Volver al Blog

Por qué tu Steam Online Subsystem no puede unirse a un Lobby en Unreal Engine 5

Publicado el 15 de junio de 2026
Por qué tu Steam Online Subsystem no puede unirse a un Lobby en Unreal Engine 5

En resumen

Esta guía técnica aborda los problemas comunes de conexión en lobbies al utilizar el Steam Online Subsystem en Unreal Engine 5, centrándose en la correcta configuración del DefaultEngine.ini para evitar fallos de resolución de dirección IP. Explica la transición entre el legacy SteamNetDriver y el moderno SteamSockets (SDR), además de cómo evitar la polución del sandbox de desarrollo en el AppID 480 (Spacewar). Finalmente, ofrece pautas de filtrado en C++, gestión de puertos locales y alternativas de backend para optimizar el matchmaking en proyectos multiplayer.

Empaquetas tu proyecto de Unreal Engine 5, ejecutas dos cuentas de Steam independientes, lanzas el cliente y ves tu lobby hosteado aparecer en los resultados de búsqueda de sesiones. Pulsas "Join", esperas cinco segundos y... no ocurre nada. La pantalla se congela, el log de consola imprime un warning genérico de socket y el juego te devuelve al menú principal.

Si tienes problemas donde tu Steam Online Subsystem no puede unirse a conexiones de lobby, estás lidiando con uno de los problemas de configuración de red más frustrantes de Unreal Engine 5. Se trata de un fallo silencioso en el que la API de matchmaking de Steam registra correctamente tu lobby, pero el network driver del motor no puede completar el handshake de conexión a bajo nivel. En esta guía, desglosaremos la arquitectura subyacente del network driver, identificaremos los desajustes comunes en el DefaultEngine.ini, resolveremos las limitaciones del sandbox del AppID 480 y te mostraremos cómo configurar un pipeline de conexión de Steam infalible en Unreal Engine 5.

Entendiendo la arquitectura de Netcode de Epic Games vs. Valve

Para entender por qué falla la conexión, primero debes comprender cómo la capa de netcode de Unreal Engine traduce el resultado de búsqueda de un lobby de Steam en una conexión de red. Cuando llamas a JoinSession desde el código de tu juego, la interfaz de sesión de OnlineSubsystemSteam resuelve el lobby en un connection string. Para Steam, este connection string tiene el formato steam.STEAM_ID (por ejemplo, steam.76561198000000000), que representa el Steam ID único del host.

La factoría del network driver de Unreal Engine (GameNetDriver) recibe este connection string y analiza el prefijo del esquema (steam.). Luego, comprueba tus configuraciones en el DefaultEngine.ini bajo [/Script/Engine.GameEngine] para encontrar la clase configurada para manejar conexiones de Steam. Si este mapeo falta, no coincide o si la clase de conexión de red correcta no está cargada, el motor recurre por defecto al IpNetDriver.

El IpNetDriver no puede resolver un Steam ID. Intentará tratar steam.76561198000000000 como un hostname DNS estándar o una dirección IP, fallará al resolverlo y provocará un network timeout. Si tus network drivers están mal configurados o los handshakes de conexión fallan, te toparás con los mismos network driver timeouts que sufren los desarrolladores al intentar solucionar los timeouts del network driver de Unreal Engine. Comprender cómo coinciden las definiciones de NetDriver con el esquema de conexión es el primer paso para solucionar este desajuste.

El conflicto entre SteamSockets y SteamNetDriver

La causa más común de este fallo en Unreal Engine 5 es un conflicto entre el legacy SteamNetDriver y el plugin moderno SteamSockets. Históricamente, Unreal Engine utilizaba el legacy SteamNetDriver (basado en la antigua API P2P de Valve). Los proyectos modernos de UE5 utilizan el plugin SteamSockets, que aprovecha la API Steam Networking Sockets de Valve (soportando Steam Datagram Relay, o SDR, para protección DDoS y optimización de enrutamiento).

Muchos desarrolladores añaden "SteamSockets" a los módulos de dependencia de su proyecto en C++, pero olvidan actualizar las definiciones de la clase del network driver en sus archivos de configuración. O a la inversa: especifican las clases del driver legacy mientras el motor intenta inicializar Steam Sockets. Veamos la configuración correcta para cada enfoque.

Configuración del Legacy SteamNetDriver

Si estás utilizando el network driver legacy OnlineSubsystemSteam, tu DefaultEngine.ini debe mapear el GameNetDriver a la clase legacy:

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

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

Además, asegúrate de que bUseSteamNetworking=true esté configurado bajo la categoría [OnlineSubsystemSteam] en tu DefaultEngine.ini.

Configuración del moderno SteamSockets

Si has habilitado el plugin SteamSockets en tu archivo .uproject, debes cambiar los nombres de las clases del driver y de conexión. Ten en cuenta el cambio en los nombres de las clases y las cabeceras de sección:

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

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

Si configuras tu Build.cs para incluir SteamSockets pero utilizas las configuraciones del legacy OnlineSubsystemSteam.SteamNetDriver en tu archivo INI, el motor inicializará la clase de conexión de red incorrecta. Esto evita que el handshake de red del cliente resuelva el Steam ID del host, lo que provoca que el intento de unión (join) se cuelgue y expire por timeout.

Verificando las dependencias de Build

Asegúrate de que el archivo .Build.cs de tu proyecto coincida con la configuración elegida. Por ejemplo, si estás apuntando a la implementación moderna de Steam Sockets, el archivo Build.cs del módulo principal de tu juego debe declarar explícitamente tanto OnlineSubsystemSteam como 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"
        });
    }
}

La trampa del Sandbox de Steam AppID 480

Si tus configuraciones son correctas pero sigues sin poder unirte, es muy probable que estés cayendo en la trampa de los límites del sandbox de Steam Dev AppID 480. Por defecto, los desarrolladores usan el AppID 480 (Spacewar) para probar la integración con Steam sin necesidad de registrar una aplicación comercial con Valve. Sin embargo, el AppID 480 es compartido por miles de desarrolladores en todo el mundo.

Esto genera dos problemas muy claros:

  1. Polución de Lobbies (Lobby Pollution): Una búsqueda de sesiones en el AppID 480 devolverá lobbies hosteados por juegos de otros desarrolladores. Cuando tu cliente intente unirse a un lobby aleatorio, fallará debido a la discrepancia de versiones de juego o IDs de compilación (build IDs).
  2. Aislamiento por Región: Por defecto, los lobbies de Steam tienen visibilidad regional para mantener pings bajos. Si estás haciendo pruebas con un compañero fuera de tu oficina (por ejemplo, uno en Nueva York y otro en Londres), las consultas de sesión estándar no se encontrarán ni se conectarán entre sí a menos que modifiques los filtros de distancia de búsqueda.

Para evitar estas limitaciones, debes configurar explícitamente los parámetros de búsqueda de sesión en C++ utilizando un filtro de distancia a nivel mundial (worldwide) y parámetros de consulta personalizados.

Aquí tienes cómo escribir una consulta de búsqueda de sesión personalizada en C++ para apuntar a lobbies globales de Steam mientras filtras el tráfico no relacionado del 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());
    }
}

Al crear la sesión en el cliente host, asegúrate de adjuntar la misma clave personalizada (GAME_VERSION_KEY con el valor MyTPSGame_v1.0.4) al mapa FOnlineSessionSettings::Settings. Esto garantiza que tu consulta de búsqueda solo devuelva lobbies de tu propio juego, evitando por completo la polución del AppID 480. Una vez que superes la conexión inicial del lobby, también debes asegurarte de que tus RPCs se repliquen de manera confiable sin causar desincronizaciones de replicación RPC en Unreal Engine que puedan romper los estados de tu gameplay inmediatamente después de unirte.

Conflictos de Port Bindings y pruebas en una sola máquina

Si estás probando el multiplayer localmente en un solo PC usando dos cuentas de Steam (por ejemplo, con Sandboxie o lanzando una segunda instancia compilada mediante un script de consola), es muy probable que te enfrentes a conflictos de port binding. Al hostear, el Steam Subsystem de Unreal Engine intenta registrar un servidor de juego local. El parámetro bInitServerOnClient=true en DefaultEngine.ini le indica al cliente que inicialice la API de servidor de juego de Steamworks.

Si dos instancias en la misma máquina intentan vincularse (bind) al Steam Query Port (27015) y Game Port (7777) por defecto, la segunda instancia fallará al abrir sus sockets. Como resultado, el segundo cliente buscará y encontrará el lobby, pero no podrá inicializar el socket de conexión entrante.

Para solucionar esto:

  1. Cambia el puerto para la segunda instancia: Al lanzar la segunda instancia del juego desde la terminal o un script de lotes (batch), fuérzala a vincularse a un puerto diferente añadiendo -port=7778 a los parámetros de la línea de comandos.
  2. Corrige los offsets del Query Port: Bajo [OnlineSubsystemSteam], verifica la configuración de tus puertos de query y de juego:
    [OnlineSubsystemSteam]
    bEnabled=true
    SteamDevAppId=480
    bInitServerOnClient=true
    bUseSteamNetworking=true
    GameServerQueryPort=27015
    
    Si estás haciendo pruebas en la misma red local, asegúrate de que el firewall de tu router no esté bloqueando el tráfico UDP en estos puertos.

Eliminando dolores de cabeza en Matchmaking con un Game Backend moderno

Lidiar manualmente con las configuraciones ini de Steam, los filtros de distancia y las limitaciones de AppID consume una cantidad enorme de tiempo para los equipos indie. Montar un sistema de lobbies listo para producción con fallbacks regionales, reglas de matchmaking y handshakes de network driver confiables puede costar fácilmente de 4 a 6 semanas de ingeniería de backend dedicada.

Aquí es donde entra en juego un backend dedicado. horizOn es un Backend-as-a-Service (BaaS) diseñado específicamente para desarrolladores de videojuegos. En lugar de obligarte a depurar librerías de sockets específicas de Steam o a escribir complejos módulos wrapper en C++, horizOn te proporciona un SDK unificado para gestionar lobbies, matchmaking y sesiones de jugadores.

Al migrar tu lógica de matchmaking a horizOn, no tendrás que preocuparte por mapeos de network drivers en los archivos de tu motor. Las sesiones de los jugadores se gestionan mediante una infraestructura de servidores distribuida globalmente, ofreciendo matchmaking instantáneo y relays protegidos contra DDoS desde el primer momento.

Buenas prácticas para Multiplayer de Steam en Unreal Engine

Para construir un pipeline multiplayer confiable que escale más allá de las pruebas de prototipos, sigue estas buenas prácticas:

  1. Haz la transición a Steam Sockets pronto: Evita configuraciones heredadas (legacy) de P2P para el networking driver. Vincula tu proyecto al network driver moderno SteamSockets para aprovechar el enrutamiento de Steam Datagram Relay (SDR) y evitar fallos de NAT punch-through.
  2. Aplica claves de filtrado personalizadas: Al realizar pruebas en el AppID 480, añade un identificador de proyecto muy específico a tus configuraciones de sesión. Esto evitará que tu cliente intente conectarse a otros juegos que comparten el sandbox.
  3. Maneja con elegancia el fallback al Subsystem NULL: Asegúrate de que tu netcode pueda recurrir al subsystem NULL cuando Steam no se esté ejecutando. Esto permite realizar pruebas offline en LAN sin romper la lógica de conexión.
  4. Optimiza los timeouts para unirse a sesiones: Establece ConnectionTimeout bajo [ActiveNetDriver] en al menos 15.0 segundos. El handshake P2P de Steam puede tardar varios segundos en completarse, especialmente si se enruta a través de relays globales de SDR.

Conclusión

Solucionar los fallos de conexión de los lobbies de Steam en Unreal Engine 5 se reduce a alinear las configuraciones del motor con los plugins de red que utilizas al compilar. Mapeando tus NetDriverDefinitions correctamente y sobrescribiendo los filtros de búsqueda de Spacewar, podrás establecer conexiones estables para tus playtests.

Si quieres ahorrarte los dolores de cabeza de la infraestructura del backend y enfocarte por completo en el gameplay, considera integrarlo con un backend dedicado. ¿Listo para escalar tu backend multiplayer? Prueba horizOn gratis o echa un vistazo a la documentación de la API.


Fuente: Steam Online Subsystem can't join lobby