@@ -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は新規および既存 のゲームエンジンと簡単に繋ぎ込みが できるよう設計されています。`G GPOSessionCallbacks` フックを介してアプリケーションを 呼び出すことにより、ほとんど のロールバックの実装を行います。
### 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フレームの遅延に気づ くこともあります。かたや 、厳密なタイミング操作を要求 しないボードゲームやパズルゲームなら 、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 <localport> <num players> ('local' | <remote ip>:<remote port>) for each player
vectorwar.exe <localport> <num players> ('local' | <remote ip>:<remote port>) 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;
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` ヘッダーにあるコメント、そしてコードを直接読むことをお勧めします。それではみなさん頑張ってください!