diff --git a/README.ja.md b/README.ja.md index b92b179..616cbe4 100644 --- a/README.ja.md +++ b/README.ja.md @@ -25,13 +25,13 @@ Windowsのビルドは[Visual Studio 2019](https://visualstudio.microsoft.com/do ## サンプルアプリケーション -ソースディレクトリ内のVector Warアプリケーションでは、2つのクライアントを同期するGGPOが搭載されています。コマンドライン引数は +ソースディレクトリ内のVector Warには、GGPOを使った2つのクライアントを同期する単純なアプリケーションが含まれています。コマンドライン引数は以下の通りです。 ``` vectorwar.exe ('local' | :) for each player ``` -2~4プレイヤーゲームの始め方についての例は、binディレクトリにある.cmdファイルをご覧ください。 +2~4プレイヤーでのゲーム開始方法の例については、binディレクトリにある.cmdファイルを参照してください。 ## ライセンス diff --git a/doc/DeveloperGuide.ja.md b/doc/DeveloperGuide.ja.md index 7f69cad..228fe1f 100644 --- a/doc/DeveloperGuide.ja.md +++ b/doc/DeveloperGuide.ja.md @@ -1,37 +1,37 @@ -# GGPO開発者向けガイド +# GGPO開発者ガイド -GGPOネットワークライブラリ・開発者向けガイドは、アプリにGGPOネットワークライブラリを実装する開発者向けに用意されたテキストです。 +GGPOネットワークライブラリ開発者ガイドは、アプリケーションにGGPOネットワークライブラリを実装する開発者向けに用意されたドキュメントです。 ## ゲームステートと入力 -ゲームは数多くの変化するパートがあると思います。GGPOはこれら2つだけに依存します。 +ゲームには数多くの変化するパートがあるかと思います。GGPOは次の2つだけに依存します。 -- **ゲームステート**はゲーム内で現在のステートすべてを描写します。シューティングゲームの場合、画面上にある自機と敵機の位置、ショットや敵弾の位置、敵機の残り体力、現在のスコアなどになります。 +- **ゲームステート**はゲームでの全ての状態を表します。シューティングゲームの場合、画面上にある自機と敵機の位置、ショットや敵弾の位置、敵機の体力、現在のスコアなどになります。 -- **ゲーム入力**はゲームステートを変更する一連のものを指します。プレイヤーが操作したジョイスティックやボタンも間違いなく含みますが、目に見えない入力も含みます。例えば、ゲーム内で何かを計算するためにその日の時刻を使用したならば、フレームの最初になるその日の時刻も、また入力になります。 +- **ゲーム入力**はゲームステートを変更する一連のものを指します。言うまでもなく、プレイヤーが操作したジョイスティックやボタンの押下が含まれますが、入力以外のものも含みます。例えば、現在時刻を使って何かを計算した場合、フレームを開始した時の時刻も入力になります。 -ゲームエンジンの中にはゲームステートでも入力でもないものが多数あります。例えばオーディオやビデオレンダラーはゲームの結果に影響を及ぼさないので、ゲームステートではありません。ゲームに影響を及ぼさないような、特別効果を生成する特別効果エンジンがあれば、ゲームステートから除外することができます。 +ゲームエンジンにはゲームステートでも入力でもないものが他にもたくさんあります。例えばオーディオやビデオレンダラーはゲームの結果に影響を与えないため、ゲームステートではありません。ゲームに影響を与えない特殊効果を生成する特殊効果エンジンがあったとしたら、それもゲームステートから除外できます。 ## 同期にステートと入力を使用する -GGPOを使用したゲームで遊ぶ各プレイヤーは、現在遊んでいるゲームの完全なコピーを持っています。両プレイヤーが同じゲーム内容を遊べるよう、GGPOは両プレイヤーにあるゲームステートのコピーを同期し続ける必要があります。プレイヤー間で各フレームごとにゲームステート全コピーを送信することは大きな負担になってしまいます。代わりにGGPOはお互いの入力を送信し、互いのゲームを一つ一つ進めています。この機能のために、ゲームエンジンは3つの条件を満たさねばなりません。 +GGPOを使ったゲームで遊ぶ各プレイヤーは、プレイしているゲームの完全なコピーを持っています。両プレイヤーが同じゲーム内容で遊べるよう、保持しているゲームステートのコピーを同期し続ける必要があります。フレームが進む度にプレイヤー間でゲームステートの全コピーを送信するのは大きな負荷になります。代わりにGGPOはお互いの入力を送信し、各プレイヤーのゲームを進めます。これが機能するには、ゲームエンジンが3つの条件を満たしている必要があります。 -- ゲームシミュレーションが完全に決定性(deterministic)であること。与えられたゲームステートと入力があった場合、1フレームゲームステートを進めた際に全プレイヤーのゲームステートが全く同一の結果にならなければいけません。 -- ゲームステートが完全にカプセル化(encapsulated)されていること、そして直列化が可能(serializable)であること。 -- ゲームエンジンがそのフレーム時のゲーム内容をレンダリングすることなく、ロード、セーブ、フレームのシミュレーションができること。これはロールバック実行の際に使用します。 +- ゲームのシミュレーションは完全に決定的でなければなりません。つまり、特定のゲームステートと入力があった時に、ゲームステートを1フレーム進めると全プレイヤーのゲームステートが同じにならなければいけません。 +- ゲームステートが完全にカプセル化され、シリアライズが可能であること。 +- ゲームエンジンはそのフレームのゲーム内容をレンダリングすることなく、復元、保存、フレームのシミュレーションができなくてはなりません。これはロールバックを実装するために使用されます。 ## プログラミングガイド -以下のセクションはあなたのアプリをGGPOにポーティング、移植する一連の流れを紹介します。GGPO APIの詳細な説明は、以下のGGPO参照セクションをご覧ください。 +次のセクションではあなたのアプリケーションをGGPO上で動作させるための一連の流れを紹介しています。GGPO APIの詳細な説明については、以下のGGPOリファレンスセクションを参照してください。 -### GGPOとのインターフェイス +### GGPOとの繋ぎ込み -GGPOは新旧のゲームエンジンと簡単にインターフェイスできるよう設計されています。GPOSessionCallbacksのフックを介してアプリに呼び出すことにより、大分のロールバックの実装を行います。 +GGPOは新規および既存のゲームエンジンと簡単に繋ぎ込みができるよう設計されています。`GGPOSessionCallbacks`フックを介してアプリケーションを呼び出すことにより、ほとんどのロールバックの実装を行います。 ### GGPOSessionオブジェクトの生成 -`GGPOSession`オブジェクトはGGPOフレームワークへのインターフェイスです。ローカル、IPアドレス、対戦したいプレイヤーをバインドするためのポートを渡す`ggponet_start_session`ファンクションを使用して、作成します。またセッションが1プレイヤー側、2プレイヤー側いずれの場合でも、ゲームステートを管理するコールバック関数で満たされた`GGPOSessionCallbacks`オブジェクトに渡す必要があります。全GGPOSessionCallbackファンクションは実行をしなければなりません。詳細は以下を参照してください。 -例として、ポート8001にバインドしている別プレイヤーと同じホストで新しいセッションをスタートする場合、次のようになります: +`GGPOSession`オブジェクトはGGPOフレームワークへのインターフェースです。ローカルのポートと、対戦したいプレイヤーのIPアドレスとポートを`ggponet_start_session`関数を渡して作成します。またゲームステートを管理するコールバック関数で満たされた`GGPOSessionCallbacks`オブジェクトと、このセッションで遊ぶプレイヤーの数を渡す必要があります。全ての`GGPOSessionCallback`関数を実装しなければなりません。詳細は以下を参照してください。 +例えば、ポート8001にバインドされた別のプレイヤーと同じホストで新しいセッションを開始する場合、次のようになります。 ``` GGPOSession ggpo; @@ -57,16 +57,16 @@ GGPOは新旧のゲームエンジンと簡単にインターフェイスでき -`GGPOSession`オブジェクトは1つのゲームセッションだけに用いられるべきです。別の相手と接続する必要がある場合、`ggpo_close_session`を使用して既存のオブジェクトを閉じ、新たに以下のものを始めてください: +`GGPOSession`オブジェクトは単一のゲームセッションだけに使われるべきです。別の相手と接続する必要がある場合、`ggpo_close_session`を使用して既存のオブジェクトを閉じ、新しいオブジェクトを開始します。 ``` /* Close the current session and start a new one */ ggpo_close_session(ggpo); ``` -### プレイヤーロケーションの送信 +### プレイヤーの場所を送信する -GGPOSessionオブジェクトを作成した時、ゲームに参加しているプレイヤーの数を渡しましたが、どのようにコンタクトをするかが実際には記述されていません。そのためには、それぞれのプレイヤーを記述する`GGPOPlayer`オブジェクトを渡して、`ggpo_add_player`を呼び出します。以下は2プレイヤーで遊ぶ時に`ggpo_add_player`を使用する際の例になります: +GGPOSessionオブジェクトを作成した時、ゲームに参加しているプレイヤーの数を渡しましたが、実際にそれらを連携をする方法について説明していませんでした。これを行うには、各プレイヤーを表す`GGPOPlayer`オブジェクトを`ggpo_add_player`関数に渡して呼び出します。次の例は、2人用のゲームでの`ggpo_add_player`の使い方です。 ``` GGPOPlayer p1, p2; @@ -85,10 +85,10 @@ result = ggpo_add_player(ggpo, &p2, &player_handles[1]); ### ローカルと遠隔プレイヤーによる入力の同期 -入力の同期は各ゲームフレームの最初に行われます。各ローカルプレイヤーの`ggpo_add_local_input`の呼び出しと、 遠隔プレイヤーの入力を取得するために`ggpo_synchronize_input`の呼び出しをすることで行われます。 -`ggpo_synchronize_inputs`の戻り値を必ず確認するようにしてください。`GGPO_OK`以外の値を返す場合、ゲームステートを次に進めるべきではありません。わずかなあいだ、遠隔プレイヤーからパケットを受信していなかったり、内部で行われる予測の限界に達することがあるために、こういったことは頻繁に発生します。 +入力の同期は各ゲームフレームの最初に行われます。各ローカルプレイヤーに対する`ggpo_add_local_input`の呼び出しと、遠隔プレイヤーの入力を取得する`ggpo_synchronize_input`の呼び出しによって行われます。 +`ggpo_synchronize_inputs`の戻り値は必ず確認するようにしてください。`GGPO_OK`以外の値が返ってきた場合、ゲームステートを進めないでください。これは通常、GGPOがしばらくの間、遠隔プレイヤーからパケットを受信せず、内部の予測制限に達したことで発生します。 -例えば、ローカルゲームのコードがこのような場合: +例えば、ローカルゲームのコードが次のようになっている場合、 ``` GameInputs &p1, &p2; @@ -97,7 +97,7 @@ result = ggpo_add_player(ggpo, &p2, &player_handles[1]); AdvanceGameState(&p1, &p2, &gamestate); /* send p1 and p2 to the game */ ``` -以下のように変更するべきです: +次のように変更する必要があります。 ``` GameInputs p[2]; @@ -121,11 +121,11 @@ result = ggpo_add_player(ggpo, &p2, &player_handles[1]); } ``` -ロールバック中に発生したものでも、フレームごとに`ggpo_synchronize_inputs`を呼び出す必要があります。ゲームステートを進めるためには、ローカルコントローラーから得られた値を読むのではなく、常に`ggpo_synchronize_inputs`から返ってくる値を使用してください。ロールバック中、`ggpo_synchronize_inputs`は前のフレームに用いられた値とともに、`ggpo_add_local_input`に渡された値を置き換えます。また、ロールバックの影響を緩和するためにローカルプレイヤー向けの入力遅延を加えた場合、 フレーム遅延が終えるまで`ggpo_add_local_input`に渡される入力は`ggpo_synchronize_inputs`に返されません。 +ロールバック中に発生したものも含め、全てのフレームで`ggpo_synchronize_inputs`を呼び出す必要があります。ゲームステートを進めるためには、ローカルコントローラーから得られた値を読むのではなく、常に`ggpo_synchronize_inputs`から返された値を使用してください。ロールバック中に`ggpo_synchronize_inputs`は`ggpo_add_local_input`に渡された値を前のフレームに使われた値に置き換えます。また、ロールバックの影響を緩和するためにローカルプレイヤー向けの入力遅延を加えた場合、`ggpo_add_local_input`に渡された入力はフレーム遅延が終わるまで`ggpo_synchronize_inputs`に返されません。 -### セーブ、ロード、およびフリーコールバックの実装 +### 保存、復元、解放コールバックの実装 -定期的にゲームステートをセーブ、リストア(復元)するために、GGPOは`load_game_state`と`save_game_state`コールバックを使用します。`save_game_state`関数はゲームの現在のステートを復元し、`buffer`出力パラメーター内に戻るために、十分な情報を含んだバッファを作成する必要があります。`load_game_state`ファンクションは以前にセーブしたバッファからゲームステートを復元します。例として: +GGPOはゲームステートを定期的に保存または復元するために、`load_game_state`と`save_game_state`コールバックを使用します。`save_game_state`関数はゲームの現在のステートを復元し、それを`buffer`出力パラメーターで返すのに十分な情報を含むバッファーを作成する必要があります。`load_game_state`関数は以前に保存したバッファーからゲームステートを復元します。例えば、 ``` struct GameState gamestate; // Suppose the authoritative value of our game's state is in here. @@ -151,7 +151,7 @@ ggpo_load_game_state_callback(unsigned char *buffer, int len) } ``` -これ以上必要がなくなった時は、GGPOがあなたの`save_game_state`コールバックにある割り当てたメモリを破棄するために、`free_buffer`コールバックを呼び出します。 +不要になったら、GGPOは`free_buffer`コールバックを呼び出して、`save_game_state`コールバックで割り当てたメモリを解放します。 ``` void __cdecl @@ -163,49 +163,49 @@ ggpo_free_buffer(void *buffer) ### 残っているコールバックの実装 -先ほど挙げたように、`GGPOSessionCallbacks`構造内にはオプショナルコールバックがありません。最低でも`return true`である必要がありますが、残っているコールバックは即座に実行される必要がありません。詳しい情報はggponet.hのコメントをご覧ください。 +前述のように、`GGPOSessionCallbacks`構造体にはオプション扱いのコールバックはありません。これらは少なくとも`return true`である必要がありますが、残りのコールバックは必ずしもすぐに実装する必要はありません。詳細については`ggponet.h`のコメントを参照してください。 -### GGPOアドバンスとアイドル関数の呼び出し +### ggpo_advance_frameとggpo_idle関数の呼び出し -いよいよ終わりに近づいてきています。大丈夫、お約束します。最後のステップはゲームステートが1フレームごとに進んだことを終えたら、毎回GGPOに通知することです。1フレームを終えた後、次のフレームを始める前に`ggpo_advance_frame`を呼び出すだけです。 +いよいよ終わりに近づいてきました。大丈夫、お約束します。最後のステップはゲームステートを1フレーム進める度にGGPOへ通知することです。1フレームを終えた後、次のフレームを開始する前に`ggpo_advance_frame`を呼び出すだけです。 -GGPOは内部記録のパケットを送受信するために、一定の時間が必要になります。GGPOに許可したミリ秒単位で、最低でもフレームごとに1回は`ggpo_idle function`を呼び出す必要があります。 +GGPOは内部記録を行うパケットを送受信するために、一定の時間が必要になります。GGPOに許可したミリ秒単位で、最低でもフレームごとに1回は`ggpo_idle`関数を呼び出す必要があります。 ## アプリケーションのチューニング: フレーム遅延 vs 投機的実行 -遅延を感じさせないようにするために、GGPOはフレーム遅延と投機的実行の両方を用います。アプリ開発者が遅延入力のフレーム数はどの程度にするか、については選択ができます。もしゲームのフレーム数よりパケットの送信に時間がかかる場合は、GGPOが投機的実行を用いて残りの遅延を感じさせないようにします。もし希望すれば、ゲーム中でもこの数字を変更できます。フレーム遅延の適切な値はゲームによって依存します。以下は役に立つヒントです。 +GGPOは遅延を感じさせないようにするために、フレーム遅延と投機的実行の両方を使用します。これは、アプリケーション開発者が入力を遅延させるフレーム数を選択できるようにすることで実現します。もしゲームのフレーム数よりパケットの送信に時間がかかった場合、GGPOは投機的実行を使って残りの遅延を隠します。この数値は、必要に応じてゲーム中でも調整することができます。フレーム遅延の適切な値はゲームに大きく依存します。役に立つヒントをいくつか紹介しましょう。 -まずはゲームを遊ぶ感覚に影響を与えない範囲で、フレーム遅延を出来るだけ高く設定するようにしてみてください。例えば格闘ゲームであればドット単位の精度を要する操作、寸分狂いもないタイミング、極めて素早く動かすアーケードコントローラーといった要素があります。こういったゲームの場合、中級者の大半は2フレームの遅延に気づき、ベテランプレイヤーであれば1フレームの遅延に気づくこともあります。かたや、厳密なタイミング操作を要求しないボードゲームやパズルゲームなら、4か5のフレーム遅延であればユーザーが遅延に気づき始める前に上手くゲームを進められるかもしれません。 +まずはゲームを遊ぶ感覚に影響を与えない範囲で、フレーム遅延を出来るだけ大きく設定してみてください。例えば格闘ゲームではドット単位の精度、寸分違わぬタイミング、非常に正確なアーケードコントローラーの操作が必要となります。このタイプのゲームでは、ほとんどの中級プレイヤーは2フレームの遅延に気付き、上級プレイヤーであれば1フレームの遅延に気付くこともあります。一方、厳密な操作を必要としないボードゲームやパズルゲームであれば、4~5のフレーム遅延を設定すればユーザーが気付く前に上手くゲームを進められるかもしれません。 -フレーム遅延を高く設定するもう一つの理由は、ロールバック中に発生しうるグリッチ(不具合)を排除することにあります。ロールバックが長くなればなるほど、間違った予測フレームを一時的に表示したことで、本来ないシーンを継ぎ接ぎした様子が表示される可能性が高くなるからです。例えば、ユーザーがボタンを押した瞬間にちょうど2フレーム分全画面がフラッシュする仕様のゲームがあったとします。フレーム遅延を1に設定し、パケット送信に4フレームかかったとすると、この場合はロールバックは約3フレーム分(4 - 1 = 3)になります。フラッシュがロールバックの最初のフレームに発生した場合、2フレームのフラッシュはロールバックで消えてしまい、遠隔で遊ぶプレイヤーはフラッシュの演出が見えなくなってしまいます。この場合、さらに高いフレーム遅延値を設定するか、ロールバック発生後までフラッシュを遅らせるようビデオレンダラーを再設計するか、のどちらかになります。 +フレーム遅延を大きく設定するもうひとつの理由は、ロールバック中に発生し得るグリッチ(不具合)を排除することにあります。ロールバックが長くなればなるほど、間違った予測フレームを一時的に実行したことによって生じた、本来存在しないシーンを継ぎ接ぎした様子が表示される可能性が高くなります。例えば、ユーザーがボタンを押した瞬間に2フレームの画面フラッシュが起きるゲームがあったとします。フレーム遅延を1に設定し、パケット送信に4フレームかかった場合、ロールバックは約3フレーム分(4 - 1 = 3)になります。フラッシュがロールバックの最初のフレームで発生した場合、2フレームのフラッシュはロールバックによって完全に消失してしまい、遠隔で遊ぶプレイヤーはフラッシュ演出を見ることができなくなります。この場合、さらに大きなフレーム遅延値を設定するか、ロールバック発生後までフラッシュを遅らせるようビデオレンダラーを再設計するのが良いでしょう。 ## サンプルアプリケーション -ソースディレクトリ内のVector Warアプリケーションでは、2つのクライアントを同期するGGPOが搭載されています。コマンドライン引数は +ソースディレクトリ内のVector Warには、GGPOを使った2つのクライアントを同期する単純なアプリケーションが含まれています。コマンドライン引数は以下の通りです。 ``` vectorwar.exe ('local' | :) for each player ``` -2~4プレイヤーゲームの始め方についての例は、binディレクトリにある.cmdファイルをご覧ください。 +2~4プレイヤーでのゲーム開始方法の例については、binディレクトリにある.cmdファイルを参照してください。 ## ベストプラクティスとトラブルシューティング -以下はGGPOへアプリを移植する際に検討をしたい、推薦できるベストプラクティスの一覧です。最初の段階からゲームを始めなくとも、ここで推奨している多くは簡単にまねることができます。多くのアプリケーションは推薦している以下の手法に準拠しています。 +以下はアプリケーションをGGPO上で動作させる際に検討したいベストプラクティスの一覧です。これら推奨事項は、まだゲームを作り初めていない段階でも簡単に理解できます。多くのアプリケーションは既にほとんどの推奨事項を満たしています。 -### 非ゲームステートとゲームステートを分ける +### ゲームステートを非ゲームステートから分離する -GGPOは定期的にゲームにおける全ステートのセーブとロードを要求します。多くのゲームにとって、セーブが必要となるステートはゲーム全体のなかでも小さな要素にあたります。ビデオやオーディオレンダラー、テーブルの検索、テクスチャ―サウンドデータ、コードセグメントは大半の場合、フレームからフレームまで一定か、ゲームステートの計算には関与しません。これらはセーブや復元する必要がありません。 +GGPOは定期的にゲームステート全体の保存と復元を要求します。ほとんどのゲームにおいて、保存が必要なステートはゲーム全体のごく一部です。通常、ビデオやオーディオレンダラー、テーブルの検索、テクスチャー、サウンドデータ、コードセグメントは、フレームごとに不変であるか、ゲームステートの計算には影響しません。これらを保存または復元する必要はありません。 -ゲームステートから非ゲームステートを出来る限り分けましょう。例えば、全ゲームステートをC構造体へカプセル化を考えるかもしれません。この二つは、ゲームステートであるもの、そうでないもの、セーブやロード呼び出しの実行に必要でないものをはっきりと分けてくれます(さらなる情報はリファレンスガイドをご覧ください)。 +できるだけゲーム以外の状態をゲームステートから分離する必要があります。例えば、全ゲームステートをC言語の構造体にカプセル化することを考えるかもしれません。これは、ゲームステートであるものとそうでないものが明確に区別され、保存と復元のコールバック実装が簡単になります(詳細についてはリファレンスガイドを参照してください)。 -### ゲームステートを進める際の固定のタイムクオンタムを定義する +### ゲームステートを進める際の固定時間を定義する -GGPOは時折、ロールバックやフレームごとにアプリに対してシングルステップ実行をする必要があります。もしあなたのゲームステートが変数のチックレートで進めているのであれば難しい実行です。レンダーループがそうでなくとも、ゲームステートの進行はフレームにつき固定のタイムクオンタムでするようにしてください。 +GGPOは、フレームごとにアプリケーションのロールバックとシングルステップ実行を必要とすることがあります。もしゲームステートを可変ティックレートで進めている場合、実行は困難になります。レンダーループがそうでない場合でも、フレームごとに固定時間単位でゲームステートを進めるようにしてください。 -### ゲームの一連の流れをレンダリングするところから、ゲームステートのアップデートを分離する +### ゲームループ内にあるレンダリングからゲームステートの更新を分離する -GGPはロールバック中、事前フレームコールバックを何度も呼び出します。ロールバックが終わるまで、ロールバック中に発生したエフェクトやサウンドは遅らせる必要があります。これはレンダーステートからゲームステートを分離することで簡単に行うことができます。そうできたら、以下のようになるでしょう。 +GGPOはロールバック中に、advance frameコールバックを何度も呼び出します。ロールバック中に発生するエフェクトやサウンドはロールバックが完了するまで先延ばしする必要があります。これはゲームステートとレンダーステートを分離することで最も簡単に実現できます。分離が出来たら、ゲームループは次のようになるでしょう。 ``` Bool finished = FALSE; @@ -221,29 +221,29 @@ GGPはロールバック中、事前フレームコールバックを何度も while (!finished); ``` -言い換えると、ゲームステートは入力のみで決定され、レンダリングコードは現在のゲームステートによって動作をします。レンダリングなしで一連の入力をもとに簡単にゲームステートを進める方法であるべきです。 +言い換えると、ゲームステートは入力のみで決定され、レンダリングは現在のゲームステートによって実行される必要があります。また、レンダリングせずに一連の入力を元にゲームステートを簡単に進める方法が必要です。 -### ゲームステートの進行は決定性であること +### ゲームステートの進行が決定的であることを確認する -ゲームステートを特定したら、必ず次のゲームステートはゲーム入力のみで計算されるようにしてください。ゲームステートと入力がすべて正しく特定できたなら、このことは自然と発生しますが、時として手が込む作業です。以下は見落としやすい内容です。 +ゲームステートを特定したら、次のゲームステートが入力のみから計算されることを確認します。これは、全てのゲームステートと入力を正しく識別できていれば自然とそうなりますが、時には注意が必要です。見落とされがちなことをいくつか紹介します。 #### 乱数ジェネレーターに気を付ける -次のゲームステートを計算するうえで、多くのゲームは乱数を使用します。もし乱数を使っていたら、それが両プレイヤーがフレーム0の時に乱数ジェネレーターのシードが同一であること、そしてあなたのゲームステート内に乱数ジェネレーターのステートが含まれていること、といった完全に決定性であることを必ず確認してください。この両方を確認すれば、GGPOが特定のフレームでロールバックが何回も必要になろうとも、そのフレームで生成された乱数は必ず同一になります。 +次のゲームステートを計算するうえで、多くのゲームは乱数を使用します。もし乱数を使う場合、それらが完全に決定的であること、乱数ジェネレーターのシードが両プレイヤーの0フレーム目で同じであること、乱数ジェネレーターの状態がゲームステートに含まれていることを確認してください。これらのことが行われていれば、特定のフレームに対して生成される乱数は、GGPOがそのフレームをロールバックする回数に関係なく、常に同じ値になります。 -#### 外部時間ソース(壁時計時刻)に気を付ける +#### 外部の時刻情報(壁時計時間)に気を付ける -ゲームステートの計算にその日の現在時刻を用いる場合は注意してください。このことでゲームに影響を与える、または他のゲームステートを導く可能性があります(例: タイマーを乱数ジェネレーターのシードに用いる)。2つのコンピューターもしくはゲームコンソールの時刻は、ほぼ同期することはなく、ゲームステート計算に時刻を使用すると同期の問題につながります。ゲームステートに時刻の使用する、またはフレームに入力する一部として、プレイヤーの1人のために現在時刻を含める、そして計算に時刻を常に用いるといったことは排除するべきです。 +ゲームステートの計算に現在時刻を使う場合は注意してください。ゲームに影響を与えたり、別のゲームステートに導く可能性があります(例: 乱数ジェネレーターのシードにタイマーを使う)。2台のコンピューターまたはゲームコンソールの時刻が同期することはほとんどないため、ゲームステートの計算に時刻を使用すると同期のトラブルに繋がります。ゲームステートに時刻を使うのを止めるか、プレイヤーの現在時刻をフレームへの入力の一部として含め、常にその時刻を使って計算を行う必要があります。 -非ゲームステート計算における外部時刻ソースの使用は構いません(例: 画面上の効果時間を計算、またはオーディオサンプルの減衰)。 +ゲームステート以外の計算に外部の時刻情報を使う分には問題ありません(例: 画面上のエフェクト時間の計算やオーディオサンプルの減衰など)。 -### ダングリングリファレンスに気を付ける +### ダングリングポインターに気を付ける -ゲームステートが動的に割り当てられたメモリを含んでいる場合、データをセーブ/ロードする際には、ポインタをリベースするセーブとロードファンクションに十分気を付けてください。緩和をする一つの方法として、ポインタの代わりに割り当てられたメモリを参照するため、ベースとオフセットを使用します。これでリベースが必要なポインタの数が大幅に削減できます。 +ゲームステートに動的に割り当てられたメモリが含まれる場合、データの保存や復元の際に十分に気を付けながらポインターの再配置を行ってください。これを緩和するひとつの方法は、ポインターの代わりにベースとオフセットを使って割り当てられたメモリを参照することです。これにより再配置が必要なポインターの数を大幅に減らすことができます。 -### 静的変数、またはほかの隠れたステートに気を付ける +### 静的変数や隠れたステートに気を付ける -あなたのゲームで用いられている言語は、全ステートを追跡するのを難しくさせている特徴があるかもしれません。Cにある静的自動変数はこの挙動の一例にあたります。すべてのロケーションを追跡して、セーブできるフォームへ変換する必要があります。例として、この2つを比較してください。まずはこちらから: +ゲームが記述されている言語には、全てのステートの追跡を困難にさせる機能があるかもしれません。C言語の静的自動変数はこの動作の一例です。該当する全ての箇所を探し出し、保存可能な形式に変換する必要があります。例えば、以下を見比べてください。 ``` // This will totally get you into trouble. @@ -254,7 +254,7 @@ GGPはロールバック中、事前フレームコールバックを何度も } ``` -次にこちらを: +次のように書き換えます。 ``` // If you must, this is better static int global_counter = 0; /* move counter to a global */ @@ -274,14 +274,14 @@ GGPはロールバック中、事前フレームコールバックを何度も } ``` -### GGPO同期テスト機能をたくさん使用しましょう +### GGPOの同期テスト機能をたくさん使いましょう -GGPOにあなたのアプリを移植したら、リークがありやすいゲームステートの可能性がある同期問題、この追跡ができる`ggpo_start_synctest`関数を使うことができます。 +あなたのアプリケーションがGGPO上で動作するようになったら、`ggpo_start_synctest`関数を使ってゲームステートの漏れによる同期問題を追跡することができます。 -この同期テストセッションは特別に作られもので、シミュレーション決定性内にあるエラーを探すために設計された、シングルプレーヤーセッションです。同期テストセッションを実行すると、GGPOはあなたのゲーム内でフレームごとに1フレームのロールバックを行います。初回に行った時のフレームステートと、ロールバック中に行ったステートを比較して、異なっていればエラーが浮上します。ゲーム実行中に`ggpo_log`関数を使用した場合、エラーを追跡するために初期フレームのログとロールバックフレームのログをdiffすることができます。 +この同期テストセッションは、シミュレーション決定論におけるエラーを探すために設計された特別なシングルプレイヤーセッションです。同期テストセッションで実行すると、GGPOは全てのフレームに対して1フレームのロールバックを行います。フレームが最初に実行されたときのステートとロールバック中に実行されたステートを比較し、それらが異なっていた場合はエラーを発生させます。ゲーム実行中に`ggpo_log`関数を使用すると、初回フレームのログとロールバックフレームのログを比較してエラーを追跡することができます。 -ゲームコードを書き込んでいる時に開発システムで継続的に同期テストを実行することで、非同期を発生するバグをすぐに見つけることができます。 +ゲームコードを書いている時に開発システム上で同期テストを継続的に実行することで、同期ズレの原因となったバグをすぐに見つけることができます。 ## さらに詳しく知りたい方は -この文章はGGPOの基本的な特徴を紹介しています。さらに知りたい方は、ggponet.hヘッダーにあるコメント、そしてコードを直接読むことをお勧めします。それではみなさん頑張ってください! +このドキュメントではGGPOの基本的な機能について紹介しました。さらに知りたい方は、`ggponet.h`ヘッダーにあるコメント、そしてコードを直接読むことをお勧めします。それではみなさん頑張ってください! diff --git a/doc/README.ja.md b/doc/README.ja.md index 7b24996..f2781bc 100644 --- a/doc/README.ja.md +++ b/doc/README.ja.md @@ -57,7 +57,7 @@ GGPOは遠隔の入力を受信したら、その都度前回のフレームで ## GGPOインタフェース(GGPO Interface) -GGPOインターフェイスはP2Pと同期テストバックエンド間の詳細な実装を抽象化しています。適切なバックエンドは`ggpo_start_session`か`ggpo_start_synctest`エントリーポイントを呼び出した時に、自動的に生成されます。 +GGPOインターフェースはP2Pと同期テストバックエンド間の詳細な実装を抽象化しています。適切なバックエンドは`ggpo_start_session`か`ggpo_start_synctest`エントリーポイントを呼び出した時に、自動的に生成されます。 ## P2Pバックエンド(P2P Backend) @@ -85,4 +85,4 @@ UDPオブジェクトは単純なUDPパケットの送受信を行います。 ## 同期テストバックエンド(Sync Test Backend) -(図にはありません)同期テストバックエンドは、P2Pバックエンドがアプリのセーブステートと決定的に機能上実行していることを確認するときに同じ同期オブジェクトを使用します。同期テストの使用に関する詳しい情報は、開発者向けガイドを参照してください。 +(図にはありません)同期テストバックエンドは、P2Pバックエンドがアプリのセーブステートと決定的に機能上実行していることを確認するときに同じ同期オブジェクトを使用します。同期テストの使用に関する詳しい情報は、開発者ガイドを参照してください。 diff --git a/doc/README.md b/doc/README.md index 5c6044d..c2477c8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,4 +1,4 @@ -# What Is GGPO? +# What's GGPO? Created in 2009, the GGPO networking SDK pioneered the use of rollback networking in peer-to-peer games. It's designed specifically to hide network latency in fast paced, twitch style games which require very precise inputs and frame perfect execution.