العودة إلى المدونة

لماذا لا يمكن لـ Steam Online Subsystem الخاص بك الانضمام إلى الـ Lobby في Unreal Engine 5

نُشر في 15 يونيو 2026
لماذا لا يمكن لـ Steam Online Subsystem الخاص بك الانضمام إلى الـ Lobby في Unreal Engine 5

باختصار

يستعرض هذا الدليل حلولاً لمشكلة تعذر الاتصال بالـ Lobbies عبر Steam Online Subsystem في Unreal Engine 5. يغطي المقال كيفية حل تعارضات التكوين بين SteamNetDriver القديم و SteamSockets الحديث وتعديل ملف DefaultEngine.ini. كما يتناول كيفية تجاوز قيود Sandbox الخاصة بـ AppID 480 وحظر منافذ الأجهزة المحلية لضمان إجراء اختبارات Multiplayer ناجحة.

تقوم بعمل Package لمشروعك في Unreal Engine 5، وتقوم بتشغيل حسابي Steam منفصلين، وتطلق الـ Client، لترى الـ Lobby المستضاف يظهر في نتائج الـ Session search. تضغط على "Join"، وتنتظر خمس ثوانٍ، و... لا يحدث شيء. تتجمد الشاشة، ويطبع سجل الكونسول (Console log) تحذير socket عام، ويتم إرجاعك إلى القائمة الرئيسية (Main Menu).

إذا كانت اتصالات Steam Online Subsystem لديك لا يمكنها الانضمام إلى الـ Lobby، فأنت تواجه واحدة من أكثر مشاكل إعدادات الشبكة (Network configuration) إحباطاً في Unreal Engine 5. إنه فشل صامت حيث تقوم Steam Matchmaking API بتسجيل الـ Lobby الخاص بك بشكل صحيح، ولكن الـ Network driver الخاص بالمحرك لا يمكنه إكمال الـ Handshake منخفض المستوى للاتصال. في هذا الدليل، سنقوم بتحليل البنية التحتية لـ Network driver الأساسية، وتحديد عدم التطابق الشائع في ملف DefaultEngine.ini، وحل قيود Sandbox الخاصة بـ AppID 480، ونوضح لك كيفية إعداد Steam Connection Pipeline قوي ولا يقهر في Unreal Engine 5.

فهم بنية الـ Netcode بين Epic Games و Valve

لفهم سبب فشل الانضمام، عليك فهم كيف تترجم طبقة الـ Netcode في Unreal Engine نتيجة البحث عن Steam Lobby إلى اتصال شبكة. عندما تستدعي JoinSession من كود اللعبة الخاص بك، تقوم واجهة الـ Session لـ OnlineSubsystemSteam بتحويل الـ Lobby إلى Connection string. بالنسبة لـ Steam، يتم تنسيق هذا الـ Connection string كـ steam.STEAM_ID (على سبيل المثال، steam.76561198000000000)، والذي يمثل الـ Steam ID الفريد للمستضيف.

يستقبل مصنع الـ Network driver في Unreal Engine (أي GameNetDriver) هذا الـ Connection string ويقوم بعمل Parse للبادئة الخاصة به (steam.). بعد ذلك، يتحقق من إعدادات DefaultEngine.ini الخاصة بك تحت القسم [/Script/Engine.GameEngine] للعثور على الفئة (Class) المهيأة للتعامل مع اتصالات Steam. إذا كان هذا التعيين (Mapping) مفقوداً، أو غير متطابق، أو إذا لم يتم تحميل فئة الاتصال الصحيحة، فإن المحرك يتراجع تلقائياً (Falls back) إلى 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 و routing optimization).

يضيف العديد من المطورين "SteamSockets" إلى الـ Dependency modules الخاصة بمشروعهم في C++ ولكنهم ينسون تحديث تعريفات فئة الـ Network driver في ملفات الإعدادات الخاصة بهم. أو على العكس، يقومون بتحديد فئات الـ Driver القديمة بينما يحاول المحرك تهيئة Steam Sockets. دعونا نلقي نظرة على الإعداد الصحيح لكل طريقة.

إعدادات SteamNetDriver القديمة (Legacy)

