كيف خلقت لعبتي على الإنترنت. الجزء 1: الشبكات



مرحبا بالجميع! قضيت عطلة مؤخراً ، وكان هناك وقت لبرمجة مشاريع منزلي بهدوء. لذلك أردت أن أجعل لعبتي البسيطة على الإنترنت على Rust. بتعبير أدق ، مطلق النار 2D بسيط. قررت أن أجعل الشبكة جزءًا أوليًا ، وهناك بالفعل سوف نرى ماذا وكيف. نظرًا لأن النوع ينطوي على إجراء في جميع المجالات ، لذلك قررت استخدام بروتوكول UDP. بدأ تصميم بنية جزء الشبكة. أدركت أنه يمكنك وضع كل شيء في مكتبة منفصلة. لقد قمت أيضًا بتحميل المكتبة الناتجة إلى crates.io بموجب ترخيص MIT ، لأنه: أ) سيكون من المناسب بالنسبة لي توصيلها من هناك بمشاريعي. ب) ربما سيكون من المفيد لشخص آخر وسيحقق فوائد. لمزيد من التفاصيل ، مرحبا بكم في القط.

المراجع


-> المصادر
-> مكتبة في crates.io
-> الوثائق

مثال للاستخدام


العملاء


//   use victorem; fn main() -> Result<(), victorem::Exception> { // ,    11111      127.0.0.1:22222 let mut socket = victorem::ClientSocket::new("11111", "127.0.0.1:22222")?; loop { //    socket.send(b"Client!".to_vec()); //    .             socket.recv().map(|v| String::from_utf8(v).map(|s| println!("{}",s))); } } 

الخادم


 //   use victorem; use std::time::Duration; use std::net::SocketAddr; //,  .            . struct ClientServerGame; //     Game,         impl victorem::Game for ClientServerGame { //,     .       false,   . fn handle_command(&mut self, delta_time: Duration, commands: Vec<Vec<u8>>, from: SocketAddr) -> bool { for command in commands { String::from_utf8(command).map(|s| println!("{}",s)); } true } //     30 .     ,     .      ,     . fn draw(&mut self, delta_time: Duration) -> Vec<u8> { b"Server!".to_vec() } } fn main() -> Result<(), victorem::Exception> { // ,      ClientServerGame     22222 let mut server = victorem::GameServer::new(ClientServerGame, "22222")?; //       . server.run(); Ok(()) } 

الجهاز داخليا


بشكل عام ، إذا استخدمت مآخذ UDP الأولية بدلاً من مآخذ UDP الأولية لجزء الشبكة من Laminar ، فيمكن عندئذٍ تخفيض الشفرة بمعامل 100 ، واستخدم الخوارزمية الموضحة في هذه السلسلة من المقالات - Network Programming for Developers Game .
تتضمن بنية الخادم تلقي أوامر من العملاء (على سبيل المثال ، الضغط على زر الماوس أو زر ما على لوحة المفاتيح) وإرسال حالة (على سبيل المثال ، الموضع الحالي للوحدات والاتجاه الذي ينظرون إليه) حيث يمكن للعميل عرض صورة للاعب.

على الخادم


 //         u32     ,  0 -  , 1 -   ,      . pub fn get_lost(&self) -> (u32, u32) { let mut sequence: u32 = 0; let mut x = 0; let mut y = self.last_received_packet_id; while x < 32 && y > 1 { y -= 1; if !self.received.contains(&y) { let mask = 1u32 << x; sequence |= mask; } x += 1; } (sequence, self.last_received_packet_id) } 

على العميل


 //      (max_id)         (sequence)  .           fn get_lost(&mut self, max_id: u32, sequence: u32) -> Vec<CommandPacket> { let mut x = max_id; let mut y = 0; let mut ids = Vec::<u32>::new(); //      , ,     ,      . let max_cached = self.cache.get_max_id(); if max_cached != max_id { ids.push(max_cached); } while x > 0 && y < 32 { x -= 1; let mask = 1u32 << y; y += 1; let res = sequence & mask; if res > 0 { ids.push(x); } } self.cache.get_range(&ids) } 

خاتمة


في الواقع ، كان من الأسهل إجراء خوارزمية تسليم الأوامر. على الخادم ، اقبل فقط الحزمة التي انتقلت إليها أكثر من مجرد الانتقال إلى آخر حزمة تم استلامها بواسطة +1 ، وتجاهل الباقي. إرسال العميل الحزمة الأخيرة المستلمة. على العميل ، احتفظ بذاكرة تخزين مؤقت لجميع الأوامر التي حاول المستخدم إرسالها إلى الخادم. في كل مرة تصل فيها حالة جديدة من الخادم ذي المعرف ، فإن آخر رزمة يتلقاها الخادم ، يحذفها من ذاكرة التخزين المؤقت وجميع الحزم ذات المعرف أقل مما كانت عليه. يتم إرسال جميع الحزم المتبقية إلى الخادم مرة أخرى.
علاوة على ذلك ، عندما أقوم بإجراء اللعبة نفسها ، في عملية الاستخدام ، سوف أقوم بتحسين وتحسين lib. ربما سأجد بعض الأخطاء.

لقد وجدت هنا مشروع خادم للألعاب في C # - Networker + on Rust ، هناك ورقة تشبه إلى حد ما نظير خادم ألعاب على Go-leaf. فقط هناك تطور في التقدم.

ملاحظة صديقي العزيز ، إذا كنت مبتدئًا وقررت قراءة الكود الخاص بي لهذا المشروع ورؤية الاختبارات التي كتبت هناك. حتى هنا نصيحتي لك - لا تفعل ما أفعله. لقد مزجت كل شيء في كومة في الاختبارات ولم أتبع قالب "AAA" (google ما هو). ليس عليك القيام بذلك في الإنتاج. يجب أن يتحقق الاختبار العادي من شيء واحد ، وليس عدة شروط في آن واحد ، ويجب أن يتكون من الخطوات:

  1. قمت بتعيين المتغيرات الخاصة بك.
  2. يمكنك تنفيذ الإجراء الذي تريد اختباره ؛
  3. قارنت النتيجة بالنتيجة المتوقعة.

على سبيل المثال

  fn add_one(x:usize) -> usize { x+1 } #[test] fn add_one_fn_should_add_one_to_it_argument(){ let x = 2; let expected = x+1; ///////////////////////// let result = add_one(x); ////////////////////////////////// assert_eq!(expected,result); } 

Source: https://habr.com/ru/post/ar435384/


All Articles