# GGPOとは 2009年に開発されたGGPOネットワーキングSDKは、P2Pゲームにおけるロールバックネットワーキング実用化の先駆けとなったシステムです。正確な入力や、フレームごとの完璧な処理を必要とし、ゲーム展開が速くかつ配信に適したゲームにおいて、ネットワーク遅延を目立たなくさせることに重点を置いて開発されました。 従来の技術はプレイヤーの入力に遅延を織り込んで通信を行っており、その結果反応が遅く、ラグを感じるプレイ感になっていました。ロールバックネットワーキングは入力予測と投機的実行を行って、プレイヤーの入力を即座に送信するため、遅延を感じさせないネット環境をもたらします。ロールバックがあれば、タイミングや相手の動きや効果音に対する反応、指が覚えている入力、これらオフラインで行えた内容が、そのままオンラインでも行えます。GGPOネットワーキングSDKは、ロールバックネットワーキングを新作や発売されているゲームに極力簡単に組み込めるよう作られています。 # 仕組み ロールバックネットワーキングは決定的P2Pエンジンに統合できるよう設計されています。完全に決定的なエンジンなら、同じ入力をした場合にゲームは必ず同じ内容のプログラム再生をします。その内容を実現する一つの方法としては、ネットワーク上の全プレイヤーと入力のやりとりをする方法があげられますが、これは全プレイヤーがピアから入力を全て受け取った時にのみゲームプレイロジックが1フレームだけ実行される形になります。この方法ではゲーム内でキャラの動きがぎくしゃくし、反応の悪いゲーム内容になりがちです。ネットワークを介して入力を受け取る時間が長くなるほど、ゲーム展開も遅くなってしまいます。 ## 入力遅延を用いたネットワーキング ### 理論上は… 下の図を見てください。2つのクライアントが遅延0msの理想的なネットワークで同期されている図になっています。1プレイヤー側の入力が青、2プレイヤー側の入力は赤、ネットワーク層は緑です。黒の矢印は入力がシステム内で送信され、ゲームステートが推移する流れを表します。各フレームは破線で区切られています。図は1プレイヤー側から見たものになっていますが、2プレイヤー側も全く同じ手順になっています。 ![](images/overview_image1.png) 1プレイヤーの入力は、ネットワーク層によって2プレイヤーの入力とマージされ、ゲームエンジンに送信されます。エンジンはその入力を用いて現在のフレームのゲームステートを変更します。2プレイヤー側も同様に自分と1プレイヤーの入力をマージしてゲームエンジンに送信します。プレイヤーの入力に応じたロジックを適用し、過去のフレームのゲームステートを変更しながら、フレームごとにゲームが進行します。1プレイヤーと2プレイヤー両方が同じゲームステートで、それぞれのエンジンに送信される入力が同じなので、両プレイヤーのゲームステートは毎フレーム同期されたままになります。 ### 実際は… 理想的なネットワークの例では、パケットがネットワークを介して即時に送信されるものとされていますが、現実はそう甘くはありません。一般的なブロードバンド接続では、プレイヤー間の距離や回線の品質に応じて、パケットの送信に5~150msかかります。ゲームが1秒間に60フレームで実行されるとするならば、遅延は1~9フレーム相当になります。 ゲームは両プレイヤーの入力を受信するまでフレームを処理することができないので、各プレイヤーの入力に1~9フレームの遅延、つまり「ラグ」を適用しなければなりません。遅延を考慮に入れ、先程の図を変更してみると… ![](images/overview_image3.png) この例では、パケットの送信に3フレームかかります。2プレイヤーによって遠隔から送信された入力は、1フレーム目に1プレイヤー側に届かず、3フレーム後になるまでゲーム機に届きません。1プレイヤー側のゲームエンジンは入力を受信するまでゲームを進めることができないので、1フレーム目を3フレーム遅延せざるを得なくなります。続きのフレームも同様に3フレームの遅延が発生します。ネットワーク層は両プレイヤー間で送信されるパケットの最長転送時間だけ、マージされた入力を遅延せざるを得なくなります。理想的なネットワーク環境を除いては、大半のゲームジャンルにおいてこのラグはプレイ感に大きく影響を与えることとなります。 ## ロールバックネットワーキングで入力遅延を取り除く ### 投機的実行 GGPOは投機的実行を用いることで、パケット送信に必要な遅延を隠し、入力ラグの発生を防ぎます。それでは、もう一つの図を見てみましょう。 ![](images/overview_image2.png) GGPOは遠隔のプレイヤーから入力が届くのを待つ代わりに、過去の入力に基づいて他プレイヤーが行いそうな入力を予測します。予測された入力と1プレイヤーの入力をマージし、すぐにゲームエンジンへ渡すので、仮に他プレイヤーの入力が届かなくとも次のフレームへ進めることができます。 GGPOの予測が完璧であれば、オンラインで遊ぶユーザー体験はオフラインと同一のものになります。もちろん、未来を予測することは誰にもできません!GGPOも2プレイヤーの入力を間違って予測することがあります。上の図をもう一度見てください。もしGGPOが1フレーム目に2プレイヤーに間違った入力を送信したらどうなるでしょうか。1プレイヤー側に表示される2プレイヤーの入力は、2プレイヤー側で表示されるものと異なってしまいます。両サイドのゲームは同期を失い、プレイヤーは違ったゲーム画面を見ながら相手の動きに反応することになります。同期のズレは、1プレイヤー側が2プレイヤーの正しい入力が届く4フレーム目まで検出することができませんが、それでは遅すぎます。 そういうことから、GGPOの手法は「投機的実行(speculative execution)」と呼ばれます。遊んでいるプレイヤーがその時に見ているものは正しいかもしれませんが、そうでないこともあります。GGPOが遠隔プレイヤーの入力を誤って予測した場合、次のフレームへ進める前にエラーを修正する必要があります。次の例では、その方法を説明します。 ### 投機的実行エラーをロールバックで修正する GGPOは遠隔プレイヤーの入力を間違って予測する度に、ロールバックを使ってクライアントを再同期します。「ロールバック」という単語は、ステートを巻き戻し、プレイヤーの入力に関する、より正しく新しい情報を元に結果を予測する過程を指します。前のセクションでは、遠隔の入力1における予測したフレームが間違っていたらどうなるか、ということについて考えました。それでは、GGPOがエラーを修正する過程を見てみましょう。 ![](images/overview_image5.png) GGPOは遠隔の入力を受信したら、その都度前回のフレームで予測した品質をチェックします。先程触れたように、GGPOは4フレーム目まで2プレイヤー側の入力が届きません。4フレーム目で、GGPOは以前に予測した入力とネットワークから受信した入力が一致しないことに気付きます。両サイドのゲームを再同期するため、GGPOは3フレーム分の誤った入力によって発生したダメージや間違いを取り消す必要があります。誤って予測した入力を送信する前のフレームまで戻るよう、ゲームエンジンに要求します(つまり過去のステートまで「ロールバック」します)。以前のステートを復元したら、GGPOはエンジンに正しい入力で1フレーム進めるよう要求します。このフレームは水色で示しています。ゲームエンジンはこのフレームをユーザーに見えない形で出来る限り素早く進める必要があります。例えば、ビデオレンダラーはこのフレームを画面に描写するべきではありません。オーディオレンダラーは原則、音声を生成し続けるべきですが、ロールバックが終わるまでレンダーすべきではなく、サンプルが生成されたフレームを引いた現在のフレームであるnフレームでサンプルがスタートする必要があります。 エンジンがGGPOがエラーを見つける前のフレームまで到達したら、GGPOはロールバックモードを止め、ゲームを通常どおり進めることを許可します。図の5フレームと6フレーム目はGGPOの予測が正しく行われた場合を示しています。ゲームステートが正しいので、ロールバックをする理由はありません。 # コード構造 次の図はGGPOセッションで主に動作するパーツ、また各パーツごとの関連性を示しています。各コンポーネントの詳細は以下に示しています。 ![](images/overview_image4.png) ## GGPOインタフェース(GGPO Interface) GGPOインターフェースはP2Pと同期テストバックエンド間の詳細な実装を抽象化しています。適切なバックエンドは`ggpo_start_session`か`ggpo_start_synctest`エントリーポイントを呼び出した時に、自動的に生成されます。 ## P2Pバックエンド(P2P Backend) P2Pバックエンドはプレイヤー間でゲームを調整します。`ggpo_start_session` APIの呼び出しによって生成されます。大きな情報の処理の大半は含まれているヘルパークラスによって行われます。 ## ポーリングオブジェクト(Poll Object) (図にはありません)ポーリングオブジェクトはコード内で他のオブジェクトによって用いられる登録方式です。待機可能なオブジェクトが準備できたときに通知とタイマーを送信します。例としてUDPバックエンドは新たなパケットが到着したときに、通知を受信するためポーリングオブジェクトを使用します。 ## 同期オブジェクト(Sync Object) 同期オブジェクトはゲームステートのnフレームを追跡するために用いられます。埋め込まれた予測(prediction)オブジェクトが予測エラーを通知された時、同期バックエンドがより正確なステートまでゲームを巻き戻し、予測エラーを修正するためシングルステップ処理を進めます。 ## 入力キューオブジェクト(Input Queue Object) 入力キューオブジェクトはローカル、または遠隔プレイヤー用に受信した全入力を追跡します。所持していない入力を要求された場合、入力キューは次の入力を予測し、後の情報を追跡します。そうすることで同期オブジェクトは予測が誤った場合にどこまでロールバックすればよいのか分かります。リクエストがあった場合、入力キューはフレーム遅延も実行します。 ## UDPプロトコルオブジェクト(UDP Protocol Object) UDPプロトコルオブジェクトは両プレイヤー間の同期と入力交換プロトコルを扱います。また、ゲーム入力の圧縮と信頼できるUDP層も実装しています。各UDPプロトコルオブジェクトにはTimeSyncオブジェクトが含まれ、プレイヤー間の時間のずれを推測するために利用しています。 ## UDPオブジェクト(UDP Object) UDPオブジェクトは単純なUDPパケットの送受信を行います。他のプラットフォームへの移植を簡単にするため、UDPプロトコルから切り離されています。 ## 同期テストバックエンド(Sync Test Backend) (図にはありません)同期テストバックエンドは、P2Pバックエンドがアプリのセーブステートと決定的に機能上実行していることを確認するときに同じ同期オブジェクトを使用します。同期テストの使用に関する詳しい情報は、開発者ガイドを参照してください。