ネットコードの扱い方は?


10

ネットコードがゲームエンジンに「フック」できるさまざまな方法を評価することに興味があります。私は現在マルチプレイヤーゲームを設計しています。これまでのところ、少なくとも、ネットワークソケットを処理するために、グラフィックループとスクリプトを処理する他のエンジンとは別のスレッドを用意する必要があると判断しました。

私は、ネットワーク化されたゲームを完全にシングルスレッド化する1つの潜在的な方法がありました。つまり、非ブロッキングソケットを使用して、各フレームをレンダリングした後にネットワークをチェックします。ただし、フレームのレンダリングにかかる​​時間がネットワークラグに追加されるため、これは明らかに最適ではありません。ネットワーク経由で到着するメッセージは、現在のフレームのレンダリング(およびゲームロジック)が完了するまで待機する必要があります。しかし、少なくともこの方法では、ゲームプレイは多かれ少なかれスムーズなままです。

ネットワーク用に別のスレッドがあると、ゲームはネットワークに完全に応答することができます。たとえば、サーバーから状態の更新を受信するとすぐにACKパケットを送り返すことができます。しかし、ゲームコードとネットワークコードの間で通信するための最良の方法について少し混乱しています。ネットワークスレッドは受信したパケットをキューにプッシュし、ゲームスレッドはループ中の適切なタイミングでキューから読み取るため、この最大1フレームの遅延は解消されていません。

また、パケットの送信を処理するスレッドは、パイプを下りてくるパケットをチェックしているスレッドとは別のスレッドにしたいようです。これは、パイプが途中にある間は送信できないためです。着信メッセージがあるかどうかを確認します。selectまたは類似の機能について考えています。

私の質問は、最高のネットワーク応答性を実現するゲームを設計するための最良の方法は何でしょうか?明らかに、クライアントはできるだけ早くユーザー入力をサーバーに送信する必要があるため、イベント処理ループの直後、両方ともゲームループ内でnet-sendコードを送信することができます。これは意味がありますか?

回答:


13

応答性を無視します。LANでは、pingは重要ではありません。インターネットでは、60〜100ミリ秒の往復が祝福です。3Kを超えるスパイクが発生しないようにラグ神に祈ってください。これが問題になるためには、ご使用のソフトウェアを非常に少ない数の更新/秒で実行する必要があります。毎秒25回の更新で撮影する場合、パケットを受信して​​処理するまでの間に最大40msの時間があります。そして、それはシングルスレッドのケースです...

柔軟性と正確さのためにシステムを設計します。ネットワークサブシステムをゲームコードにフックする方法についての私の考えは次のとおりです。メッセージング。多くの問題の解決策は、「メッセージング」です。私は実験室のラットで癌を治したというメッセージを一度伝えたと思います。メッセージを送信することで、自動車保険を200ドル以上節約できます。しかし真剣に、メッセージングは​​おそらく2つの独立したサブシステムを維持しながらゲームコードにサブシステムを接続するための最良の方法です。

ネットワークサブシステムとゲームエンジンの間の通信、および2つのサブシステムの間の通信にはメッセージングを使用します。サブシステム間メッセージングは​​、std :: listを使用してポインターによって渡されるデータのblobのような単純なものにすることができます。

単に、送信メッセージキューと、ネットワークサブシステムのゲームエンジンへの参照があります。ゲームは、送信キューに送信したいメッセージをダンプして、それらを自動的に送信するか、または「flushMessages()」などの関数が呼び出されたときに送信することができます。ゲームエンジンに1つの大きな共有メッセージキューがあった場合、メッセージの送信に必要なすべてのサブシステム(ロジック、AI、物理、ネットワークなど)がすべてにメッセージをダンプし、メインゲームループがすべてのメッセージを読み取ることができます。そしてそれに応じて行動します。

必須ではありませんが、別のスレッドでソケットを実行することは問題ないと思います。この設計の唯一の問題は、一般に非同期であり(パケットがいつ送信されているか正確にわからない)、デバッグとタイミング関連の問題のランダムな表示/非表示を困難にする可能性があることです。それでも、正しく行われていれば、これらはどちらも問題にはなりません。

より高いレベルから言えば、ゲームエンジン自体とは別のネットワーキングです。ゲームエンジンはソケットやバッファを気にせず、イベントを気にします。イベントとは、「プレイヤーXが発砲した」「ゲームTで爆発が起こった」などです。これらは、ゲームエンジンによって直接解釈できます。どこから生成されるか(スクリプト、クライアントのアクション、AIプレーヤーなど)は重要ではありません。

イベントを送受信する手段としてネットワークサブシステムを扱う場合、ソケットで単にrecv()を呼び出すよりも多くの利点があります。

たとえば、帯域幅を最適化するには、50個の小さなメッセージ(長さが1〜32バイト)を取得し、ネットワークサブシステムにそれらを1つの大きなパケットにパッケージ化して送信させます。多分それが送信する前にそれはそれらを送信する前に圧縮するかもしれません。一方、コードは、ゲームエンジンが読み取るために、大きなパケットを再度50個の個別のイベントに解凍/パッケージ解除する可能性があります。これはすべて透過的に行われます。

他のクールなものには、共有メモリ空間のメッセージングを介して通信する同じマシン上で純粋なクライアント+純粋なサーバーを実行することによってネットワークコードを再利用するシングルプレーヤーゲームモードがあります。その後、シングルプレイヤーゲームが適切に動作すれば、リモートクライアント(つまり、真のマルチプレイヤー)も動作します。さらに、シングルプレイヤーゲームは正しく表示されるか、完全に間違っているため、クライアントが必要とするデータを事前に検討する必要があります。混合して一致させ、サーバーを実行し、マルチプレイヤーゲームのクライアントになります。すべて同じように簡単に機能します。