إذا كنت تستخدم الـ 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 الخاص بك، فسيقوم المحرك بتهيئة فئة اتصال شبكة خاطئة. هذا يمنع الـ Network handshake الخاص بالـ Client من حل الـ Steam ID الخاص بالمستضيف، مما يؤدي إلى تعليق محاولة الانضمام وحدوث 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

إذا كانت إعداداتك صحيحة ولكنك لا تزال غير قادر على الانضمام، فمن المحتمل أنك تقع ضحية لقيود الـ Sandbox لـ Steam Dev AppID 480. بشكل افتراضي، يستخدم المطورون AppID 480 (Spacewar) لاختبار تكامل Steam دون تسجيل تطبيق تجاري لدى Valve. ومع ذلك، فإن AppID 480 مشترك بين آلاف المطورين حول العالم.

يؤدي هذا إلى مشكلتين مختلفتين:

  1. Lobby Pollution (تلوث الـ Lobby): سيعود البحث عن الـ Session على AppID 480 بـ Lobbies مستضافة بواسطة ألعاب مطورين آخرين. عندما يحاول الـ Client الخاص بك الانضمام إلى Lobby عشوائي، سيفشل ذلك بسبب عدم تطابق إصدارات اللعبة أو الـ Build IDs.
  2. Region Isolation (عزل المناطق): تعتمد Steam Lobbies افتراضياً على الرؤية الإقليمية للحفاظ على Ping منخفض. إذا كنت تختبر مع زميل عمل في موقع آخر (على سبيل المثال، أحدهما في نيويورك والآخر في لندن)، فلن تجد استعلامات الـ Session القياسية بعضها البعض أو تتصل ببعضها ما لم يتم تعديل فلاتر مسافة البحث الخاصة بك.

لتجاوز هذه القيود، يجب عليك تهيئة إعدادات البحث عن الـ 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 على الـ Client المستضيف، تأكد من إلحاق نفس المفتاح المخصص (GAME_VERSION_KEY مع القيمة MyTPSGame_v1.0.4) بـخريطة FOnlineSessionSettings::Settings. يضمن هذا أن يعيد استعلام البحث الخاص بك Lobbies لعبتك فقط، مما يقضي على تلوث AppID 480 تماماً. بمجرد تجاوز اتصال الـ Lobby الأولي، يجب عليك أيضاً التأكد من أن الـ RPCs الخاصة بك تعمل بـ Replication موثوق دون التسبب في Unreal Engine RPC replication desyncs والتي قد تعطل الـ Gameplay states الخاصة بك فور الانضمام.

تعارضات حجز المنافذ (Port Bindings) والاختبار على جهاز واحد

إذا كنت تختبر الـ Multiplayer محلياً على جهاز كمبيوتر شخصي واحد باستخدام حسابي Steam (على سبيل المثال، باستخدام Sandboxie أو نسخة مترجمة ثانية يتم تشغيلها عبر سكربت سطر الأوامر)، فمن المحتمل أن تواجه تعارضات في الـ Port bindings. عند الاستضافة، يحاول الـ Steam Subsystem الخاص بـ Unreal Engine تسجيل سيرفر لعبة محلي. الإعداد bInitServerOnClient=true في DefaultEngine.ini يوجه الـ Client لتهيئة Steamworks game server API.

إذا حاولت نسختان على نفس الجهاز ربط (Bind) المنفذين الافتراضيين Steam Query Port (27015) و Game Port (7777)، فستفشل النسخة الثانية في فتح الـ Sockets الخاصة بها. ونتيجة لذلك، سيقوم الـ Client الثاني بالبحث عن الـ Lobby والعثور عليه، ولكنه لن يتمكن من تهيئة Socket الاتصال الوارد.

لإصلاح ذلك:

  1. تغيير المنفذ للنسخة الثانية: عند تشغيل نسخة اللعبة الثانية من الـ Terminal أو من سكربت Batch، قم بإجبارها على الارتباط بمنفذ مختلف عن طريق إلحاق -port=7778 ببارامترات سطر الأوامر.
  2. تصحيح إزاحات الـ Query Port: تحت القسم [OnlineSubsystemSteam]، تحقق من إعدادات الـ Query Port والـ Game Port الخاصة بك:
