私は、中規模のマルチプレイヤー、約20〜30人のプレイヤーが一度に永続サーバーに接続する等尺性2Dゲームに取り組んでいます。適切な動き予測の実装を適切に行うのに苦労しました。
物理学/運動
このゲームには真の物理学の実装はありませんが、基本原理を使用して動きを実装します。入力を継続的にポーリングするのではなく、プレーヤーが制御しているキャラクターエンティティの状態を変更するために、状態の変更(つまり、マウスのダウン/アップ/移動イベント)が使用されます。プレイヤーの方向(つまり、北東)は一定の速度と組み合わされ、エンティティの速度である真の3Dベクトルに変換されます。
メインゲームループでは、「更新」が「描画」の前に呼び出されます。更新ロジックは、速度がゼロ以外のすべてのエンティティを追跡する「物理更新タスク」をトリガーし、非常に基本的な統合を使用してエンティティの位置を変更します。例:entity.Position + = entity.Velocity.Scale(ElapsedTime.Seconds)(ここで、「Seconds」は浮動小数点値ですが、ミリ秒の整数値に対して同じアプローチが機能します)。
重要な点は、動きに補間が使用されていないことです-初歩的な物理エンジンには「前の状態」または「現在の状態」という概念はなく、位置と速度のみがあります。
状態変更および更新パケット
プレイヤーが制御しているキャラクターエンティティの速度が変化すると、エンティティのアクションタイプ(スタンド、ウォーク、ラン)、方向(北東)、および現在位置を含む「アバターの移動」パケットがサーバーに送信されます。これは、3D一人称ゲームの仕組みとは異なります。3Dゲームでは、プレイヤーが動き回るにつれて、速度(方向)がフレームごとに変化します。すべての状態変更を送信すると、フレームごとにパケットが効率的に送信され、コストがかかりすぎます。代わりに、3Dゲームは状態の変化を無視し、一定の間隔(たとえば80〜150ミリ秒ごと)で「状態更新」パケットを送信するようです。
私のゲームでは速度と方向の更新の頻度がはるかに低いため、すべての状態変更を送信する必要はありません。すべての物理シミュレーションは同じ速度で行われ、確定的ですが、レイテンシは依然として問題です。そのため、通常の位置更新パケット(3Dゲームに似ています)を送信しますが、頻度はずっと低くなります-現在は250ミリ秒ごとですが、良い予測では500ミリ秒に簡単にブーストできると思います。最大の問題は、私が現在標準から逸脱していることです。他のすべてのドキュメント、ガイド、およびサンプルはオンラインで定期的に更新を送信し、2つの状態間を補間します。それは私のアーキテクチャと互換性がないようで、(非常に基本的な)「ネットワーク化された物理」アーキテクチャに近い、より良い動き予測アルゴリズムを考え出す必要があります。
サーバーはパケットを受信し、スクリプトに基づいてその移動タイプからプレーヤーの速度を決定します(プレーヤーは実行できますか?プレーヤーの実行速度を取得します)。速度が得られると、それを方向と組み合わせてベクトル(エンティティの速度)を取得します。いくつかのチート検出と基本的な検証が行われ、サーバー側のエンティティが現在の速度、方向、および位置で更新されます。基本的な調整も実行され、プレイヤーが移動要求でサーバーをあふれさせるのを防ぎます。
自身のエンティティを更新した後、サーバーは範囲内の他のすべてのプレーヤーに「アバター位置更新」パケットをブロードキャストします。位置更新パケットは、リモートクライアントのクライアント側の物理シミュレーション(ワールドステート)を更新し、予測と遅延補正を実行するために使用されます。
予測とラグ補償
前述のように、クライアントは自分の立場に対して権威があります。不正行為や異常の場合を除き、クライアントのアバターはサーバーによって再配置されることはありません。クライアントのアバターには外挿(「今すぐ移動して後で修正」)は必要ありません-プレイヤーが見るものは正しいです。ただし、移動しているすべてのリモートエンティティには、何らかの外挿または内挿が必要です。クライアントのローカルシミュレーション/物理エンジン内では、何らかの予測および/または遅延補償が明らかに必要です。
問題点
私はさまざまなアルゴリズムに苦労してきましたが、いくつかの質問と問題があります。
外挿、内挿、またはその両方を行う必要がありますか?私の「直感」は、速度に基づいた純粋な外挿を使用する必要があるということです。状態の変化はクライアントによって受信され、クライアントは遅延を補正する「予測された」速度を計算し、通常の物理システムが残りを行います。ただし、他のすべてのサンプルコードや記事とは相反するように感じられます。これらはすべて、多くの状態を格納し、物理エンジンなしで補間を実行するようです。
パケットが到着したとき、パケットの位置を、一定時間(たとえば200ms)にわたってパケットの速度で補間しようとしました。次に、補間された位置と現在の「エラー」位置の差を取り、新しいベクトルを計算して、送信された速度ではなくエンティティに配置します。ただし、別のパケットがその時間間隔で到着するという前提があり、次のパケットがいつ到着するかを「推測」することは非常に困難です。特に、すべてのパケットが一定の間隔で到着するわけではないためです。概念に根本的な欠陥があるのか、それとも正しいのか、いくつかの修正/調整が必要なのか?
リモートプレーヤーが停止するとどうなりますか?エンティティをすぐに停止できますが、再び移動するまで「間違った」スポットに配置されます。ベクトルを推定するか、補間しようとすると、以前の状態を保存しないため問題が発生します。物理エンジンには「位置Xに到達した後に停止する必要がある」と言う方法がありません。速度を理解するだけで、それ以上複雑なことはありません。「パケット移動状態」情報をエンティティまたは物理エンジンに追加するのは嫌です。これは、基本的な設計原理に違反し、ゲームエンジン全体でネットワークコードを流出させるためです。
エンティティが衝突するとどうなりますか?3つのシナリオがあります-制御プレイヤーがローカルで衝突する、2つのエンティティが位置更新中にサーバーで衝突する、またはリモートエンティティ更新がローカルクライアントで衝突します。すべての場合において、衝突の処理方法は不確かです-不正行為は別として、両方の状態は「正しい」が、異なる期間にあります。リモートエンティティの場合、壁を歩いて描くのは理にかなっていないので、ローカルクライアントで衝突検出を実行し、「停止」させます。上記のポイント#2に基づいて、エンティティを「壁を通り抜けて」継続的に移動しようとする「修正されたベクトル」を計算することがあります。ポジション。ゲームはこれをどのように回避しますか?