オンラインゲームの作成方法。 パート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分の1に削減でき、このシリーズの記事-Network Programming for Game Developersで説明されているアルゴリズムを使用します。

サーバーのアーキテクチャには、クライアントからのコマンドの受信(たとえば、マウスボタンやキーボード上のボタンの押下)と、クライアントがプレーヤーに画像を表示できる状態(たとえば、ユニットの現在の位置や見ている方向)の送信が含まれます。



サーバー上



 //         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がそれよりも小さいすべてのパケットが削除されます。 残りのすべてのパケットは再びサーバーに送信されます。

さらに、ゲーム自体を作成するときは、使用の過程でライブラリをさらに改善および最適化します。 おそらく私はさらにいくつかのバグを見つけるでしょう。



ここで、C# -RustのNetworker +にゲームサーバープロジェクトを見つけました。葉があります。Goのゲームサーバーの類似物のようなものです。 進行中の開発のみがあります。



PS親愛なる友人、あなたが初心者で、このプロジェクトのコードを読んで、そこで書いたテストを見ることに決めたなら。 だからここにあなたへの私のアドバイスがあります-私のようにしないでください。 テストですべてをヒープに混ぜて、「AAA」テンプレート(Googleの意味)に従わなかった。 これは本番環境では必要ありません。 通常のテストでは、一度に複数の条件ではなく、1つのことを確認し、次の手順で構成する必要があります。



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






All Articles