خلال الأسبوعين الماضيين ، كنت أعمل على محرك شبكة لعبتي. قبل ذلك ، لم أكن أعرف شيئًا عن تقنيات الشبكات في الألعاب ، لذلك قرأت العديد من المقالات وأجرت العديد من التجارب لفهم جميع المفاهيم وأتمكن من كتابة محرك الشبكة الخاص بي.
في هذا الدليل ، أود أن أشاطركم مختلف المفاهيم التي تحتاج إلى تعلمها قبل كتابة محرك اللعبة الخاص بك ، وكذلك أفضل الموارد والمقالات لتعلمها.
بشكل عام ، هناك نوعان رئيسيان من أبنية الشبكات: نظير إلى نظير وخادم العميل. في بنية نظير إلى نظير (p2p) ، يتم نقل البيانات بين أي زوج من اللاعبين المتصلين ، وفي بنية خادم العميل ، يتم نقل البيانات فقط بين المشغلات والخادم.
على الرغم من أن بنية نظير إلى نظير لا تزال تستخدم في بعض الألعاب ، إلا أن المعيار هو خادم العميل: إنه سهل التنفيذ ، ويتطلب عرض قناة أصغر ويسهل الحماية من الغش. لذلك ، في هذا الدليل ، سنركز على بنية خادم العميل.
على وجه الخصوص ، نحن مهتمون للغاية بالخوادم الاستبدادية: في مثل هذه الأنظمة ، يكون الخادم دائمًا على حق. على سبيل المثال ، إذا اعتقد اللاعب أنه في الإحداثيات (10 ، 5) ، ويخبره الخادم أنه في (5 ، 3) ، فيجب على العميل استبدال مركزه بالوظيفة التي يرسلها الخادم ، وليس العكس. باستخدام خوادم استبدادية يجعل التعرف على الغشاش أسهل.
هناك ثلاثة مكونات رئيسية لأنظمة شبكات الألعاب:
- بروتوكول النقل: كيف يتم نقل البيانات بين العملاء والخادم.
- بروتوكول التطبيق: ما يتم نقله من العملاء إلى الخادم ومن الخادم إلى العملاء وبأي تنسيق.
- منطق التطبيق: كيف يتم استخدام البيانات المرسلة لتحديث حالة العملاء والخادم.
من المهم للغاية فهم دور كل جزء والصعوبات المرتبطة به.
بروتوكول النقل
الخطوة الأولى هي اختيار بروتوكول لنقل البيانات بين الخادم والعملاء. هناك بروتوكولا إنترنت لهذا:
TCP و
UDP . ولكن يمكنك إنشاء بروتوكول النقل الخاص بك بناءً على أحدها أو استخدام المكتبة التي يتم استخدامها فيها.
مقارنة TCP و UDP
كلا TCP و UDP تستند إلى
IP . يسمح لك IP بنقل الحزمة من المصدر إلى المستلم ، لكنه لا يضمن وصول الحزمة المرسلة عاجلاً أو آجلاً إلى المستلم ، وأنها ستصل إليها مرة واحدة على الأقل وأن تسلسل الحزم سيصل بالترتيب الصحيح. علاوة على ذلك ، قد تحتوي الحزمة فقط على حجم بيانات محدود محدد بواسطة قيمة
MTU .
UDP هي مجرد طبقة رقيقة فوق IP. لذلك ، لديه نفس القيود. في المقابل ، يحتوي TCP على العديد من الميزات. يوفر اتصالاً موثوقاً ومرتباً بين عقدتين مع التحقق من الخطأ. لذلك ، بروتوكول TCP مناسب جدًا ويستخدم في العديد من البروتوكولات الأخرى ، على سبيل المثال ، في
HTTP و
FTP و
SMTP . لكن كل هذه الميزات تأتي بسعر:
تأخير .
لفهم لماذا يمكن أن تتسبب هذه الوظائف في تأخير ، تحتاج إلى فهم كيفية عمل TCP. عندما تقوم العقدة المرسلة بإعادة توجيه الحزمة إلى عقدة الاستلام ، تتوقع تلقي إقرار (ACK). إذا لم يتلقها بعد وقت معين (بسبب فقد الرزمة أو التأكيد ، أو لأسباب أخرى) ، فإنه يعيد إرسال الرزمة. علاوة على ذلك ، يضمن TCP استلام الحزم بالترتيب الصحيح ، وبالتالي ، إلى أن يتم استلام الحزمة المفقودة ، لا يمكن معالجة جميع الرزم الأخرى ، حتى لو كانت قد استلمت بالفعل من قبل عقدة الاستقبال.
ولكن كما تعلمون ، فإن التأخير في الألعاب متعددة اللاعبين مهم للغاية ، خاصة في الأنواع النشطة مثل FPS. هذا هو السبب في استخدام العديد من الألعاب UDP مع بروتوكول الخاصة بهم.
يمكن أن يكون البروتوكول الأصلي المستند إلى UDP أكثر كفاءة من TCP لأسباب مختلفة. على سبيل المثال ، قد يضع علامة على بعض الحزم على أنها موثوق بها والبعض الآخر غير موثوق به. لذلك ، لا يهتم إذا وصلت الحزمة غير الموثوق بها إلى المتلقي. أو يمكنها معالجة عدة تدفقات بيانات حتى لا تؤدي الحزمة المفقودة في قطار واحد إلى إبطاء التدفقات المتبقية. على سبيل المثال ، قد يكون هناك دفق لإدخال اللاعب ودفق آخر لرسائل الدردشة. إذا فقدت رسالة دردشة ليست بيانات عاجلة ، فلن تؤدي إلى إبطاء الإدخال ، وهو أمر عاجل. أو ، يمكن أن ينفذ بروتوكول الملكية الموثوقية بشكل مختلف عن بروتوكول TCP حتى يكون أكثر فعالية في ألعاب الفيديو.
لذلك ، إذا كان TCP هراء للغاية ، فسنقوم بإنشاء بروتوكول النقل الخاص بنا على أساس UDP؟
كل شيء أكثر تعقيدًا قليلاً. على الرغم من أن TCP شبه مثالي للأنظمة الشبكية للألعاب ، إلا أنه يمكن أن يعمل بشكل جيد في لعبتك ويوفر وقتك الثمين. على سبيل المثال ، قد لا يمثل التأخير مشكلة في لعبة قائمة على الدور أو لعبة لا يمكن لعبها إلا على شبكات LAN ، حيث يكون هناك تأخير أقل بكثير وفقدان للحزم من الإنترنت.
تستخدم العديد من الألعاب الناجحة ، بما في ذلك World of Warcraft و Minecraft و Terraria ، TCP. ومع ذلك ، تستخدم معظم FPS البروتوكولات المستندة إلى UDP ، لذلك سنتحدث أكثر عنها أدناه.
إذا قررت استخدام TCP ، فتأكد
من تعطيل
خوارزمية Nagle ، لأنه يقوم بتخزين الحزم قبل الإرسال ، مما يعني أنه يزيد من التأخير.
لمعرفة المزيد حول الاختلافات بين UDP و TCP في سياق الألعاب متعددة اللاعبين ، يمكنك قراءة المقال بواسطة Glenn Fiedler
UDP vs. وTCP .
البروتوكول الخاص
لذلك ، تريد إنشاء بروتوكول النقل الخاص بك ، ولكن لا تعرف من أين تبدأ؟ أنت محظوظ ، لأن غلين فيدلر كتب مقالتين رائعتين حول هذا الموضوع. سوف تجد العديد من الأفكار الذكية فيها.
المقالة الأولى ، "
Network for Game Programmers 2008" ، أبسط من المقال الثاني ، "
بناء شبكة لعبة Game 2016". أنصحك أن تبدأ مع كبار السن.
ضع في اعتبارك أن Glenn Fiedler هو مؤيد كبير لاستخدام بروتوكول UDP الخاص به. وبعد قراءة مقالاته ، ستحصل بالتأكيد على رأيه بأن برنامج التعاون الفني له عيوب خطيرة في ألعاب الفيديو ، وتريد تطبيق البروتوكول الخاص بك.
ولكن إذا كنت جديدًا على الشبكات ، فافعل ذلك بنفسك واستخدم TCP أو مكتبة. لتنفيذ بروتوكول النقل الخاص بك بنجاح ، عليك أولاً أن تتعلم الكثير.
مكتبات الشبكات
إذا كنت بحاجة إلى شيء أكثر كفاءة من برنامج التعاون الفني ، لكنك لا تريد أن تهتم بتنفيذ البروتوكول الخاص بك والاطلاع على العديد من التفاصيل ، يمكنك استخدام مكتبة الشبكة. هناك الكثير منهم:
لم أجربهم جميعًا ، لكنني أفضل ENet ، لأنه سهل الاستخدام وموثوق به. بالإضافة إلى ذلك ، لديها وثائق واضحة وبرنامج تعليمي للمبتدئين.
بروتوكول النقل: الخاتمة
للتلخيص: يوجد بروتوكولا نقل رئيسيان: TCP و UDP. يحتوي TCP على العديد من الميزات المفيدة: الموثوقية ، طلب الحزمة ، اكتشاف الأخطاء. لا يحتوي UDP على كل هذا ، لكن TCP ، بحكم طبيعته ، زاد من التأخير غير المقبول لبعض الألعاب. وهذا هو ، لضمان انخفاض زمن الوصول ، يمكنك إنشاء بروتوكول يستند إلى UDP الخاص بك أو استخدام مكتبة تنفذ بروتوكول نقل UDP ومكيفة لألعاب الفيديو متعددة اللاعبين.
يعتمد الاختيار بين TCP و UDP والمكتبة على عدة عوامل. أولاً ، من احتياجات اللعبة: هل تحتاج إلى زمن اختفاء منخفض؟ ثانياً ، من متطلبات بروتوكول التطبيق: هل يحتاج إلى بروتوكول موثوق؟ كما سنرى في الجزء التالي ، يمكنك إنشاء بروتوكول تطبيق يكون البروتوكول غير الموثوق به مناسبًا تمامًا. أخيرًا ، يجب أيضًا أن تأخذ في الاعتبار تجربة مطور محرك الشبكة.
لدي نصيحتين:
- تعظيم بروتوكول النقل من بقية التطبيق بحيث يمكن استبداله بسهولة دون إعادة كتابة الكود بأكمله.
- لا تفعل الأمثل سابق لأوانه. إذا لم تكن متخصصًا في الشبكة ولم تكن متأكدًا من حاجتك لبروتوكول النقل المستند إلى UDP ، فيمكنك البدء بـ TCP أو مكتبة توفر الموثوقية ، ثم اختبار الأداء وقياسه. إذا كنت تواجه مشكلات وكنت متأكدًا من أن السبب يكمن في بروتوكول النقل ، فربما حان الوقت لإنشاء بروتوكول النقل الخاص بك.
في نهاية هذا الجزء ، أنصحك بقراءة
مقدمة براين هوك في
برمجة الألعاب متعددة اللاعبين ، والتي تغطي العديد من الموضوعات التي تمت مناقشتها هنا.
بروتوكول التطبيق
الآن وبعد أن أصبح بإمكاننا تبادل البيانات بين العملاء والخادم ، نحتاج إلى تحديد البيانات التي سيتم نقلها وبأي تنسيق.
المخطط الكلاسيكي هو أن العملاء يرسلون المدخلات أو الإجراءات إلى الخادم ، وأن يرسل الخادم حالة اللعبة الحالية إلى العملاء.
لا يرسل الخادم حالة كاملة ، ولكن تمت تصفيته مع كيانات بجوار المشغل. يفعل هذا لثلاثة أسباب. أولاً ، قد تكون الحالة الكلية كبيرة جدًا بالنسبة للإرسال عالي التردد. ثانياً ، يهتم العملاء بشكل أساسي بالبيانات المرئية والمسموعة ، لأن معظم منطق اللعبة يتم محاكاته على خادم اللعبة. ثالثًا ، لا يحتاج اللاعب في بعض الألعاب إلى معرفة بيانات معينة ، على سبيل المثال ، وضع الخصم على الطرف الآخر من الخريطة ، لأنه بخلاف ذلك يمكنه شم حزم الحزم ومعرفة مكان الانتقال لقتله.
التسلسل
الخطوة الأولى هي تحويل البيانات التي نريد إرسالها (حالة الإدخال أو اللعبة) إلى تنسيق مناسب للإرسال. وتسمى هذه العملية
التسلسل .
يتبادر إلى الذهن على الفور استخدام تنسيق قابل للقراءة من قبل الإنسان ، مثل JSON أو XML. لكنه سيكون غير فعال تمامًا وسيستحوذ عبثًا على معظم القناة.
بدلاً من ذلك ، يوصى باستخدام تنسيق ثنائي مضغوط بدرجة أكبر. وهذا يعني أن الحزم سوف تحتوي على عدد قليل من وحدات البايت. هنا تحتاج إلى النظر في مشكلة
ترتيب البايت ، والتي قد تختلف على أجهزة كمبيوتر مختلفة.
يمكنك استخدام مكتبة لتسلسل البيانات ، على سبيل المثال:
فقط تأكد من أن المكتبة تنشئ أرشيفات محمولة وتتولى أمر البايت.
قد يكون الحل المستقل تطبيقًا مستقلاً ، فهو ليس معقدًا بشكل خاص ، خاصة إذا كنت تستخدم نهجًا موجهًا للبيانات في التعليمات البرمجية. بالإضافة إلى ذلك ، سيتيح لك إجراء تحسينات غير ممكنة دائمًا عند استخدام المكتبة.
كتب غلين فيدلر مقالتين حول التسلسل:
حزم القراءة والكتابة واستراتيجيات التسلسل .
ضغط
يقتصر مقدار البيانات المنقولة بين العملاء والخادم على النطاق الترددي للقناة. يسمح لك ضغط البيانات بنقل المزيد من البيانات في كل لقطة ، أو زيادة معدل التحديث ، أو ببساطة تقليل متطلبات القناة.
التعبئة قليلا
الأسلوب الأول هو التعبئة بت. تتمثل في استخدام عدد البتات الضروري لوصف القيمة المطلوبة بالضبط. على سبيل المثال ، إذا كان لديك تعداد يمكن أن يحتوي على 16 قيمة مختلفة ، فبدلاً من البايت بأكمله (8 بت) ، يمكنك استخدام 4 بتات فقط.
يشرح Glenn Fiedler كيفية تنفيذ ذلك في الجزء الثاني من مقالة
حزم القراءة والكتابة .
تعمل بتات التعبئة بشكل جيد مع أخذ العينات ، والتي ستكون موضوع القسم التالي.
أخذ العينات
التقدير هو أسلوب ضغط ضياع يستخدم مجموعة فرعية فقط من القيم المحتملة لتشفير قيمة. أسهل طريقة لتنفيذ التقدير هي تقريب أرقام الفاصلة العائمة.
يوضح Glenn Fiedler (مرة أخرى!) كيفية تطبيق أخذ العينات في الممارسة العملية في مقاله
"ضغط اللقطة" .
خوارزميات الضغط
ستكون التقنية التالية خوارزميات ضغط ضياع.
هنا ، في رأيي ، الخوارزميات الثلاثة الأكثر إثارة للاهتمام التي تحتاج إلى معرفتها:
- ترميز Huffman برمز محسوب مسبقًا سريع للغاية ويمكن أن يعطي نتائج جيدة. تم استخدامه لضغط الحزم في محرك شبكة Quake3.
- zlib هي خوارزمية ضغط للأغراض العامة لا تزيد مطلقًا من كمية البيانات. كما يتبين هنا ، تم استخدامه في العديد من التطبيقات. قد يكون من الضروري تحديث الحالات. ولكن يمكن أن يكون مفيدًا إذا كنت بحاجة إلى إرسال أصول أو نصوص طويلة أو إغاثة للعملاء من الخادم.
- قد تكون نسخ أطوال السلسلة أبسط خوارزمية ضغط ، لكنها فعالة للغاية بالنسبة لأنواع معينة من البيانات ، ويمكن استخدامها كخطوة قبل المعالجة قبل zlib. إنه مناسب بشكل خاص لضغط التضاريس التي تتكون من البلاط أو الفوكسل ، والتي يتكرر فيها العديد من العناصر المجاورة.
ضغط دلتا
أحدث تقنية ضغط هي ضغط دلتا. يكمن في حقيقة أنه يتم نقل الاختلافات فقط بين حالة اللعبة الحالية وآخر حالة يتلقاها العميل.
تم استخدامه لأول مرة في محرك شبكة Quake3. فيما يلي مقالتان توضحان كيفية استخدامه:
كما استخدمها غلين فيدلر في الجزء الثاني من مقالته حول
Snapshot Compression .
التشفير
بالإضافة إلى ذلك ، قد تحتاج إلى تشفير نقل المعلومات بين العملاء والخادم. هناك عدة أسباب لهذا:
- الخصوصية / السرية: لا يمكن قراءة الرسائل إلا بواسطة المستلم ، ولا يمكن لأي شخص آخر يستنشق الشبكة قراءتها.
- المصادقة: يجب على الشخص الذي يريد أن يلعب دور اللاعب معرفة مفتاحه.
- منع الغش: سيكون من الصعب للغاية على اللاعبين الضارين إنشاء حزم الغش الخاصة بهم ، سيتعين عليهم إعادة إنتاج نظام التشفير والعثور على المفتاح (الذي يتغير مع كل اتصال).
أوصي بشدة باستخدام المكتبة لهذا الغرض. أقترح استخدام
libsodium لأنه بسيط بشكل خاص ويحتوي على برامج تعليمية رائعة. هناك أهمية خاصة لبرنامج تعليمي
لتبادل المفاتيح ، والذي يسمح لك بإنشاء مفاتيح جديدة مع كل اتصال جديد.
بروتوكول التطبيق: الخاتمة
سننتهي مع بروتوكول التطبيق. أعتقد أن الضغط اختياري تمامًا وأن قرار استخدامه يعتمد فقط على اللعبة وعلى النطاق الترددي المطلوب. التشفير ، في رأيي ، إلزامي ، ولكن في النموذج الأولي الأول يمكنك الاستغناء عنه.
منطق التطبيق
نحن الآن قادرون على تحديث الحالة في العميل ، لكننا قد نواجه مشاكل مع التأخير. بعد الدخول ، يحتاج اللاعب إلى انتظار تحديث حالة اللعبة من الخادم لمعرفة التأثير الذي أحدثه على العالم.
علاوة على ذلك ، بين تحديثات الدولتين ، فإن العالم ثابت تمامًا. إذا كان معدل تحديث الحالات منخفضًا ، فستكون الحركات ضعيفة جدًا.
هناك العديد من التقنيات للحد من تأثير هذه المشكلة ، وفي القسم التالي سأتحدث عنها.
تأخير تقنيات تجانس
تمت مناقشة جميع التقنيات الموضحة في هذا القسم بالتفصيل في سلسلة
Multiplayer Fast-Paced من إنتاج Gabriel Gambetta. أوصي بشدة بقراءة هذه السلسلة الرائعة من المقالات. كما أنه يحتوي على عرض تفاعلي يتيح لك معرفة كيفية عمل هذه التقنيات في الممارسة العملية.
الأسلوب الأول هو تطبيق الإدخال مباشرة ، دون انتظار استجابة من الخادم. وهذا ما يسمى
التنبؤ من جانب العميل . ومع ذلك ، عندما يتلقى العميل التحديث من الخادم ، يجب عليه التأكد من صحة توقعاته. إذا لم يكن الأمر كذلك ، فعليه فقط تغيير حالته وفقًا للحالة الواردة من الخادم ، لأن الخادم استبدادي. تم استخدام هذه التقنية لأول مرة في الزلزال. يمكنك قراءة المزيد حول هذا الموضوع في مقالة
Quake Engine code review بواسطة Fabien Sanglar [
الترجمة إلى Habré].
يتم استخدام المجموعة الثانية من التقنيات لتسهيل حركة الكيانات الأخرى بين تحديثات الحالة. هناك طريقتان لحل هذه المشكلة: الاستيفاء والاستقراء. في حالة الاستيفاء ، تؤخذ الدولتان الأخيرتان ويظهر الانتقال من واحدة إلى أخرى. عيبها هو أنها تسبب جزءًا صغيرًا من التأخير ، لأن العميل يرى دائمًا ما حدث في الماضي. تتنبأ عملية الاستقراء بالمكان الذي يجب أن تستند إليه الكيانات الآن على الحالة الأخيرة التي تلقاها العميل. عيوبه هي أنه إذا غير الكيان اتجاه الحركة تمامًا ، فسيكون هناك خطأ كبير بين التوقعات والموقع الحقيقي.
الأسلوب الأخير والأكثر تقدمًا ، والمفيد فقط في FPS هو
التعويض المتأخر . عند استخدام تعويض التأخير ، يأخذ الخادم في الاعتبار تأخير العميل عند إطلاقه على الهدف. على سبيل المثال ، إذا أكمل اللاعب لقطة للرأس على شاشته ، ولكن في الحقيقة كان هدفه يقع في مكان آخر بسبب التأخير ، فسيكون من غير الشرير حرمان اللاعب من الحق في القتل بسبب التأخير. لذلك ، يعيد الخادم الوقت إلى اللحظة التي أطلق فيها اللاعب محاكاة ما رآه اللاعب على شاشته والتحقق من التصادم بين تسديدته والهدف.
جلين فيدلر (كما هو الحال دائمًا!) كتب مقالة في 2004 في
فيزياء الشبكات (2004) ، والتي وضعت الأساس لمزامنة المحاكاة الفيزيائية بين الخادم والعميل. في عام 2014 ، كتب سلسلة جديدة من مقالات
فيزياء الشبكات وصفت تقنيات أخرى لمزامنة محاكاة الفيزياء.
wiki Valve ,
Source Multiplayer Networking Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization .
.
: . , .
: //. , . .
:
نوصي بتطبيق طريقة لمحاكاة التأخير الكبير ومعدلات التحديث المنخفضة حتى تتمكن من اختبار سلوك لعبتك في ظروف سيئة ، حتى عندما يكون العميل والخادم يعملان على نفس الكمبيوتر. هذا سوف يبسط إلى حد كبير تنفيذ تقنيات تجانس التأخير.موارد مفيدة أخرى
إذا كنت ترغب في استكشاف موارد أخرى في نماذج الشبكة ، يمكنك العثور عليها هنا: