📜 ⬆️ ⬇️

How I created my online game. Part 1: Networking



Hello to all! I recently had a vacation, and I had time to quietly program my home projects. I wanted to make my own simple online game on Rust. More precisely, a simple 2D shooter. I decided to first make the network part, and there it will already be clear that yes how. Since the genre involves action in all fields, so I decided to use the UDP protocol. Began to design the architecture of the network part. I realized that you can take it all to a separate library. I also downloaded the resulting library on crates.io, under the MIT license, because: a) It will be more convenient for me to connect it from there to my projects from there. b) Maybe she is still useful to someone and will be useful. For details, welcome under cat.

Links


-> Sources
-> Library on crates.io
-> Documentation
')

Usage example


Customer


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

Server


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

Internal device


In general, if I used Laminar for the network part and not raw UDP sockets, the code could be reduced by a factor of 100, and I use the algorithm described in this series of articles - Network programming for game developers .
The server architecture involves receiving commands from clients (for example, pressing a mouse button or some button on the keyboard) and sending them a status (for example, the current position of the units and the direction they are looking) with which the client can display a picture to the player.

On server


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

On the 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) } 

Epilogue


In fact, it was possible to make the command delivery algorithm easier. On the server, accept only the packet that has the id more than the last packet received by +1, and discards the rest. Send the client the last package received. On the client, keep the cache of all the commands that the user tried to send to the server. Every time when a new state comes from the server with the last package received by the server, delete it from the cache and all the packages with the ID less than it has. All remaining packages are sent to the server again.
Further, when I will make the game itself, in the process of use I will further improve and optimize lib. Perhaps I will find some more bugs.

I found here the project of the game server on C # - Networker + on Rust there is a leaf like, like an analog game server on Go - leaf. Only there development in the process.

PS Dear friend, if you are a beginner and decided to read my code to this project and you will see there tests that I wrote. So here's my advice to you - do not do it as I do. I there all in a heap in the tests mixed and did not follow the template "AAA" (google what it is). So do not need to be in production. A normal test should check one thing and not several conditions at once and should consist of the following steps:

  1. You set your variables;
  2. You perform the action you want to test;
  3. You compare the result with the expected.

For example,

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


All Articles