Merge pull request #46 from seasidelab/fix-dev-guide

Fix Japanese developer guide
This commit is contained in:
Tony Cannon
2019-11-07 09:39:58 -08:00
committed by GitHub
4 changed files with 65 additions and 65 deletions

View File

@@ -25,13 +25,13 @@ Windowsのビルドは[Visual Studio 2019](https://visualstudio.microsoft.com/do
## サンプルアプリケーション ## サンプルアプリケーション
ソースディレクトリ内のVector Warアプリケーションでは、2つのクライアントを同期するGGPOが搭載されています。コマンドライン引数は ソースディレクトリ内のVector Warには、GGPOを使った2つのクライアントを同期する単純なアプリケーションが含まれています。コマンドライン引数は以下の通りです。
``` ```
vectorwar.exe <localport> <num players> ('local' | <remote ip>:<remote port>) for each player vectorwar.exe <localport> <num players> ('local' | <remote ip>:<remote port>) for each player
``` ```
24プレイヤーゲームの始め方についての例は、binディレクトリにある.cmdファイルをご覧ください。 24プレイヤーでのゲーム開始方法の例については、binディレクトリにある.cmdファイルを参照してください。
## ライセンス ## ライセンス

View File

@@ -1,37 +1,37 @@
# GGPO開発者向けガイド # GGPO開発者ガイド
GGPOネットワークライブラリ開発者向けガイドは、アプリにGGPOネットワークライブラリを実装する開発者向けに用意されたテキストです。 GGPOネットワークライブラリ開発者ガイドは、アプリケーションにGGPOネットワークライブラリを実装する開発者向けに用意されたドキュメントです。
## ゲームステートと入力 ## ゲームステートと入力
ゲームは数多くの変化するパートがあると思います。GGPOはこれら2つだけに依存します。 ゲームは数多くの変化するパートがあると思います。GGPOは次の2つだけに依存します。
- **ゲームステート**はゲーム内で現在のステートすべてを描写します。シューティングゲームの場合、画面上にある自機と敵機の位置、ショットや敵弾の位置、敵機の残り体力、現在のスコアなどになります。 - **ゲームステート**はゲームでの全ての状態を表します。シューティングゲームの場合、画面上にある自機と敵機の位置、ショットや敵弾の位置、敵機の体力、現在のスコアなどになります。
- **ゲーム入力**はゲームステートを変更する一連のものを指します。プレイヤーが操作したジョイスティックやボタンも間違いなく含みますが、目に見えない入力も含みます。例えば、ゲーム内で何かを計算するためにその日の時刻を使用したならば、フレームの最初になるその日の時刻も、また入力になります。 - **ゲーム入力**はゲームステートを変更する一連のものを指します。言うまでもなく、プレイヤーが操作したジョイスティックやボタンの押下が含まれますが、入力以外のものも含みます。例えば、現在時刻を使って何かを計算した場合、フレームを開始した時の時刻も入力になります。
ゲームエンジンの中にはゲームステートでも入力でもないものが多数あります。例えばオーディオやビデオレンダラーはゲームの結果に影響を及ぼさないので、ゲームステートではありません。ゲームに影響を及ぼさないような、特別効果を生成する特効果エンジンがあれば、ゲームステートから除外することができます。 ゲームエンジンにはゲームステートでも入力でもないものが他にもたくさんあります。例えばオーディオやビデオレンダラーはゲームの結果に影響を与えないため、ゲームステートではありません。ゲームに影響を与えない特殊効果を生成する特効果エンジンがあったとしたら、それもゲームステートから除外できます。
## 同期にステートと入力を使用する ## 同期にステートと入力を使用する
GGPOを使用したゲームで遊ぶ各プレイヤーは、現在遊んでいるゲームの完全なコピーを持っています。両プレイヤーが同じゲーム内容遊べるよう、GGPOは両プレイヤーにあるゲームステートのコピーを同期し続ける必要があります。プレイヤー間で各フレームごとにゲームステート全コピーを送信することは大きな負になってしまいます。代わりにGGPOはお互いの入力を送信し、互いのゲームを一つ一つ進めています。この機能のために、ゲームエンジン3つの条件を満たさねばなりません GGPOを使たゲームで遊ぶ各プレイヤーは、プレイしているゲームの完全なコピーを持っています。両プレイヤーが同じゲーム内容遊べるよう、保持しているゲームステートのコピーを同期し続ける必要があります。フレームが進む度にプレイヤー間でゲームステート全コピーを送信するは大きな負になます。代わりにGGPOはお互いの入力を送信し、各プレイヤーのゲームを進めます。これが機能するには、ゲームエンジン3つの条件を満たしている必要があります
- ゲームシミュレーション完全に決定性(deterministic)であること。与えられたゲームステートと入力があった場合、1フレームゲームステートを進めた際に全プレイヤーのゲームステートが全く同一の結果にならなければいけません。 - ゲームシミュレーション完全に決定的でなければなりません。つまり、特定のゲームステートと入力があった時に、ゲームステートを1フレーム進めると全プレイヤーのゲームステートが同じにならなければいけません。
- ゲームステートが完全にカプセル化(encapsulated)されていること、そして直列化が可能(serializable)であること。 - ゲームステートが完全にカプセル化され、シリアライズが可能であること。
- ゲームエンジンそのフレームのゲーム内容をレンダリングすることなく、ロード、セーブ、フレームのシミュレーションができること。これはロールバック実行の際に使用ます。 - ゲームエンジンそのフレームのゲーム内容をレンダリングすることなく、復元、保存、フレームのシミュレーションができなくてはなりません。これはロールバックを実装するために使用されます。
## プログラミングガイド ## プログラミングガイド
以下のセクションはあなたのアプリをGGPOにポーティング、移植する一連の流れを紹介します。GGPO APIの詳細な説明は、以下のGGPO参照セクションをご覧ください。 のセクションはあなたのアプリケーションをGGPO上で動作させるための一連の流れを紹介しています。GGPO APIの詳細な説明については、以下のGGPOリファレンスセクションを参照してください。
### GGPOとのインターフェイス ### GGPOとの繋ぎ込み
GGPOは新のゲームエンジンと簡単にインターフェイスできるよう設計されています。GPOSessionCallbacksフックを介してアプリ呼び出すことにより、大分のロールバックの実装を行います。 GGPOは新規および既存のゲームエンジンと簡単に繋ぎ込みができるよう設計されています。`GGPOSessionCallbacks`フックを介してアプリケーションを呼び出すことにより、ほとんどのロールバックの実装を行います。
### GGPOSessionオブジェクトの生成 ### GGPOSessionオブジェクトの生成
`GGPOSession`オブジェクトはGGPOフレームワークへのインターフェスです。ローカル、IPアドレス、対戦したいプレイヤーをバインドするためのポートを渡す`ggponet_start_session`ファンクションを使用して作成します。またセッションが1プレイヤー側、2プレイヤー側いずれの場合でも、ゲームステートを管理するコールバック関数で満たされた`GGPOSessionCallbacks`オブジェクト渡す必要があります。全GGPOSessionCallbackファンクションは実行をしなければなりません。詳細は以下を参照してください。 `GGPOSession`オブジェクトはGGPOフレームワークへのインターフェスです。ローカルのポートと、対戦したいプレイヤーのIPアドレスとポートを`ggponet_start_session`関数を渡して作成します。またゲームステートを管理するコールバック関数で満たされた`GGPOSessionCallbacks`オブジェクトと、このセッションで遊ぶプレイヤーの数を渡す必要があります。全ての`GGPOSessionCallback`関数を実装しなければなりません。詳細は以下を参照してください。
として、ポート8001にバインドしている別プレイヤーと同じホストで新しいセッションをスタートする場合、次のようになります: えば、ポート8001にバインドされた別のプレイヤーと同じホストで新しいセッションを開始する場合、次のようになります
``` ```
GGPOSession ggpo; 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 */ /* Close the current session and start a new one */
ggpo_close_session(ggpo); 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; GGPOPlayer p1, p2;
@@ -85,10 +85,10 @@ result = ggpo_add_player(ggpo, &p2, &player_handles[1]);
### ローカルと遠隔プレイヤーによる入力の同期 ### ローカルと遠隔プレイヤーによる入力の同期
入力の同期は各ゲームフレームの最初に行われます。各ローカルプレイヤー`ggpo_add_local_input`の呼び出しと、 遠隔プレイヤーの入力を取得するために`ggpo_synchronize_input`の呼び出しをすることで行われます。 入力の同期は各ゲームフレームの最初に行われます。各ローカルプレイヤーに対する`ggpo_add_local_input`の呼び出しと、遠隔プレイヤーの入力を取得する`ggpo_synchronize_input`の呼び出しによって行われます。
`ggpo_synchronize_inputs`の戻り値必ず確認するようにしてください。`GGPO_OK`以外の値を返す場合、ゲームステートを次に進めるべきではありません。わずかなあいだ、遠隔プレイヤーからパケットを受信していなかったり、内部で行われる予測の限界に達することがあるために、こういったことは頻繁に発生します。 `ggpo_synchronize_inputs`の戻り値必ず確認するようにしてください。`GGPO_OK`以外の値が返ってきた場合、ゲームステートを進めないでください。これは通常、GGPOがしばらくの間、遠隔プレイヤーからパケットを受信せず、内部の予測制限に達したことで発生します。
例えば、ローカルゲームのコードがのよう場合: 例えば、ローカルゲームのコードがのようになっている場合
``` ```
GameInputs &p1, &p2; 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 */ AdvanceGameState(&p1, &p2, &gamestate); /* send p1 and p2 to the game */
``` ```
以下のように変更するべきです: のように変更する必要があります。
``` ```
GameInputs p[2]; 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. 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 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 投機的実行 ## アプリケーションのチューニング: フレーム遅延 vs 投機的実行
遅延を感じさせないようにするために、GGPOはフレーム遅延と投機的実行の両方を用います。アプリ開発者が遅延入力のフレーム数はどの程度にするか、については選択ができます。もしゲームのフレーム数よりパケットの送信に時間がかか場合、GGPO投機的実行を用いて残りの遅延を感じさせないようにします。もし希望すれば、ゲーム中でもこの数字を変更できます。フレーム遅延の適切な値はゲームによって依存します。以下は役に立つヒントです GGPOは遅延を感じさせないようにするために、フレーム遅延と投機的実行の両方を使用します。これは、アプリケーション開発者が入力を遅延させるフレーム数を選択できるようにすることで実現します。もしゲームのフレーム数よりパケットの送信に時間がかかった場合、GGPO投機的実行を使って残りの遅延を隠します。この数値は、必要に応じてゲーム中でも調整することができます。フレーム遅延の適切な値はゲームに大きく依存します。役に立つヒントをいくつか紹介しましょう
まずはゲームを遊ぶ感覚に影響を与えない範囲で、フレーム遅延を出来るだけく設定するようにしてみてください。例えば格闘ゲームであればドット単位の精度を要する操作、寸分狂いもないタイミング、極めて素早く動かすアーケードコントローラーといった要素があります。こういったゲームの場合、中級者の大半は2フレームの遅延に気き、ベテランプレイヤーであれば1フレームの遅延に気くこともあります。かたや、厳密なタイミング操作を要求しないボードゲームやパズルゲームなら、45のフレーム遅延であればユーザーが遅延に気づき始める前に上手くゲームを進められるかもしれません。 まずはゲームを遊ぶ感覚に影響を与えない範囲で、フレーム遅延を出来るだけ大きく設定してみてください。例えば格闘ゲームでドット単位の精度、寸分違わぬタイミング、非常に正確なアーケードコントローラーの操作が必要となります。このタイプのゲームでは、ほとんどの中級プレイヤーは2フレームの遅延に気き、上級プレイヤーであれば1フレームの遅延に気くこともあります。一方、厳密な操作を必要としないボードゲームやパズルゲームであれば、45のフレーム遅延を設定すればユーザーが気付く前に上手くゲームを進められるかもしれません。
フレーム遅延をく設定するもうつの理由は、ロールバック中に発生しるグリッチ(不具合)を排除することにあります。ロールバックが長くなればなるほど、間違った予測フレームを一時的に表示したことで、本来ないシーンを継ぎ接ぎした様子が表示される可能性が高くなるからです。例えば、ユーザーがボタンを押した瞬間にちょうど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 <localport> <num players> ('local' | <remote ip>:<remote port>) for each player vectorwar.exe <localport> <num players> ('local' | <remote ip>:<remote port>) for each player
``` ```
24プレイヤーゲームの始め方についての例は、binディレクトリにある.cmdファイルをご覧ください。 24プレイヤーでのゲーム開始方法の例については、binディレクトリにある.cmdファイルを参照してください。
## ベストプラクティスとトラブルシューティング ## ベストプラクティスとトラブルシューティング
以下はGGPOへアプリを移植する際に検討したい、推薦できるベストプラクティスの一覧です。最初の段階からゲームを始めなくとも、ここで推奨している多くは簡単にまねることができます。多くのアプリケーションは推薦している以下の手法に準拠しています。 以下はアプリケーションをGGPO上で動作させる際に検討したいベストプラクティスの一覧です。これら推奨事項は、まだゲームを作り初めていない段階でも簡単に理解できます。多くのアプリケーションは既にほとんどの推奨事項を満たしています。
### ゲームステートゲームステートを分け ### ゲームステートを非ゲームステートから分離す
GGPOは定期的にゲームにおける全ステートのセーブとロードを要求します。多くのゲームにとって、セーブが必要となるステートはゲーム全体のなかでも小さな要素にあたります。ビデオやオーディオレンダラー、テーブルの検索、テクスチャサウンドデータ、コードセグメントは大半の場合、フレームからフレームまで一定か、ゲームステートの計算には関与しません。これらはセーブや復元する必要ありません。 GGPOは定期的にゲームステート全体の保存と復元を要求します。ほとんどのゲームにおいて、保存が必要なステートはゲーム全体のごく一部です。通常、ビデオやオーディオレンダラー、テーブルの検索、テクスチャー、サウンドデータ、コードセグメントは、フレームごとに不変であるか、ゲームステートの計算には影響しません。これらを保存または復元する必要ありません。
ゲームステートから非ゲームステートを出来る限り分けましょう。例えば、全ゲームステートをC構造体カプセル化を考えるかもしれません。この二つは、ゲームステートであるものそうでないもの、セーブやロード呼び出しの実行に必要でないものをはっきりと分けてくれます(さらなる情報はリファレンスガイドをご覧ください)。 できるだけゲーム以外の状態をゲームステートから分離する必要があります。例えば、全ゲームステートをC言語の構造体カプセル化することを考えるかもしれません。こは、ゲームステートであるものそうでないものが明確に区別され、保存と復元のコールバック実装が簡単になります(詳細についてはリファレンスガイドを参照してください)。
### ゲームステートを進める際の固定のタイムクオンタムを定義する ### ゲームステートを進める際の固定時間を定義する
GGPOは時折、ロールバックやフレームごとにアプリに対してシングルステップ実行をする必要があります。もしあなたのゲームステートが変数のチックレートで進めているのであれば難しい実行です。レンダーループがそうでなくとも、ゲームステートの進行はフレームにつき固定のタイムクオンタムでするようにしてください。 GGPOは、フレームごとにアプリケーションのロールバックとシングルステップ実行を必要とすることがあります。もしゲームステートを可変ティックレートで進めている場合、実行は困難になります。レンダーループがそうでない場合でも、フレームごとに固定時間単位でゲームステートを進めるようにしてください。
### ゲームの一連の流れをレンダリングするところからゲームステートのアップデートを分離する ### ゲームループ内にあるレンダリングからゲームステートの更新を分離する
GGPはロールバック中、事前フレームコールバックを何度も呼び出します。ロールバックが終わるまで、ロールバック中に発生したエフェクトやサウンドは遅らせる必要があります。これはレンダーステートからゲームステートを分離することで簡単に行うことができます。そうできたら、以下のようになるでしょう。 GGPOはロールバック中に、advance frameコールバックを何度も呼び出します。ロールバック中に発生するエフェクトやサウンドはロールバックが完了するまで先延ばしする必要があります。これはゲームステートとレンダーステートを分離することで最も簡単に実現できます。分離が出来たら、ゲームループは次のようになるでしょう。
``` ```
Bool finished = FALSE; Bool finished = FALSE;
@@ -221,29 +221,29 @@ GGPはロールバック中、事前フレームコールバックを何度も
while (!finished); while (!finished);
``` ```
言い換えると、ゲームステートは入力のみで決定され、レンダリングコードは現在のゲームステートによって動作をします。レンダリングなしで一連の入力をもとに簡単にゲームステートを進める方法であるべきです。 言い換えると、ゲームステートは入力のみで決定され、レンダリングは現在のゲームステートによって実行される必要があります。また、レンダリングせずに一連の入力をにゲームステートを簡単に進める方法が必要です。
### ゲームステートの進行決定であること ### ゲームステートの進行決定であることを確認する
ゲームステートを特定したら、必ず次のゲームステートはゲーム入力のみ計算されるようにしてください。ゲームステートと入力がすべて正しく特定できたなら、このことは自然と発生しますが、時として手が込む作業です。以下は見落としやすい内容です。 ゲームステートを特定したら、次のゲームステート入力のみから計算されることを確認します。これは、全てのゲームステートと入力を正しく識別できていれば自然とそうなりますが、時には注意が必要です。見落とされがちなことをいくつか紹介します。
#### 乱数ジェネレーターに気を付ける #### 乱数ジェネレーターに気を付ける
次のゲームステートを計算するうえで、多くのゲームは乱数を使用します。もし乱数を使っていたら、それが両プレイヤーがフレーム0の時に乱数ジェネレーターのシードが同一であること、そしてあなたのゲームステート内に乱数ジェネレーターのステート含まれていること、といった完全に決定性であることを必ず確認してください。この両方を確認すれば、GGPOが特定のフレームロールバックが何回も必要になろうとも、そのフレームで生成された乱数は必ず同一になります。 次のゲームステートを計算するうえで、多くのゲームは乱数を使用します。もし乱数を使う場合、それらが完全に決定的であること、乱数ジェネレーターのシードが両プレイヤーの0フレーム目で同じであること、乱数ジェネレーターの状態がゲームステート含まれていることを確認してください。これらのことが行われていれば、特定のフレームに対して生成される乱数は、GGPOがのフレームロールバックする回数に関係なく、常に同じ値になります。
#### 外部時間ソース(壁時計時)に気を付ける #### 外部の時刻情報(壁時計時)に気を付ける
ゲームステートの計算にその日の現在時刻を用いる場合は注意してください。このことでゲームに影響を与える、または他のゲームステート導く可能性があります(例: タイマーを乱数ジェネレーターのシードに用いる)。2のコンピューターもしくはゲームコンソールの時刻は、ほぼ同期することはなく、ゲームステート計算に時刻を使用すると同期の問題につながります。ゲームステートに時刻の使用する、またはフレームに入力する一部として、プレイヤーの1人のために現在時刻を含める、そして計算に時刻を常に用いるといったことは排除するべきです。 ゲームステートの計算に現在時刻を使う場合は注意してください。ゲームに影響を与えたり、別のゲームステート導く可能性があります(例: 乱数ジェネレーターのシードにタイマーを使う)。2のコンピューターまたはゲームコンソールの時刻同期することはほとんどないため、ゲームステート計算に時刻を使用すると同期のトラブルに繋がります。ゲームステートに時刻を使うのを止めるか、プレイヤーの現在時刻をフレームへの入力の一部として含め、常にその時刻を使って計算を行う必要があります。
ゲームステート計算における外部時刻ソースの使用は構いません(例: 画面上の効果時間計算、またはオーディオサンプルの減衰)。 ゲームステート以外の計算に外部時刻情報を使う分には問題ありません(例: 画面上のエフェクト時間計算オーディオサンプルの減衰など)。
### ダングリングリファレンスに気を付ける ### ダングリングポインターに気を付ける
ゲームステート動的に割り当てられたメモリを含んでいる場合、データをセーブ/ロードする際には、ポインタをリベースするセーブとロードファンクションに十分気を付けてください。緩和するつの方法として、ポインタの代わりに割り当てられたメモリを参照するため、ベースとオフセットを使用します。これでリベースが必要なポインタの数大幅に減できます。 ゲームステート動的に割り当てられたメモリが含まれる場合、データの保存や復元の際に十分に気を付けながらポインターの再配置を行ってください。これを緩和するひとつの方法、ポインタの代わりにベースとオフセットを使って割り当てられたメモリを参照することです。これにより再配置が必要なポインタの数大幅に減らすことができます。
### 静的変数、またはほかの隠れたステートに気を付ける ### 静的変数隠れたステートに気を付ける
あなたのゲームで用いられている言語は、全ステート追跡するのを難しくさせている特徴があるかもしれません。Cにある静的自動変数はこの動の一例にあたります。すべてのロケーションを追跡して、セーブできるフォームへ変換する必要があります。例として、この2つを比較してください。まずはこちらから: ゲームが記述されている言語は、全てのステート追跡を困難にさせる機能があるかもしれません。C言語の静的自動変数はこの動の一例です。該当する全ての箇所を探し出し、保存可能な形式に変換する必要があります。例えば、以下を見比べてください。
``` ```
// This will totally get you into trouble. // This will totally get you into trouble.
@@ -254,7 +254,7 @@ GGPはロールバック中、事前フレームコールバックを何度も
} }
``` ```
にこちらを: のように書き換えます。
``` ```
// If you must, this is better // If you must, this is better
static int global_counter = 0; /* move counter to a global */ 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`ヘッダーにあるコメント、そしてコードを直接読むことをお勧めします。それではみなさん頑張ってください!

View File

@@ -57,7 +57,7 @@ GGPOは遠隔の入力を受信したら、その都度前回のフレームで
## GGPOインタフェース(GGPO Interface) ## GGPOインタフェース(GGPO Interface)
GGPOインターフェスはP2Pと同期テストバックエンド間の詳細な実装を抽象化しています。適切なバックエンドは`ggpo_start_session``ggpo_start_synctest`エントリーポイントを呼び出した時に、自動的に生成されます。 GGPOインターフェスはP2Pと同期テストバックエンド間の詳細な実装を抽象化しています。適切なバックエンドは`ggpo_start_session``ggpo_start_synctest`エントリーポイントを呼び出した時に、自動的に生成されます。
## P2Pバックエンド(P2P Backend) ## P2Pバックエンド(P2P Backend)
@@ -85,4 +85,4 @@ UDPオブジェクトは単純なUDPパケットの送受信を行います。
## 同期テストバックエンド(Sync Test Backend) ## 同期テストバックエンド(Sync Test Backend)
(図にはありません)同期テストバックエンドは、P2Pバックエンドがアプリのセーブステートと決定的に機能上実行していることを確認するときに同じ同期オブジェクトを使用します。同期テストの使用に関する詳しい情報は、開発者向けガイドを参照してください。 (図にはありません)同期テストバックエンドは、P2Pバックエンドがアプリのセーブステートと決定的に機能上実行していることを確認するときに同じ同期オブジェクトを使用します。同期テストの使用に関する詳しい情報は、開発者ガイドを参照してください。

View File

@@ -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. 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.