Por que seu Steam Online Subsystem não consegue entrar no Lobby na Unreal Engine 5
Em resumo
Este guia aborda a resolução de falhas de conexão ao tentar entrar em lobbies da Steam na Unreal Engine 5. Analisamos a arquitetura do network driver, detalhando as configurações corretas no DefaultEngine.ini tanto para o legado SteamNetDriver quanto para o moderno SteamSockets. Também explicamos como superar as limitações da sandbox do AppID 480 utilizando filtros de busca customizados em C++ e resolvendo conflitos de port binding locais. Por fim, apresentamos o horizOn como uma alternativa para simplificar o matchmaking e o gerenciamento de sessões.
Você faz o package do seu projeto da Unreal Engine 5, roda duas contas Steam separadas, inicia o cliente e vê o seu lobby hospedado aparecer nos resultados de busca de sessão. Você clica em "Join", espera cinco segundos e... nada acontece. A tela trava, o log do console exibe um aviso genérico de socket e você é jogado de volta ao menu principal.
Se as conexões do seu Steam Online Subsystem não conseguem entrar no lobby, você está lidando com um dos problemas de configuração de rede mais frustrantes da Unreal Engine 5. Trata-se de uma falha silenciosa em que a API de matchmaking da Steam registra corretamente o seu lobby, mas o network driver da engine não consegue concluir o handshake de conexão de baixo nível. Neste guia, vamos analisar a arquitetura subjacente do network driver, identificar as divergências mais comuns no DefaultEngine.ini, resolver as limitações de sandbox do AppID 480 e mostrar como configurar um pipeline de conexão Steam robusto na Unreal Engine 5.
Entendendo a Arquitetura de Netcode da Epic Games vs. Valve
Para entender por que a conexão falha, é preciso compreender como a camada de netcode da Unreal Engine traduz o resultado de busca de um lobby da Steam em uma conexão de rede. Quando você chama JoinSession no código do seu jogo, a interface de sessão do OnlineSubsystemSteam resolve o lobby em uma connection string. No caso da Steam, essa connection string é formatada como steam.STEAM_ID (por exemplo, steam.76561198000000000), representando o Steam ID exclusivo do host.
A factory do network driver da Unreal Engine (GameNetDriver) recebe essa connection string e faz o parse do prefixo de esquema (steam.). Em seguida, ela verifica as configurações do seu DefaultEngine.ini na seção [/Script/Engine.GameEngine] para encontrar a classe configurada para lidar com conexões Steam. Se esse mapeamento estiver ausente, incorreto ou se a classe de conexão de rede correta não for carregada, a engine recorre ao IpNetDriver como fallback.
O IpNetDriver não consegue resolver um Steam ID. Ele tentará tratar steam.76561198000000000 como um hostname de DNS padrão ou endereço IP, falhará em resolvê-lo e disparará um timeout de rede. Se os seus network drivers estiverem desconfigurados ou os handshakes de conexão falharem, você encontrará os mesmos timeouts de network driver que atormentam desenvolvedores tentando resolver Unreal Engine network driver timeouts. Entender como as definições de NetDriver correspondem ao esquema de conexão é o primeiro passo para resolver essa incompatibilidade.
O Conflito SteamSockets vs. SteamNetDriver
A causa mais comum para essa falha na Unreal Engine 5 é um conflito entre o legado SteamNetDriver e o plugin moderno SteamSockets. Historicamente, a Unreal Engine usava o legado SteamNetDriver (baseado na API P2P mais antiga da Valve). Projetos modernos de UE5 usam o plugin SteamSockets, que utiliza a API Steam Networking Sockets da Valve (suportando Steam Datagram Relay, ou SDR, para proteção contra DDoS e otimização de rotas).
Muitos desenvolvedores adicionam "SteamSockets" aos módulos de dependência de seus projetos em C++, mas esquecem de atualizar as definições de classe do network driver em seus arquivos de configuração. Ou, inversamente, especificam as classes de driver legadas enquanto a engine tenta inicializar o Steam Sockets. Vamos dar uma olhada na configuração correta para cada abordagem.
A Configuração do Legado SteamNetDriver
Se você estiver usando o network driver legado do OnlineSubsystemSteam, seu DefaultEngine.ini deve mapear o GameNetDriver para a classe legada:
[/Script/Engine.GameEngine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
[/Script/OnlineSubsystemSteam.SteamNetDriver]
NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"
Além disso, certifique-se de que bUseSteamNetworking=true esteja definido na categoria [OnlineSubsystemSteam] do seu DefaultEngine.ini.
A Configuração Moderna do SteamSockets
Se você ativou o plugin SteamSockets no seu arquivo .uproject, você deve alterar os nomes das classes de driver e de conexão. Observe a mudança nos nomes das classes e nos cabeçalhos das seções:
[/Script/Engine.GameEngine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="SteamSockets.SteamSocketsNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
[/Script/SteamSockets.SteamSocketsNetDriver]
NetConnectionClassName="SteamSockets.SteamSocketsNetConnection"
Se você configurar o seu Build.cs para incluir SteamSockets, mas usar as configurações do legado OnlineSubsystemSteam.SteamNetDriver no seu arquivo INI, a engine inicializará a classe de conexão de rede errada. Isso impede que o handshake de rede do cliente resolva o Steam ID do host, fazendo com que a tentativa de join trave e sofra timeout.
Verificando Dependências de Build
Certifique-se de que o .Build.cs do seu projeto corresponda à configuração escolhida. Por exemplo, se o seu alvo for a implementação moderna do Steam Sockets, o arquivo Build.cs do módulo principal do seu jogo deve declarar explicitamente tanto OnlineSubsystemSteam quanto 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"
});
}
}
A Armadilha da Sandbox do Steam AppID 480
Se as suas configurações estiverem corretas, mas você ainda não conseguir entrar no jogo, é provável que esteja sendo vítima dos limites da sandbox do Steam Dev AppID 480. Por padrão, os desenvolvedores usam o AppID 480 (Spacewar) para testar a integração com a Steam sem precisar registrar um aplicativo comercial na Valve. No entanto, o AppID 480 é compartilhado por milhares de desenvolvedores no mundo todo.
Isso cria dois problemas distintos:
- Poluição de Lobbies: Uma busca de sessão no AppID 480 retornará lobbies hospedados por jogos de outros desenvolvedores. Quando o seu cliente tentar dar join em um lobby aleatório, a tentativa falhará devido a versões de jogo ou IDs de build incompatíveis.
- Isolamento de Região: Os lobbies da Steam utilizam visibilidade regional por padrão para manter os pings baixos. Se você estiver testando com um colega de equipe remoto (por exemplo, um em Nova York e outro em Londres), as consultas de sessão padrão não encontrarão nem conectarão os dois, a menos que seus filtros de distância de busca sejam modificados.
Para contornar essas limitações, você deve configurar explicitamente suas definições de busca de sessão em C++ para usar um filtro de distância mundial e parâmetros de busca customizados.
Veja como escrever uma busca de sessão customizada em C++ para buscar lobbies globais da Steam, filtrando o tráfego não relacionado do 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());
}
}
Ao criar a sessão no cliente host, certifique-se de anexar a mesma chave customizada (GAME_VERSION_KEY com o valor MyTPSGame_v1.0.4) ao mapa FOnlineSessionSettings::Settings. Isso garante que sua busca retorne apenas os lobbies do seu jogo, eliminando completamente a poluição do AppID 480. Uma vez superada a conexão inicial ao lobby, você também deve garantir que suas RPCs sejam replicadas de forma confiável, sem causar desyncs de replicação de RPC na Unreal Engine que podem quebrar os seus estados de gameplay imediatamente após o join.
Conflitos de Port Binding e Testes em uma Única Máquina
Se você estiver testando o multiplayer localmente em um único PC usando duas contas Steam (por exemplo, usando o Sandboxie ou uma segunda instância compilada iniciada por meio de um script de linha de comando), é provável que você enfrente conflitos de port binding. Ao hospedar, o Steam Subsystem da Unreal Engine tenta registrar um servidor de jogo local. A configuração bInitServerOnClient=true no DefaultEngine.ini instrui o cliente a inicializar a API de game server do Steamworks.
Se duas instâncias na mesma máquina tentarem se associar à Steam Query Port padrão (27015) e à Game Port (7777), a segunda instância falhará ao abrir seus sockets. Como resultado, o segundo cliente buscará e encontrará o lobby, mas não conseguirá inicializar o socket de conexão de entrada.
Para corrigir isso:
- Altere a Porta da Segunda Instância: Ao iniciar a segunda instância do jogo pelo terminal ou por um script em lote (batch script), force-a a se associar a uma porta diferente adicionando
-port=7778aos parâmetros de linha de comando. - Corrija os Offsets da Query Port: Na seção
[OnlineSubsystemSteam], verifique a configuração das portas de query e do jogo (game port):
[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480
bInitServerOnClient=true
bUseSteamNetworking=true
GameServerQueryPort=27015
Se estiver testando na mesma rede local, certifique-se de que o firewall do seu roteador não esteja bloqueando o tráfego UDP nessas portas.
Eliminando Dores de Cabeça no Matchmaking com um Game Backend Moderno
Lutar manualmente com as configurações de ini, filtros de distância e limitações de AppID da Steam é uma enorme perda de tempo para equipes indie. Configurar um sistema de lobby pronto para produção com fallbacks regionais, regras de matchmaking e handshakes de network driver confiáveis pode facilmente custar de 4 a 6 semanas de engenharia de backend dedicada.
É aqui que entra um game backend dedicado. O horizOn é um Backend-as-a-Service (BaaS) desenvolvido especificamente para criadores de jogos. Em vez de forçar você a depurar bibliotecas de socket específicas da Steam ou escrever wrappers complexos em C++, o horizOn oferece um SDK unificado para gerenciar lobbies, matchmaking e sessões de jogadores.
Ao migrar a sua lógica de matchmaking para o horizOn, você não precisa se preocupar com mapeamentos de network driver nos arquivos da sua engine. As sessões dos jogadores são intermediadas usando uma infraestrutura de servidores distribuída globalmente, oferecendo matchmaking instantâneo e relays protegidos contra DDoS nativamente.
Melhores Práticas para Multiplayer na Steam com Unreal Engine
Para construir um pipeline de multiplayer confiável que vá além dos testes de protótipo, siga estas melhores práticas:
- Transicione para Steam Sockets Cedo: Evite configurações legadas de P2P networking driver. Associe-se ao net driver moderno
SteamSocketspara aproveitar o roteamento do Steam Datagram Relay (SDR) e evitar falhas de NAT punch-through. - Aplique Chaves de Filtragem Customizadas: Ao testar no AppID 480, anexe um identificador de projeto altamente específico às suas configurações de sessão. Isso impede que o seu cliente tente se conectar a outros jogos que compartilham a sandbox.
- Trate com Elegância o Fallback para o NULL Subsystem: Garanta que o seu netcode possa recorrer ao subsistema
NULLquando a Steam não estiver rodando. Isso permite testes em LAN offline sem quebrar a lógica de conexão. - Otimize os Timeouts de Join de Sessão: Defina
ConnectionTimeoutem[ActiveNetDriver]para pelo menos 15.0 segundos. O handshake P2P da Steam pode levar vários segundos para ser concluído, especialmente se for roteado por meio de relays SDR globais.
Conclusão
Corrigir falhas de conexão de lobby da Steam na Unreal Engine 5 envolve alinhar as configurações da engine com os plugins de rede com os quais você compila o projeto. Ao mapear suas NetDriverDefinitions corretamente e sobrescrever os filtros de busca do Spacewar, você pode estabelecer conexões estáveis para os seus playtests.
Se você quiser evitar as dores de cabeça com infraestrutura de backend e focar inteiramente na gameplay, considere fazer a integração com um backend dedicado. Pronto para escalar o seu backend multiplayer? Experimente o horizOn gratuitamente ou confira a documentação da API.