قم باستخراج الجزء الأساسي لمزامنة الإطار من مشروع مزامنة الإطار https://github.com/dudu502/LittleBee في SDK ويستند SDK هذا إلى netstandard2.0. توفير أجزاء SDK للخادم والعميل. حاليًا، تستخدم حالات التعليمات البرمجية الرئيسية الوحدة لكتابة العميل، وبرنامج وحدة التحكم dotnetcore هو الخادم.
Docs/
Diagrams/
: وثيقة الوصف.Protocols/
: أدوات البروتوكول وتكويناته.Engine/
: دليل SDK.Client/
: مكتبة لمشاريع العميل، تشير إلى المشروع المشترك، بناءً على مشروع netstandard2.0.Common/
: المكتبة المرجعية الأساسية لمشاريع العميل والخادم، بناءً على مشروع netstandard2.0.Server/
: مكتبة تستخدم من قبل المشاريع من جانب الخادم، تشير إلى المشروع المشترك، بناءً على مشروع netstandard2.0.Examples/
: مشاريع الحالة.Clients/
: يوجد حاليًا مشروع حالة تم تطويره باستخدام Unity.Servers/
: حاليًا برنامج وحدة تحكم netcore. ابحث عن فئة السياق في SDK. هذه الفئة مهمة جدًا لإطار العمل. يتم استخدام هذه الفئة كمدخل للبرنامج في كل من العميل والخادم استخدامات API مماثلة. الاسم الافتراضي للسياق مقسم إلى خادم وعميل. عند استخدامه، حدد نوع العميل أو الخادم وفقًا لاحتياجاتك لإنشاء مثيل السياق.
public const string CLIENT = "client" ;
public const string SERVER = "server" ;
قم بتمرير الاسم في المنشئ لتحديد السياق المقابل:
Context clientContext = new Context ( Context . CLIENT ) ;
Context serverContext = new Context ( Context . SERVER ) ;
طريقة الاستحواذ المقابلة:
Context context = Context . Retrieve ( name ) ;
يمكن الحصول على كائن السياق من خلال الطريقة المذكورة أعلاه في SDK أو في لعبة مخصصة. يمكنك بعد ذلك بسهولة الحصول على كائنات وبيانات أخرى من خلال كائن السياق.
هذا هو الرسم التخطيطي لفئة السياق الذي يحتوي على جميع مداخل الوحدة الوظيفية التي يوفرها SDK للمطورين.
classDiagram
فئة إينتووركسيرفر {
+إرسال (IPEndPoint ep، معرف رسالة قصيرة، بيانات بايت [])
+إرسال (معرف العميل int، معرف الرسالة القصيرة، بيانات البايت [])
+ إرسال (int [] معرفات العميل، معرف الرسالة القصيرة، بيانات البايت [])
+إرسال (معرف الرسالة القصيرة، بيانات البايت [])
+ تشغيل (منفذ كثافة العمليات)
+int GetActivePort()
}
فئة إينتوورككلينت {
+إرسال (معرف رسالة قصيرة، بيانات البايت [])
+اتصال()
+ الاتصال (سلسلة IP، منفذ int، مفتاح السلسلة)
+إغلاق()
إنت جيتاكتيفبورت ()
}
وحدة تحكم محاكاة الطبقة {
+GetFrameMsLength()
+GetFrameLerp()
+UpdateFrameMsLength (العامل العائم)
+ إنشاء محاكاة (محاكاة sim، ISimulativeBehaviour[] bhs)
+Start(DateTime sTime,int hist_kf_cout,تقدم الإجراء<float>,عداء الإجراء)
+إيقاف()
+الحصول على المحاكاة ()
+التخلص من المحاكاة ()
}
محاكاة الطبقة {
+ابدأ()
+ احصل على السلوك ()
+ يحتوي على السلوك(ISimulativeBehaviour به)
+إضافة سلوك (ISimulativeBehaviour beh)
+ إزالة السلوك (ISimulativeBehaviour beh)
+تشغيل()
}
فئة ISimulativeBehaviour {
+ابدأ()
+تحديث()
+إيقاف()
}
سياق الفئة {
+ خادم INetworkServer
+ عميل INetworkClient
+ مسجل Ilogger
+اسم السلسلة
+ السياق (اسم السلسلة، عميل INetworkClient، مسجل ILogger)
+ السياق (اسم السلسلة، خادم INetworkServer، مسجل ILogger)
+ السياق (اسم السلسلة، مسجل ILogger)
+ استرداد ثابت (اسم السلسلة)
+Context SetSimulationController (وحدة تحكم SimulationController)
+SimulationController GetSimulationController()
+GetMeta(اسم السلسلة،القيمة الافتراضية للسلسلة)
+SetMeta(اسم السلسلة، قيمة السلسلة)
+Context SetModule (وحدة AbstractModule)
+M GetModule<M>()
+Context RemoveModule (نوع النوع)
}
الأنواع المضمنة المحددة في contextMetaId هي كما يلي. سيتم استخدام هذه الأنواع داخليًا في SDK. يمكن للمستخدمين أيضًا تخزين الأنواع المخصصة وقراءتها من خلال context.SetMeta وContext.GetMeta.
public sealed class ContextMetaId
{
public const string USER_ID = "user_id" ;
public const string SERVER_ADDRESS = "server_address" ;
public const string MAX_CONNECTION_COUNT = "max_connection_count" ;
public const string ROOM_MODULE_FULL_PATH = "room_module_full_path" ;
public const string STANDALONE_MODE_PORT = "standalone_mode_port" ;
public const string GATE_SERVER_PORT = "gate_server_port" ;
public const string SELECTED_ROOM_MAP_ID = "selected_room_map_id" ;
public const string PERSISTENT_DATA_PATH = "persistent_data_path" ;
}
ينقسم الخادم إلى جزأين: البوابة والمعركة. تهدف خدمة البوابة إلى تزويد المستخدمين بخدمة الردهة والسماح للمستخدمين بتسجيل الدخول. كل مستخدم لديه معرف مستخدم مختلف حاليًا مع سلاسل مختلفة للمشروع الرسمي يجب أن يكون الدليل في قاعدة البيانات للتأكد من أن معرف كل مستخدم فريد. توفر خدمة البوابة هذه للمستخدمين الخدمات المتعلقة بالفريق مثل إنشاء الغرف، والانضمام إلى الغرف، ومغادرة الغرف، وما إلى ذلك. عندما يبدأ عدة مستخدمين معركة في الغرفة، ستبدأ خدمة البوابة عملية جديدة لخدمة المعركة وتخطر هؤلاء المستخدمين بالدخول إلى خدمة المعركة. ستفتح كل معركة عملية معركة جديدة. في خدمة Battle، يتم تنفيذ خدمة مزامنة الإطارات الرئيسية لجميع المستخدمين.
فيما يلي مثال للتعليمات البرمجية لخادم البوابة:
const string TAG = "gate-room" ;
static void Main ( string [ ] args )
{
// 使用Context.SERVER 构造Context
Context context = new Context ( Context . SERVER , new LiteNetworkServer ( TAG ) , new DefaultConsoleLogger ( TAG ) )
. SetMeta ( ContextMetaId . ROOM_MODULE_FULL_PATH , "battle_dll_path.dll" ) // the battle project's build path.
. SetMeta ( ContextMetaId . MAX_CONNECTION_COUNT , "16" )
. SetMeta ( ContextMetaId . SERVER_ADDRESS , "127.0.0.1" )
. SetModule ( new RoomModule ( ) ) ;
context . Server . Run ( 9030 ) ;
Console . ReadLine ( ) ;
}
التالي هو حالة مشروع المعركة:
static void Main ( string [ ] args )
{
string key = "SomeConnectionKey" ;
int port = 50000 ;
uint mapId = 1 ;
ushort playerNumber = 100 ;
int gsPort = 9030 ;
// 一些从Gate服务中传入的参数
if ( args . Length > 0 )
{
if ( Array . IndexOf ( args , "-key" ) > - 1 ) key = args [ Array . IndexOf ( args , "-key" ) + 1 ] ;
if ( Array . IndexOf ( args , "-port" ) > - 1 ) port = Convert . ToInt32 ( args [ Array . IndexOf ( args , "-port" ) + 1 ] ) ;
if ( Array . IndexOf ( args , "-mapId" ) > - 1 ) mapId = Convert . ToUInt32 ( args [ Array . IndexOf ( args , "-mapId" ) + 1 ] ) ;
if ( Array . IndexOf ( args , "-playernumber" ) > - 1 ) playerNumber = Convert . ToUInt16 ( args [ Array . IndexOf ( args , "-playernumber" ) + 1 ] ) ;
if ( Array . IndexOf ( args , "-gsPort" ) > - 1 ) gsPort = Convert . ToInt32 ( args [ Array . IndexOf ( args , "-gsPort" ) + 1 ] ) ;
}
Context context = new Context ( Context . SERVER , new LiteNetworkServer ( key ) , new DefaultConsoleLogger ( key ) )
. SetMeta ( ContextMetaId . MAX_CONNECTION_COUNT , playerNumber . ToString ( ) )
. SetMeta ( ContextMetaId . SELECTED_ROOM_MAP_ID , mapId . ToString ( ) )
. SetMeta ( ContextMetaId . GATE_SERVER_PORT , gsPort . ToString ( ) )
. SetModule ( new BattleModule ( ) ) ;
SimulationController simulationController = new SimulationController ( ) ;
simulationController . CreateSimulation ( new Simulation ( ) , new ISimulativeBehaviour [ ] { new ServerLogicFrameBehaviour ( ) } ) ;
context . SetSimulationController ( simulationController ) ;
context . Server . Run ( port ) ;
Console . ReadKey ( ) ;
}
الكود الموجود على جانب العميل مشابه لذلك الموجود على جانب الخادم، ويتم تعيينه من خلال فئة السياق الرئيسية، وفيما يلي رمز الحالة 1:
void Awake ( ) {
// 用Context.CLIENT构造Context,客户端的Context还需要指定SimulationController等,因此比服务端的Context稍微复杂一些
MainContext = new Context ( Context . CLIENT , new LiteNetworkClient ( ) , new UnityLogger ( "Unity" ) ) ;
MainContext . SetMeta ( ContextMetaId . STANDALONE_MODE_PORT , "50000" )
. SetMeta ( ContextMetaId . PERSISTENT_DATA_PATH , Application . persistentDataPath ) ;
MainContext . SetModule ( new GateServiceModule ( ) ) //大厅组队相关服务
. SetModule ( new BattleServiceModule ( ) ) ; //帧同步服务
// 构造模拟器控制器,SDK提供了一个Default版本的控制器,一般情况下用Default就可以了
DefaultSimulationController defaultSimulationController = new DefaultSimulationController ( ) ;
MainContext . SetSimulationController ( defaultSimulationController ) ;
defaultSimulationController . CreateSimulation ( new DefaultSimulation ( ) , new EntityWorld ( ) ,
new ISimulativeBehaviour [ ] {
new FrameReceiverBehaviour ( ) , //收取服务器的帧数据并处理
new EntityBehaviour ( ) , //执行ECS中System
} ,
new IEntitySystem [ ]
{
new AppearanceSystem ( ) , //外观显示系统
new MovementSystem ( ) , //自定义移动系统,计算所有Movement组件
new ReboundSystem ( ) , //自定义反弹系统
} ) ;
EntityWorld entityWorld = defaultSimulationController . GetSimulation < DefaultSimulation > ( ) . GetEntityWorld ( ) ;
entityWorld . SetEntityInitializer ( new GameEntityInitializer ( entityWorld ) ) ; // 用于初始化和构造Entity
entityWorld . SetEntityRenderSpawner ( new GameEntityRenderSpawner ( entityWorld , GameContainer ) ) ; //ECSR中Renderer的构造
}
تعرض هذه الحالة نتيجة كيانات متعددة تعمل في وقت واحد في عميلين. لا يمكن التحكم في الكائنات أثناء التشغيل، لذا فإن هذه الحالة هي أيضًا أبسط حالة، حيث تتغير حركة جميع الكائنات مع الإطار المنطقي في لحظة التهيئة.
توضح هذه الحالة أنه يمكن للمستخدمين التحكم في حركة الكائنات ومزامنة التغييرات في حركة الكائنات عبر عملاء متعددين.
تشير جميع مشاريع الحالة إلى مكتبة https://github.com/omid3098/OpenTerminal لعرض أوامر الاختبار لتسهيل تصحيح أخطاء واجهة برمجة التطبيقات. اضغط على "" على لوحة المفاتيح لفتح سطر الأوامر.
أمر OpenTerminal | واجهة برمجة التطبيقات في SDK | ملاحظة |
---|---|---|
setuid | contextInst.SetMeta(ContextMetaId.USER_ID, uid); | قم بتعيين بيانات المستخدم في السياق |
يتصل | contextInst.Client.Connect(127.0.0.1, 9030, "بوابة الغرفة"); | الاتصال بالخادم بشكل افتراضي |
اتصال IP المنفذ مفتاح | contextInst.Client.Connect(ip, port, key); | الاتصال بالخادم المحدد |
يخلق | contextInst.GetModule().RequestCreateRoom(mapid); | قم بإنشاء غرفة بعد الاتصال بالخادم |
ينضم | contextInst.GetModule().RequestJoinRoom(roomId); | انضم إلى غرفة مخصصة |
يترك | contextInst.GetModule().RequestLeaveRoom(); | ترك الغرفة المخصصة |
قائمة الغرف | contextInst.GetModule().RequestRoomList(); | تحديث قائمة الغرف |
يطلق | contextInst.GetModule().RequestLaunchGame(); | ابدأ اللعبة |
com.updateteam | contextInst.GetModule().RequestUpdatePlayerTeam(roomId, userId, teamId); | تحديث بيانات الغرفة داخل الغرفة |
updatemap | contextInst.GetModule().RequestUpdateMap(roomId, MapId, maxPlayerCount); | تغيير الخريطة في الغرفة |
قف | الرجاء التحقق من أسلوب الإيقاف في Sample.cs وفئاته الفرعية | نهاية اللعبة |
com.drawmap | الرجاء التحقق من أسلوب DrawMap في Sample.cs وفئاته الفرعية | ارسم خريطة |
saverep | الرجاء التحقق من طريقة SaveReplay في Sample.cs وفئاتها الفرعية | حفظ التشغيل (التسجيل) |
com.playrep | الرجاء التحقق من طريقة PlayReplay في Sample.cs وفئاتها الفرعية | حفظ التشغيل (التسجيل) |
المدرجة أدناه هي بعض بنيات البيانات الرئيسية في SDK، ويمكن أن تصبح أيضًا هياكل بروتوكول ويمكن إجراء تسلسل لها وإلغاء تسلسلها ودعم بنيات البيانات الاختيارية الميدانية المستخدمة على نطاق واسع في SDK. يمكن إنشاؤه بواسطة الأدوات الموجودة في Docs/Protocols/، بالشكل:
public class PtMyData
{
//Fields
public static byte [ ] Write ( PtMyData value ) { }
public static PtMyData Read ( byte [ ] bytes ) { }
}
اسم الفئة | مجال | ملاحظة |
---|---|---|
بت فريم | معرف كيان السلسلة محدث PtComponentUpdaterList بايت[] NewEntitiesRaw | بيانات الإطار الرئيسي |
إطارات | كثافة العمليات FrameIdx ListKeyFrames | مجموعة جميع الإطارات الرئيسية في إطار معين |
بتماب | نسخة السلسلة كيانات قائمة الكيانات | بيانات الخريطة |
بي تي ريبلاي | نسخة السلسلة معرف الخريطة uint ListInitEntities إطارات القائمة | تسجيل (تشغيل) البيانات |
تعد آلية تسجيل (تشغيل) الفيديو هي الآلية الأكثر تميزًا في تقنية مزامنة الإطارات، وهي أيضًا نقطة لا مفر منها. يتمتع SDK أيضًا بالقدرة على حفظ مقاطع الفيديو وتحميلها.
يعد محاكي مزامنة الإطارات جزءًا أساسيًا من SDK لإجراء مزامنة الإطارات. النقطة الأساسية هي أن جميع الأجهزة يجب أن تحافظ على عدد ثابت من الإطارات بعد فترة زمنية بعد بدء التشغيل، ويتطلب ذلك معايرة DateTime عند كل علامة العلاقة بين الفاصل الزمني المحلي وTICK المنطقية. يمكن الاطلاع على التعليمات البرمجية التفصيلية عن طريق فتح ملف SimulationController.cs.