Unreal Engine 5でSteam Online Subsystemがロビーに参加(Join Lobby)できない理由
要点まとめ
Unreal Engine 5でSteam Online Subsystemを使用する際、ロビーの検索結果には表示されるものの接続(Join)できないネットワーク構成エラーの解決策を解説しています。 `DefaultEngine.ini` における `SteamNetDriver` と `SteamSockets` の競合や、開発用AppID 480によるロビーの汚染と地域制限の解決方法を具体的に紹介します。 さらに、シングルPCテストでのポート競合の回避策を提示し、BackendサービスによるMatchmaking構築の効率化についても解説しています。
Unreal Engine 5のプロジェクトをパッケージ化し、2つの異なるSteamアカウントを使用してクライアントを起動すると、セッションの検索結果にホストされたロビーが表示されます。「Join」をクリックし、5秒ほど待っても……何も起こりません。画面がフリーズし、コンソールログに一般的なソケット警告が出力され、メインメニューに強制的に戻されてしまいます。
SteamのOnline Subsystemにおいてロビーへの接続(join lobby connections)に失敗する場合、Unreal Engine 5における最も厄介なネットワーク構成(network configuration)の問題に直面している可能性があります。これは、SteamのMatchmaking APIはロビーを正しく登録しているにもかかわらず、エンジンのネットワークドライバー(network driver)が低レイヤーの接続ハンドシェイク(connection handshake)を完了できないという、サイレントなエラーです。本ガイドでは、根本的なネットワークドライバーのアーキテクチャを解説し、よくある DefaultEngine.ini の不一致を特定し、AppID 480のサンドボックス制限を解決した上で、Unreal Engine 5で堅牢なSteam接続パイプラインを構成する方法を紹介します。
Epic Games vs. ValveのNetcodeアーキテクチャを理解する
接続が失敗する理由を理解するには、Unreal EngineのNetcodeレイヤーがSteamロビーの検索結果をどのようにネットワーク接続に変換しているかを把握する必要があります。ゲームコードから JoinSession を呼び出すと、 OnlineSubsystemSteam のセッションインターフェースがロビーを接続文字列に解決します。Steamの場合、この接続文字列はホストのユニークなSteam IDを表す steam.STEAM_ID(例:steam.76561198000000000)の形式になります。
Unreal Engineのネットワークドライバーファクトリ(GameNetDriver)はこの接続文字列を受け取り、スキームプレフィックス(steam.)をパースします。そして、[/Script/Engine.GameEngine] 配下にある DefaultEngine.ini の設定を確認し、Steam接続を処理するように構成されたクラスを探します。このマッピングが存在しないか、不一致があるか、あるいは正しいネット接続クラスがロードされていない場合、エンジンは IpNetDriver にフォールバックします。
IpNetDriver はSteam IDを解決できません。steam.76561198000000000 を標準のDNSホスト名やIPアドレスとして扱おうとし、解決に失敗してネットワークタイムアウトを発生させます。ネットワークドライバーの設定ミスや接続ハンドシェイクの失敗があると、Unreal Engine network driver timeoutsの解決に悩む開発者と同様のネットワークドライバータイムアウトに直面することになります。NetDriverの定義が接続スキームとどのように一致しているかを理解することが、この不一致を解消するための第一歩です。
SteamSockets vs. SteamNetDriverの競合
Unreal Engine 5におけるこの問題の最も一般的な原因は、レガシーな SteamNetDriver とモダンな SteamSockets プラグインの競合です。歴史的に、Unreal Engineはレガシーな SteamNetDriver(Valveの古いP2P APIベース)を使用していました。モダンなUE5プロジェクトでは、ValveのSteam Networking Sockets API(DDoS保護とルーティング最適化のためのSteam Datagram Relay(SDR)をサポート)を利用する SteamSockets プラグインを使用します。
多くの開発者は、C++でプロジェクトの依存モジュールに "SteamSockets" を追加するものの、構成ファイルのネットワークドライバーのクラス定義を更新するのを忘れてしまいます。あるいはその逆に、エンジンがSteam Socketsを初期化しようとしているにもかかわらず、レガシーなドライバークラスを指定してしまうこともあります。それぞれの構成方法の正しい設定を見ていきましょう。
レガシーなSteamNetDriverの構成
レガシーな OnlineSubsystemSteam ネットワークドライバーを使用している場合、DefaultEngine.ini で GameNetDriver をレガシーなクラスにマッピングする必要があります。
[/Script/Engine.GameEngine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
[/Script/OnlineSubsystemSteam.SteamNetDriver]
NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"
また、DefaultEngine.ini の [OnlineSubsystemSteam] カテゴリの下で bUseSteamNetworking=true が設定されていることを確認してください。
モダンなSteamSocketsの構成
.uproject ファイルで SteamSockets プラグインを有効にしている場合は、ドライバーと接続クラス名を変更する必要があります。クラス名とセクションヘッダーの変更にご注意ください。
[/Script/Engine.GameEngine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="SteamSockets.SteamSocketsNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
[/Script/SteamSockets.SteamSocketsNetDriver]
NetConnectionClassName="SteamSockets.SteamSocketsNetConnection"
Build.cs に SteamSockets を含むように構成しながら、INIファイルでレガシーな OnlineSubsystemSteam.SteamNetDriver の設定を使用していると、エンジンは誤ったネット接続クラスを初期化してしまいます。これにより、クライアントのネットワークハンドシェイクでホストのSteam IDを解決できなくなり、参加の試みがハングしてタイムアウトになります。
ビルド依存関係の検証
プロジェクトの .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"
});
}
}
Steam AppID 480サンドボックスの罠
設定が正しいにもかかわらず参加できない場合は、Steamのデベロッパー向けAppID 480のサンドボックス制限の罠にはまっている可能性があります。デフォルトでは、開発者はValveに商用アプリケーションを登録することなくSteamインテグレーションをテストするために、AppID 480(Spacewar)を使用します。しかし、AppID 480は世界中の何千もの開発者によって共有されています。
これにより、2つの異なる問題が発生します。
- Lobby Pollution: AppID 480でのセッション検索では、他の開発者のゲームがホストするロビーも返されます。クライアントがそれらのランダムなロビーに参加しようとすると、ゲームバージョンやビルドIDの不一致によって失敗します。
- Region Isolation: Steamのロビーは、pingを低く抑えるためにデフォルトで地域ごとの表示制限がかけられています。オフサイトのチームメンバー(例:ニューヨークとロンドン)とテストを行っている場合、検索の距離フィルターを変更しない限り、標準的なセッションクエリではお互いを発見したり接続したりすることはできません。
これらの制限を回避するには、C++でセッション検索設定を明示的に構成し、ワールドワイド(worldwide)な距離フィルターとカスタムクエリパラメータを使用する必要があります。
関連のないAppID 480のトラフィックをフィルタリングして除外しつつ、グローバルなSteamロビーをターゲットにするカスタムC++セッション検索クエリの記述方法は以下の通りです。
#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());
}
}
ホスト側のクライアントでセッションを作成する際、FOnlineSessionSettings::Settings マップに同じカスタムキー(値が MyTPSGame_v1.0.4 の GAME_VERSION_KEY)を追加していることを確認してください。これにより、検索クエリがご自身のゲームのロビーのみを返すようになり、AppID 480の汚染を完全に排除できます。最初のロビー接続をクリアした後は、接続直後にゲームプレイの状態を崩壊させる原因となる Unreal Engine RPC replication desyncs を引き起こすことなく、RPCが確実にレプリケートされるようにする必要もあります。
ポートバインディングとシングルマシンでのテスト競合
1台のPCで2つのSteamアカウントを使用し、ローカルでMultiplayerのテストを行う場合(Sandboxieを使用するか、コマンドラインスクリプトから起動した2つ目のコンパイル済みインスタンスを使用する場合など)、ポートバインディング(port binding)の競合が発生する可能性が高くなります。ホスト時、Unreal EngineのSteam Subsystemはローカルゲームサーバーの登録を試みます。DefaultEngine.ini の bInitServerOnClient=true 設定は、クライアントに対してSteamworksのゲームサーバーAPIを初期化するよう指示します。
同じマシン上の2つのインスタンスがどちらもデフォルトのSteam Query Port (27015) と Game Port (7777) にバインドしようとすると、2つ目のインスタンスはソケットを開くのに失敗します。結果として、2つ目のクライアントはロビーを検索して見つけることはできますが、インバウンドの接続ソケットを初期化できなくなります。
対策は以下の通りです。
- 2つ目のインスタンスのポートを変更する: ターミナルまたはバッチスクリプトから2つ目のゲームインスタンスを起動する際、コマンドラインパラメータに
-port=7778を追加して、強制的に別のポートにバインドさせます。 - クエリポートのオフセットを修正する:
[OnlineSubsystemSteam]の下で、クエリポートとゲームポートの構成を確認します。
[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480
bInitServerOnClient=true
bUseSteamNetworking=true
GameServerQueryPort=27015
同じローカルネットワーク上でテストする場合は、ルーターのファイアウォールがこれらのポートでのUDPトラフィックをブロックしていないことを確認してください。
モダンなゲームBackendによるMatchmakingの悩みの解消
Steamのini設定、距離フィルター、AppIDの制限と手動で格闘することは、インディーチームにとって膨大な時間の浪費になります。地域ごとのフォールバック、Matchmakingルール、信頼性の高いネットワークドライバーのハンドシェイクを備えた、プロダクション環境に対応したロビーシステムを構築するには、専任のBackendエンジニアリングで簡単に4〜6週間はかかってしまいます。
そこで、専用のゲームBackendの出番です。horizOnは、ゲーム開発者向けに特別に設計されたBackend-as-a-Service(BaaS)です。Steam特有のソケットライブラリのデバッグや、複雑なC++のラッパーモジュールの記述を強いられる代わりに、horizOnはロビー、Matchmaking、プレイヤーセッションを管理するための統合SDKを提供します。
Matchmakingのロジックを horizOn に移行すれば、エンジンファイル内のネットワークドライバーのマッピングを気にする必要はありません。プレイヤーセッションはグローバルに分散されたサーバーインフラストラクチャを介して仲介され、即座に機能するMatchmakingと、DDoS保護されたリレーを最初から利用できます。
Unreal EngineにおけるSteam Multiplayerのベストプラクティス
プロトタイプのテストを超えてスケールする、信頼性の高いMultiplayerパイプラインを構築するには、以下のベストプラクティスに従ってください。
- 早期にSteam Socketsへ移行する: レガシーなP2Pネットワークドライバー構成は避けてください。モダンな
SteamSocketsネットドライバーにバインドして、Steam Datagram Relay(SDR)ルーティングを活用し、NAT punch-throughの失敗を防ぎます。 - カスタムフィルタリングキーを適用する: AppID 480でテストする場合、セッション設定に非常に具体的なプロジェクト識別子を追加してください。これにより、クライアントがサンドボックスを共有している他のゲームに接続しようとするのを防げます。
- NULL Subsystemへのフォールバックを適切に処理する: Steamが実行されていない場合に、ネットコードが
NULLサブシステムにフォールバックできるようにします。これにより、接続ロジックを破損することなくオフラインLANテストが可能になります。 - セッション参加のタイムアウトを最適化する:
[ActiveNetDriver]の下のConnectionTimeoutを少なくとも15.0秒に設定します。SteamのP2Pハンドシェイクは、特にグローバルなSDRリレーを経由する場合、完了までに数秒かかることがあります。
結論
Unreal Engine 5におけるSteamロビー接続エラーの解消は、ビルド対象のネットワークプラグインとエンジンの構成を一致させることがすべてです。NetDriverDefinitions を正しくマッピングし、Spacewarの検索フィルターをオーバーライドすることで、プレイテスト用の安定した接続を確立できます。
バックエンドインフラストラクチャの悩みを回避し、ゲームプレイに完全に集中したい場合は、専用のBackendとの統合を検討してください。Multiplayer Backendをスケールさせる準備はできていますか?horizOnを無料で試すか、API docsをご覧ください。