يقوم إصدار Little Bee المتقدم حاليًا باستخراج منطق مزامنة الإطار في SDK، يرجى الاطلاع على المشروع https://github.com/dudu502/littlebee_libs هذا مثال على لعبة مزامنة الإطار، حيث يقوم بمزامنة مئات الكائنات وآلاف الحالات في اللعبة خلفية اللعبة هي لعبة إطلاق نار في ظل نظام كوكبي، وفيما يلي رابط الفيديو.
[شاهد تشغيل الفيديو (يوتيوب)]
[شاهد إعادة الفيديو (يوتيوب)]
[شاهد تشغيل الفيديو (بيليبيلي)]
[شاهد إعادة تشغيل الفيديو (بيليبيلي)]
مزامنة الإطار | مزامنة الدولة | |
---|---|---|
تناسق | يحدد مستوى التصميم الاتساق الحتمي | يمكن أن يضمن الاتساق |
عدد اللاعبين | دعم محدود للاعبين المتعددين | العديد من اللاعبين لديهم مزايا |
عبر منصة | بحاجة إلى النظر في اتساق عمليات النقطة العائمة | نظرًا لأن الحسابات الرئيسية تتم على الخادم، فلا توجد مشكلات عبر الأنظمة الأساسية. |
مكافحة الغش | من السهل الغش، ولكن يمكن تحسينها | يمكن أن تكون جيدة جدًا في منع الغش |
قطع الاتصال وإعادة الاتصال | من الصعب تنفيذه، لكنه ليس مستحيلا | ما عليك سوى إعادة إرسال الرسالة مرة واحدة، وهو أمر سهل التنفيذ |
متطلبات التشغيل | يمكن أن تتحقق على أكمل وجه | غير قادر على تحقيقه |
لعبة وقفة | سهل التنفيذ | ليس من السهل تنفيذها |
حجم نقل الشبكة | صغيرة نسبيا | كبيرة نسبيا |
صعوبة التطوير | معقدة نسبيا | بسيطة نسبيا |
ألعاب آر تي إس | مناسب | غير مناسب |
العاب قتال | مناسب | غير مناسب |
العاب موبا | مناسب | غير مناسب |
ألعاب MMO | غير مناسب | مناسب |
بعد فهم الصعوبات التي يجب التغلب عليها في عملية تطوير مزامنة الإطار، سنفكر بعد ذلك في اختيار طريقة تنفيذ أفضل أو إطار عمل تطوير. بما أن تطوير تزامن الإطار يتطلب فصل البيانات والأداء، إلى أي مدى يجب فصلهما؟ يمكن أيضًا وضع جزء حساب البيانات في موضوع منفصل. تتمثل ميزة كتابة المنطق بهذه الطريقة في أنها تسمح أيضًا للخادم بالعمل لتحقيق وظيفة إعادة تشغيل اللعبة بسرعة، وأعتقد أن ECS فقط هي التي يمكنها تحقيق هذا المستوى من الفصل. تعد مزامنة الإطارات بالإضافة إلى ECS شريكًا مثاليًا تمامًا.
أولاً، نحتاج إلى تقديم ECS ليست تقنية جديدة تمامًا، ولم تقترحها Unity لأول مرة. ظهر هذا المصطلح مبكرًا جدًا، وأصبح شائعًا فجأة في السنوات الأخيرة بسبب لعبة "Overwatch" من Blizzard. تم إنشاء أطر عمل الخادم والعميل الخاصة بـ "Overwatch" بالكامل استنادًا إلى ECS، وتتمتع بأداء ممتاز في ميكانيكا اللعبة والشبكة والعرض. بصراحة، ECS ليست مثل نمط التصميم، وقد تمت مناقشة جميع أنماط التصميم التي استخدمناها من قبل في إطار التصميم الموجه للكائنات، وECS ليست موجهة للكائنات. تحتوي الوحدة أيضًا على ECS. في الواقع، تعد مكونات Unity أيضًا نوعًا من ECS، لكنها ليست نقية بدرجة كافية. ECS مناسبة بشكل خاص للعب. هناك العديد من المتغيرات لـ ECS، وهنا ECS مع بعض التعديلات الطفيفة.
هذه إحدى ميزات ألعاب مزامنة الإطار. إذا كانت اللعبة تحتوي على نظام إعادة تشغيل، فيجب تنفيذ اللعبة من خلال مزامنة الإطار. يمكن أيضًا تسمية التشغيل بتسجيل الفيديو، ولكنه يختلف تمامًا عن تسجيل الفيديو. التشغيل باستخدام ملفات الفيديو حيث أن الناقل عادة ما يستهلك ملفًا ضخمًا، ولا يمكن تبديل النافذة أثناء عملية التشغيل، حيث يتم بسهولة سرقة مقاطع الفيديو وإساءة استخدامها بشكل ضار الجودة المعدلة والمضغوطة والمتدهورة، لذا فإن تشغيل الفيديو له عيب كبير. يمكن أن يؤدي التشغيل المتزامن مع الإطار إلى جعل الملف صغيرًا للغاية ولا يمكن العبث به. ويمكن للمستخدمين تبديل النوافذ حسب الرغبة أثناء عملية التشغيل. يمكن القول أن النظام الضروري لألعاب مزامنة الإطارات هو نظام إعادة التشغيل.
يوصى هنا باستخدام RevenantX/LiteNetLib. هذه المكتبة قوية جدًا وسهلة الاستخدام، وتوفر نقل UDP موثوقًا به، وهو ما أريده تمامًا. هناك العديد من بروتوكولات البيانات للاختيار من بينها للاتصال بالشبكة. أنا أستخدم بروتوكول دفق ثنائي عصامي. الوظائف الرئيسية هي التسلسل وإلغاء التسلسل. مثل هيكل PtRoom هذا:
//Template auto generator:[AutoGenPt] v1.0
//Creation time:2021/1/28 16:43:48
using System ;
using System . Collections ;
using System . Collections . Generic ;
namespace Net . Pt
{
public class PtRoom
{
public byte __tag__ { get ; private set ; }
public uint RoomId { get ; private set ; }
public byte Status { get ; private set ; }
public uint MapId { get ; private set ; }
public string RoomOwnerUserId { get ; private set ; }
public byte MaxPlayerCount { get ; private set ; }
public List < PtRoomPlayer > Players { get ; private set ; }
public PtRoom SetRoomId ( uint value ) { RoomId = value ; __tag__ |= 1 ; return this ; }
public PtRoom SetStatus ( byte value ) { Status = value ; __tag__ |= 2 ; return this ; }
public PtRoom SetMapId ( uint value ) { MapId = value ; __tag__ |= 4 ; return this ; }
public PtRoom SetRoomOwnerUserId ( string value ) { RoomOwnerUserId = value ; __tag__ |= 8 ; return this ; }
public PtRoom SetMaxPlayerCount ( byte value ) { MaxPlayerCount = value ; __tag__ |= 16 ; return this ; }
public PtRoom SetPlayers ( List < PtRoomPlayer > value ) { Players = value ; __tag__ |= 32 ; return this ; }
public bool HasRoomId ( ) { return ( __tag__ & 1 ) == 1 ; }
public bool HasStatus ( ) { return ( __tag__ & 2 ) == 2 ; }
public bool HasMapId ( ) { return ( __tag__ & 4 ) == 4 ; }
public bool HasRoomOwnerUserId ( ) { return ( __tag__ & 8 ) == 8 ; }
public bool HasMaxPlayerCount ( ) { return ( __tag__ & 16 ) == 16 ; }
public bool HasPlayers ( ) { return ( __tag__ & 32 ) == 32 ; }
public static byte [ ] Write ( PtRoom data )
{
using ( ByteBuffer buffer = new ByteBuffer ( ) )
{
buffer . WriteByte ( data . __tag__ ) ;
if ( data . HasRoomId ( ) ) buffer . WriteUInt32 ( data . RoomId ) ;
if ( data . HasStatus ( ) ) buffer . WriteByte ( data . Status ) ;
if ( data . HasMapId ( ) ) buffer . WriteUInt32 ( data . MapId ) ;
if ( data . HasRoomOwnerUserId ( ) ) buffer . WriteString ( data . RoomOwnerUserId ) ;
if ( data . HasMaxPlayerCount ( ) ) buffer . WriteByte ( data . MaxPlayerCount ) ;
if ( data . HasPlayers ( ) ) buffer . WriteCollection ( data . Players , ( element ) => PtRoomPlayer . Write ( element ) ) ;
return buffer . Getbuffer ( ) ;
}
}
public static PtRoom Read ( byte [ ] bytes )
{
using ( ByteBuffer buffer = new ByteBuffer ( bytes ) )
{
PtRoom data = new PtRoom ( ) ;
data . __tag__ = buffer . ReadByte ( ) ;
if ( data . HasRoomId ( ) ) data . RoomId = buffer . ReadUInt32 ( ) ;
if ( data . HasStatus ( ) ) data . Status = buffer . ReadByte ( ) ;
if ( data . HasMapId ( ) ) data . MapId = buffer . ReadUInt32 ( ) ;
if ( data . HasRoomOwnerUserId ( ) ) data . RoomOwnerUserId = buffer . ReadString ( ) ;
if ( data . HasMaxPlayerCount ( ) ) data . MaxPlayerCount = buffer . ReadByte ( ) ;
if ( data . HasPlayers ( ) ) data . Players = buffer . ReadCollection ( ( rBytes ) => PtRoomPlayer . Read ( rBytes ) ) ;
return data ;
}
}
}
}
هذا مشروع وحدة يعتمد على مزامنة الإطار
بعض الأدوات: أداة إنشاء هيكل Pt، أداة إنشاء Excel2Json، مشروع المكتبة العامة، مشروع مكتبة ServerDll
وثائق التصميم: وثائق التصميم التفصيلية، وثائق تصميم النموذج الأولي، جداول التكوين.
تصف الأشكال الثلاثة التالية استخدام محاكي تزامن الإطار في ثلاثة سيناريوهات مختلفة.
يوضح الشكل أدناه السلوك العام للعميل والخادم في نفس الوقت، ويتوافق منطق التشغيل أيضًا مع نفس السلوك.
توضح هذه الصورة منطق تنفيذ العميل والخادم في كل علامة منطقية. الجزء العلوي هو العميل. يتضمن المنطق الذي يحتاج العميل إلى تنفيذه جزء ECSR، والجزء السفلي هو جزء الخادم.
تصف الصورة الأخيرة كل إطار منطقي للتشغيل.
من خلال هذه الصور والأنواع المحددة من الألعاب، يمكننا إعداد نظام ومكون مخصص للتعامل مع المنطق ذي الصلة.
هذا مشروع مجموعة خدمات، بما في ذلك WebServer وGateServer وRoomServer وما إلى ذلك.