[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480
bInitServerOnClient=true
bUseSteamNetworking=true
GameServerQueryPort=27015

إذا كنت تختبر على نفس الشبكة المحلية، فتأكد من أن جدار الحماية (Firewall) لجهاز الراوتر الخاص بك لا يمنع حركة مرور UDP على هذه المنافذ.

القضاء على مشاكل الـ Matchmaking باستخدام Backend حديث للألعاب

إن المعاناة اليدوية مع إعدادات ini الخاصة بـ Steam، وفلاتر المسافة، وقيود AppID تستهلك وقتاً طائلاً من الفرق المستقلة (Indie teams). إن إعداد نظام Lobby جاهز للإنتاج (Production-ready) مع تراجعات إقليمية (Regional fallbacks)، وقواعد Matchmaking، ومصافحات اتصال (Network driver handshakes) موثوقة يمكن أن يكلف بسهولة من 4 إلى 6 أسابيع من هندسة الـ Backend المخصصة.

هنا يأتي دور Backend مخصص للألعاب. إن horizOn عبارة عن Backend-as-a-Service (BaaS) مصمم خصيصاً لمطوري الألعاب. بدلاً من إجبارك على تصحيح أخطاء مكتبات الـ Sockets الخاصة بـ Steam أو كتابة موديولات C++ معقدة، يوفر horizOn واجهة SDK موحدة لإدارة الـ Lobbies، الـ Matchmaking، وجلسات اللاعبين (Player sessions).

من خلال نقل منطق الـ Matchmaking الخاص بك إلى horizOn، لن تقلق بشأن تعيينات الـ Network driver في ملفات المحرك لديك. يتم التوسط في جلسات اللاعبين باستخدام بنية تحتية للسيرفرات موزعة عالمياً، مما يوفر Matchmaking فورياً و Relays محمية ضد DDoS مباشرة بمجرد التشغيل.

أفضل الممارسات للـ Multiplayer عبر Steam في Unreal Engine

لبناء Pipeline موثوق للـ Multiplayer يتوسع ليتجاوز مرحلة اختبار النماذج الأولية، اتبع أفضل الممارسات التالية:

  1. الانتقال إلى Steam Sockets مبكراً: تجنب إعدادات Network driver القديمة الخاصة بـ P2P. اربط بالـ Net driver الحديث لـ SteamSockets للاستفادة من توجيه Steam Datagram Relay (SDR) ومنع فشل NAT punch-through.
  2. تطبيق مفاتيح تصفية مخصصة: عند الاختبار على AppID 480، ألحق معرف مشروع محدد للغاية بإعدادات الـ Session الخاصة بك. هذا يمنع الـ Client الخاص بك من محاولة الاتصال بالألعاب الأخرى التي تشارك الـ Sandbox.
  3. التعامل بمرونة مع التراجع إلى NULL Subsystem: تأكد من أن الـ Netcode الخاص بك يمكنه التراجع (Fallback) إلى الـ NULL subsystem عندما لا يكون Steam قيد التشغيل. يسمح هذا باختبار LAN دون اتصال بالإنترنت ودون تعطيل منطق الاتصال.
  4. تحسين مهلات الانضمام إلى الجلسة (Session Join Timeouts): اضبط ConnectionTimeout تحت [ActiveNetDriver] ليكون 15.0 ثانية على الأقل. يمكن أن يستغرق الـ P2P Handshake الخاص بـ Steam بضع ثوانٍ ليكتمل، خاصةً إذا كان يتم توجيهه عبر Steam Datagram Relay (SDR) عالمي.

الخلاصة

إن إصلاح إخفاقات اتصال Steam Lobby في Unreal Engine 5 يدور بالكامل حول مطابقة إعدادات المحرك مع إضافات الشبكة التي تبني عليها. من خلال تعيين NetDriverDefinitions بشكل صحيح وتجاوز فلاتر بحث Spacewar، يمكنك إنشاء اتصالات مستقرة لاختبارات اللعب (Playtests).

إذا كنت ترغب في تخطي صداع البنية التحتية للـ Backend والتركيز بالكامل على الـ Gameplay، ففكر في دمجه مع Backend مخصص. هل أنت جاهز لتوسيع نطاق الـ Multiplayer backend الخاص بك؟ جرب horizOn مجاناً أو راجع API docs.


المصدر: Steam Online Subsystem can't join lobby