IOゲームを作成するとします。 agar.io、slither.io、および
それらの数千のような
もの 。
IOゲームとはこの名前は、ブラウザ、クライアントサーバーマルチプレイヤーゲームにリアルタイムで付けられました。
このようなゲームの仕組みは通常比較的単純であり、多数の実際のプレイヤーとの壮大な戦いを通して興味が持たれます。
このジャンルの祖先は
agar.ioです 多くの人々は、そのようなゲームがwebsocketを使用することを知っています。 また、独自のプロトコルを作成するのに複雑なことはありません。 そう思ったのは、約1年前にこのスタイルでスペースシューティングプロジェクトを始めたときです。
今はそうは思いません
問題は何ですか
マルチプレイヤーゲームのすべての開発者が解決しなければならない主な問題は、起こっていることの「同時性」を達成することです。
素朴なアプローチ:しかし、ヒーローを動かして、これらの動きをサーバーに送信すると、サーバーはそれらをライバルに送信します-それは機能しません。 これを行うと、ヒーローはリアルタイムで生き、ライバルは過去に生きます。 サーバーへの2つのpingの動きが遅れます。 pingが100ミリ秒の場合、ライバルが200ミリ秒前にどこにいたかがわかります。 したがって、プレイすることは不可能になります。
問題の声明
マウスポインターに続いて、宇宙船が飛ぶ2次元の部屋があります。
次のようにプロトコルを作成する必要があります。
- 動きはスムーズでした
- すべての「パイロット」は同じ絵を持ち、ある時点ですべての船を表示しました
免責事項:
最終結果は非常に簡単です。
ただし、実験を純粋にするために、記事を読む前に独自のバージョンを考えてみてください。そうすれば、コメントを書くときに後知恵の知識の影響を避けることができます。
最初の決定
各フレームをレンダリングするときに、各クライアントがマウスカーソルの位置をサーバーに送信できるようにします。
そして、サーバーはそれを受信すると、すぐに船の現在の座標を考慮し、それらをすべてのクライアント(マウスの座標を送信したクライアントを含む)に送信します。
すべてがシンプルです。 ここで同時性が達成されます。
また、サーバーがローカルに配置されていて、クライアントが非常に少ない場合でも機能します。
実際の状況に近い状況では、このアプローチは次の3つの結果をもたらしました:必死のトラフィック(同じagar.ioのレベルよりも1桁高い)、船の定期的な「ジャンプ」(スムーズな飛行の代わり)、および通常3-5分で開始されるワイルドラグ、そして、ブラウザを再起動するだけで、彼らは決して通過せず、「処理」されました。
プロトコルの2番目のバージョン
最初の編集は明らかでした。クライアントからコマンドを受信した直後に船の動きを送信するのは間違っています。
これを行う場合、サーバーからクライアントへのコマンドの受信頻度は次の3つに依存します。
- サーバーにボートを送る頻度
- ボートからサーバーへのチャネルの混雑
- およびサーバーから他のクライアントへのチャネルの輻輳
変数が多すぎます。 測定の結果、20ミリ秒ごとに送信されるはずのチームは、5から90ミリ秒のランダムな間隔で送信されることがわかりました。
この問題の解決策は簡単でした-コマンドをすぐにクライアントに送信する代わりに、タイマーでそれらを送信しました-20msで1つのコマンド。
船の「ジャンプ」は著しく減少しました...
第三版
...しかし、消えませんでした。
ある時点で、ネットワークの遅延に従って、次のパケットには大きな遅延が生じました。 20ms後ではなく、たとえば100後です。
したがって、ボートは最初に減速し、その後突然停止し、数ピクセルで「テレポート」されました。
そして、奇妙なアイデアが浮上しました。そのような遅延の場合、それでも、先に飛んだのと同じ方向にボートを動かしましょう。
たぶん、写真はより滑らかになりますか? このアイデアはうまくいきませんでしたが、正しい解決策への一歩であることがわかりました。
そのため、クライアントのボートは常に(つまり、すべてのフレームが)一定の速度で飛行します。
サーバーは、この速度を変更するコマンドと、船の座標の「修正」を受信します。
結果? まばらな長距離ジャンプの代わりに、私たちのボートは一連の短距離ジャンプを始めました。
クライアントの観点から見ると、船はある場所にあり、サーバーの観点からは別の場所にあることが判明しました。
サーバーから送信された「修正」により、マイクロジャンプが発生しました。
次の例は
、完全に成功しているわけではない2つの
モロニックなアイデアが連続して実装された場合に、予期しない結果が得られる例を示しています。
そして、修正をあまり頻繁に送信せず、何が起こるか見てみましょう!
もちろん、良いことは何もうまくいきませんでした-再びジャンプすることはよりまれで長くなりました。
しかし、その後、修正の送信間隔を複数回増やしても、ボートの制御の反応が悪くならないことに気付きました。
第4バージョン
しかしこれは、FPS-ボートを1秒間に60回画面上で動かす必要があり、マウスから制御信号を送信するという2つの特性値を完全に無駄にしていることを意味します。
制御信号は、人間の視覚の特性と相関するのではなく、人間の反応の速度と相関するため、はるかに少ない頻度で送信できます。
そして彼女は約200-250msです!
サーバーへのping(約100ミリ秒)を考慮すると、これは制御信号を約100〜150ミリ秒に1回送信できることを意味しており、クライアントでは動きの滑らかさがすでに提供されているはずです。
その結果、プロトコルの4番目のバージョンは次のようになりました。
- クライアントはサーバーに120msごとにマウス位置を送信しました
- この制御コマンドを受信したサーバーは、船舶の座標を再カウントしました
- 非同期的に、サーバーでスレッドが機能し、同じ120ミリ秒で1回だけ、船舶の座標と速度がクライアントに送信されました。
- 座標を受け取った後、クライアントは船をこの新しいポイントに移動し、許容できるFPSを達成するために船のスムーズな移動に速度を使用しました
このバージョンからのボーナスは、トラフィックの急激な減少です。 今、彼は寒天と同じになりました(マテウス・バラダレスは何かを知っていたようです!)。
しかし、最も重要なことは、3〜5分後に発生し、何も治らない重い遅延の問題が自動的に解決されることです。
(数日後、私は
この記事に出会い、そこで何が起こっているのかが明らかになりました)。
問題が1つだけありました-同期中の小さな船のジャンプ。
バージョン5-最終
同期が行われなかった、またはめったに行われなかった場合、サーバー上のクライアントとクライアント上のシップの場所が異なることが判明し、これは当然受け入れられませんでした。
しかし、ジャンプに耐えたくもありませんでした。これは、ネットワーク遅延を伴う直接的な本格的なジャンプに変わりました。
最初の考えは次のように見えました。時間同期を「広げる」としたらどうでしょうか? ここでは、ボートをすぐに目的のポイントに移動しませんが、7〜8のフレームを移動するように指示しますか?
「しかし、これは追加の遅延です...」
-ところで、ボートの速度を変更するのは120ミリ秒ごとです。 そのため、120ミリ秒でどこになるかを事前に知っています!
-この場所からもっと!
そのため、最も単純なアイデアが生まれました(遡及的に検討する場合):サーバーは、同期する代わりに、船の座標をクライアントに送信します。
そして、プロトコル全体は次のようになりました。
- クライアントはマウスの位置を120msごとにサーバーに送信します
- この制御コマンドを受信すると、サーバーは船の座標を再カウントします
- 非同期的に、スレッドはサーバー上で実行され、同じ120ミリ秒で1回だけクライアントに座標を送信します。この場合、船は次の120ミリ秒の終わりになるはずです。
- 座標を受け取ったクライアントは、単純な計算を行います。ボートが現在ポイントAにある場合、次の120ミリ秒でどれだけ速く移動して、サーバーからポイントBに来るかを計算します。
このプロトコルはすべての問題を解決しました。 パケット遅延に対する耐性があります。
何らかの理由で、新しい目標を受け取った時点で、ボートが前の目標に到達しなかった(または上空を飛行しなかった)場合でも、これはコースの滑らかさに影響を与えず、速度のみがわずかに調整されます。
プロトコルに満足しています。 結果はここで見ることができます:
https :
//spacewar.io