メッセージを渡すために単純なstd :: listなどを使用することについて言及しました。これはStackOverflowのトピックかもしれませんが、スレッドがすべて同じアドレス空間を共有していることは本当ですか?複数のスレッドが私のキューに属するメモリを同時にねじ込まない限り、私は大丈夫でしょうか?通常のようにヒープ上のキューにデータを割り当てるだけで、ミューテックスを使用できますか?
Steven Lu

それは正解です。std :: listへのすべての呼び出しを保護するミューテックス。
PatrickB

返信いただきありがとうございます!これまでのところ、スレッディングルーチンで多くの進歩を遂げています。自分のゲームエンジンを持っているのはとても素晴らしい気分です!
Steven Lu

4
その感覚はすり減ります。あなたが得る大きな真鍮のものは、しかし、あなたと一緒にいてください。
ChrisE 2011

@StevenLuやや[非常に]遅くなりますが、スレッドのメモリへの同時接続を防ぐことは、どのように実行しようとしているか、どれだけ効率的になりたいかに応じて、非常に難しい場合があることを指摘しておきます。今日これを行っていたら、複雑なホイールを再発明する必要がないように、多くの優れたオープンソースの並行キュー実装の1つを紹介します。
モニカの訴訟に資金

4

(少なくとも)ネットワークソケットを処理するために別のスレッドが必要です

いいえ、必要ありません。

私は、ネットワーク化されたゲームを完全にシングルスレッド化する1つの潜在的な方法がありました。つまり、非ブロッキングソケットを使用して、各フレームをレンダリングした後にネットワークをチェックします。ただし、フレームのレンダリングにかかる​​時間がネットワークラグに追加されるため、これは明らかに最適ではありません。

必ずしも重要ではありません。ロジックはいつ更新されますか?まだ何もできない場合は、ネットワークからデータを取得しても意味がありません。同様に、まだ何も言うことがない場合に応答しても意味がありません。

たとえば、サーバーから状態の更新を受信するとすぐにACKパケットを送り返すことができます。

ゲームのペースが速すぎて、次のフレームがレンダリングされるのを待つことが大幅な遅延である場合、個別のACKパケットを送信する必要のない十分なデータを送信することになります。通常のデータ内にACK値を含めるだけです。ペイロード(必要な場合)。

ほとんどのネットワークゲームでは、次のようなゲームループが発生する可能性があります。

while 1:
    read_network_messages()
    read_local_input()
    update_world()
    send_network_updates()
    render_world()

更新をレンダリングから切り離すことができますが、これは強く推奨されますが、特別な必要がない限り、他のすべてはほとんど単純なままです。正確にはどのようなゲームを作っていますか?


7
更新をレンダリングから分離することは強く推奨されるだけでなく、たわごとのないエンジンが必要な場合に必要です。
AttackingHobo 2011年

ただし、ほとんどの人はエンジンを作成していません。おそらく、大部分のゲームはまだ2つを分離していません。経過時間の値を更新関数に渡すための整定は、ほとんどの場合問題なく機能します。
Kylotan

2

ただし、フレームのレンダリングにかかる​​時間がネットワークラグに追加されるため、これは明らかに最適ではありません。ネットワーク経由で到着するメッセージは、現在のフレームのレンダリング(およびゲームロジック)が完了するまで待機する必要があります。

それはまったく真実ではありません。受信者が現在のフレームをレンダリングしている、メッセージはネットワーク経由します。ネットワークラグは、クライアント側のフレーム数全体に固定されます。はい。ただし、クライアントのFPSが非常に少なく、これが大きな問題である場合、問題はさらに大きくなります。


0

ネットワーク通信はバッチ処理する必要があります。各ゲームティックを送信する単一のパケットに努力する必要があります(これは、フレームがレンダリングされる場合が多いですが、実際には独立しているはずです)。

ゲームエンティティはネットワークサブシステム(NSS)と通信します。NSSはメッセージやACKなどをまとめて、最適なサイズのUDPパケット(通常は1500バイト)をいくつか(できれば1つ)送信します。NSSはパケット、チャネル、優先度、再送信などをエミュレートし、単一のUDPパケットのみを送信します。

ゲームのチュートリアルに関する説明を読むか、Glenn Fiedlerのアイデアの多くを実装するENetを使用してください

または、ゲームが単収縮反応を必要としない場合は、TCPを使用できます。その後、すべてのバッチ処理、再送信、およびACKの問題が解消されます。ただし、NSSで帯域幅とチャネルを管理する必要はあります。


0

「応答性を完全に無視」しないでください。すでに遅延したパケットにさらに40ミリ秒のレイテンシを追加しても、得るものはほとんどありません。いくつかのフレーム(60fps)を追加する場合は、位置の更新処理を遅らせ、別のいくつかのフレームを更新します。パケットをすばやく受け入れてすばやく処理することをお勧めします。これにより、シミュレーションの精度が向上します。

画面に表示されているものを表すために必要な最小限の状態情報について考えることで、帯域幅の最適化に大きな成功を収めました。次に、データの各ビットを調べ、そのモデルを選択します。位置情報は、時間の経過に伴うデルタ値として表すことができます。このために独自の統計モデルを使用して何年もかけてそれらをデバッグするか、ライブラリを使用して支援することができます。私はこのライブラリの浮動小数点モデルDataBlock_Predict_Floatを使用することを好みます。これ により、ゲームシーングラフに使用される帯域幅を最適化することが非常に簡単になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.