我如何创建我的在线游戏。 第1部分:网络



大家好! 我最近有个假期,有时间冷静地编写我的家庭项目。 因此,我想在Rust上制作我的简单在线游戏。 更准确地说,是一个简单的2D射击游戏。 我决定先将网络作为一部分,在那里已经可以看到它的内容和方式。 由于类型涉及所有领域中的动作,因此我决定使用UDP协议。 他开始设计网络部分的体系结构。 我意识到您可以将所有内容放在一个单独的库中。 我还根据MIT许可将结果库上载到了crates.io,因为:a)这样,从那里将其连接到我的项目将更加方便。 b)也许对其他人有用并且会带来好处。 有关详细信息,欢迎猫。

参考文献


-> 来源
-> 位于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(()) } 

内部装置


通常,如果我对Laminar的网络部分使用原始UDP套接字而不是原始UDP套接字,则代码可以减少100倍,并且我使用本系列文章中描述的算法- 游戏开发人员的网络编程
服务器的体系结构包括从客户端接收命令(例如,按下鼠标按钮或键盘上的某些按钮)并向其发送状态(例如,单位的当前位置和它们所看的方向),客户端可以通过该状态向播放器显示图片。

在服务器上


 //         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接收的最后一个数据包,然后丢弃其余的数据包。 向客户端发送最后收到的数据包。 在客户端上,保留用户尝试发送到服务器的所有命令的缓存。 每次从服务器收到ID为新状态(服务器收到的最后一个数据包)时,都将其从缓存中删除,并将所有ID小于其拥有的数据包删除。 所有剩余的数据包将再次发送到服务器。
此外,当我自己制作游戏时,在使用过程中,我将进一步改进和优化lib。 也许我会发现更多错误。

我在这里发现了C#中的一个游戏服务器项目-Rust上的Networker +,有一个叶子,有点像Go-叶子上的游戏服务器的类似物。 只有发展在进步。

PS亲爱的朋友,如果您是初学者,并决定阅读我的项目代码,并查看我在此处编写的测试。 因此,这是我对您的建议-不要像我那样做。 我在测试中将所有内容混合在一起,没有遵循“ 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/zh-CN435384/


All Articles