Comment j'ai créé mon jeu en ligne. Partie 1: Réseautage



Bonjour à tous! J'ai récemment pris des vacances et j'ai eu le temps de programmer calmement mes projets de maison. Je voulais donc faire mon simple jeu en ligne sur Rust. Plus précisément, un simple shooter 2D. J'ai décidé de faire d'abord partie du réseau, et là on verra déjà quoi et comment. Comme le genre implique une action dans tous les domaines, j'ai donc décidé d'utiliser le protocole UDP. Il a commencé à concevoir l'architecture de la partie réseau. J'ai réalisé que vous pouvez tout mettre dans une bibliothèque séparée. J'ai également téléchargé la bibliothèque résultante sur crates.io sous la licence MIT, car: a) Il sera plus pratique pour moi de la connecter ultérieurement à mes projets. b) Peut-être que cela sera utile à quelqu'un d'autre et apportera des avantages. Pour plus de détails, bienvenue au chat.

Les références


-> Sources
-> Bibliothèque sur crates.io
-> Documentation

Exemple d'utilisation


Client


//   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))); } } 

Serveur


 //   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(()) } 

Périphérique interne


En général, si j'utilisais des sockets UDP brutes au lieu de sockets UDP brutes pour la partie réseau de Laminar , le code pourrait être réduit d'un facteur 100, et j'utilise l'algorithme décrit dans cette série d'articles - Programmation réseau pour les développeurs de jeux .
L'architecture du serveur implique la réception de commandes de clients (par exemple, en appuyant sur un bouton de la souris ou sur un bouton du clavier) et en leur envoyant un état (par exemple, la position actuelle des unités et la direction dans laquelle ils regardent) avec lequel le client peut afficher une image pour le joueur.

Sur le serveur


 //         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) } 

Sur le client


 //      (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) } 

Épilogue


En fait, il était plus facile de créer un algorithme de livraison de commande. Sur le serveur, acceptez uniquement le paquet avec lequel aller plus que le dernier paquet reçu par +1, et jetez le reste. Envoyez au client le dernier paquet reçu. Sur le client, conservez un cache de toutes les commandes que l'utilisateur a tenté d'envoyer au serveur. Chaque fois qu'un nouvel état arrive du serveur avec id, le dernier paquet reçu par le serveur, supprimez-le du cache et tous les paquets avec id inférieur à ce qu'il a. Tous les paquets restants sont à nouveau envoyés au serveur.
De plus, lorsque je ferai le jeu lui-même, en cours d'utilisation, j'améliorerai et optimiserai davantage la bibliothèque. Je trouverai peut-être quelques bugs supplémentaires.

J'ai trouvé ici un projet de serveur de jeux en C # - Networker + sur Rust, il y a une feuille, un peu comme un analogue d'un serveur de jeux sur Go - leaf. Seulement, il y a un développement en cours.

PS Cher ami, si vous êtes débutant et que vous avez décidé de lire mon code pour ce projet et de voir les tests que j'y ai écrits. Voici donc mon conseil pour vous - ne faites pas comme moi. J'ai tout mélangé en tas dans les tests et je n'ai pas suivi le modèle «AAA» (google ce que c'est). Vous n'avez pas à le faire en production. Un test normal devrait vérifier une chose, pas plusieurs conditions à la fois, et devrait comprendre les étapes suivantes:

  1. Vous définissez vos variables;
  2. Vous effectuez l'action que vous souhaitez tester;
  3. Vous comparez le résultat avec celui attendu.

Par exemple

  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/fr435384/


All Articles