Adjust Japanese documentation.
This commit is contained in:
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
2~4プレイヤーゲームの始め方についての例は、binディレクトリにある.cmdファイルをご覧ください。
|
2~4プレイヤーでのゲーム開始方法の例については、binディレクトリにある.cmdファイルを参照してください。
|
||||||
|
|
||||||
## ライセンス
|
## ライセンス
|
||||||
|
|
||||||
|
|||||||
@@ -4,34 +4,34 @@ GGPOネットワークライブラリ開発者ガイドは、アプリケーシ
|
|||||||
|
|
||||||
## ゲームステートと入力
|
## ゲームステートと入力
|
||||||
|
|
||||||
ゲームは数多くの変化するパートがあると思います。GGPOは次の2つだけに依存します。
|
ゲームには数多くの変化するパートがあるかと思います。GGPOは次の2つだけに依存します。
|
||||||
|
|
||||||
- **ゲームステート**はゲーム内で現在の状態全てを表します。シューティングゲームの場合、画面上にある自機と敵機の位置、ショットや敵弾の位置、敵機の残り体力、現在のスコアなどになります。
|
- **ゲームステート**はゲームでの全ての状態を表します。シューティングゲームの場合、画面上にある自機と敵機の位置、ショットや敵弾の位置、敵機の体力、現在のスコアなどになります。
|
||||||
|
|
||||||
- **ゲーム入力**はゲームステートを変更する一連のものを指します。プレイヤーが操作したジョイスティックやボタンも間違いなく含みますが、目に見えない入力も含みます。例えば、ゲーム内で何かを計算するために現在時刻を使用したならば、フレームの最初になる現在時刻も、また入力になります。
|
- **ゲーム入力**はゲームステートを変更する一連のものを指します。言うまでもなく、プレイヤーが操作したジョイスティックやボタンの押下が含まれますが、入力以外のものも含みます。例えば、現在時刻を使って何かを計算した場合、フレームを開始した時の時刻も入力になります。
|
||||||
|
|
||||||
ゲームエンジンの中にはゲームステートでも入力でもないものが多数あります。例えばオーディオやビデオレンダラーはゲームの結果に影響を与えないので、ゲームステートではありません。ゲームに影響を与えないような、特殊効果を生成する特殊効果エンジンがあれば、ゲームステートから除外することができます。
|
ゲームエンジンにはゲームステートでも入力でもないものが他にもたくさんあります。例えばオーディオやビデオレンダラーはゲームの結果に影響を与えないため、ゲームステートではありません。ゲームに影響を与えない特殊効果を生成する特殊効果エンジンがあったとしたら、それもゲームステートから除外できます。
|
||||||
|
|
||||||
## 同期にステートと入力を使用する
|
## 同期にステートと入力を使用する
|
||||||
|
|
||||||
GGPOを使ったゲームで遊ぶ各プレイヤーは、現在遊んでいるゲームの完全なコピーを持っています。両プレイヤーが同じゲーム内容を遊べるよう、GGPOは両プレイヤーにあるゲームステートのコピーを同期し続ける必要があります。プレイヤー間で各フレームごとにゲームステート全コピーを送信することは大きな負荷になってしまいます。代わりにGGPOはお互いの入力を送信し、互いのゲームを進めています。この機能のために、ゲームエンジンは3つの条件を満たさねばなりません。
|
GGPOを使ったゲームで遊ぶ各プレイヤーは、プレイしているゲームの完全なコピーを持っています。両プレイヤーが同じゲーム内容で遊べるよう、保持しているゲームステートのコピーを同期し続ける必要があります。フレームが進む度にプレイヤー間でゲームステートの全コピーを送信するのは大きな負荷になります。代わりにGGPOはお互いの入力を送信し、各プレイヤーのゲームを進めます。これが機能するには、ゲームエンジンが3つの条件を満たしている必要があります。
|
||||||
|
|
||||||
- ゲームシミュレーションが完全に決定的であること。与えられたゲームステートと入力があった場合、1フレームゲームステートを進めた際に全プレイヤーのゲームステートが全く同一の結果にならなければいけません。
|
- ゲームのシミュレーションは完全に決定的でなければなりません。つまり、特定のゲームステートと入力があった時に、ゲームステートを1フレーム進めると全プレイヤーのゲームステートが同じにならなければいけません。
|
||||||
- ゲームステートが完全にカプセル化され、シリアライズが可能であること。
|
- ゲームステートが完全にカプセル化され、シリアライズが可能であること。
|
||||||
- ゲームエンジンがそのフレーム時のゲーム内容をレンダリングすることなく、復元、保存、フレームのシミュレーションができること。これはロールバックを実装する際に使用します。
|
- ゲームエンジンはそのフレームのゲーム内容をレンダリングすることなく、復元、保存、フレームのシミュレーションができなくてはなりません。これはロールバックを実装するために使用されます。
|
||||||
|
|
||||||
## プログラミングガイド
|
## プログラミングガイド
|
||||||
|
|
||||||
次のセクションはあなたのアプリケーションをGGPOに移植する一連の流れを紹介します。GGPO APIの詳細な説明は、以下のGGPOリファレンスセクションを参照してください。
|
次のセクションではあなたのアプリケーションをGGPO上で動作させるための一連の流れを紹介しています。GGPO APIの詳細な説明については、以下のGGPOリファレンスセクションを参照してください。
|
||||||
|
|
||||||
### GGPOとのインターフェース
|
### GGPOとの繋ぎ込み
|
||||||
|
|
||||||
GGPOは新規および既存のゲームエンジンと簡単に繋ぎ込みができるよう設計されています。`GGPOSessionCallbacks`フックを介してアプリケーションに呼び出すことにより、ほとんどのロールバックの実装を行います。
|
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;
|
||||||
@@ -64,9 +64,9 @@ GGPOは新規および既存のゲームエンジンと簡単に繋ぎ込みが
|
|||||||
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_advance_frameとggpo_idle関数の呼び出し
|
### ggpo_advance_frameとggpo_idle関数の呼び出し
|
||||||
|
|
||||||
いよいよ終わりに近づいてきています。大丈夫、お約束します。最後のステップはゲームステートが1フレームごとに進んだら、毎回GGPOに通知することです。1フレームを終えた後、次のフレームを開始する前に`ggpo_advance_frame`を呼び出すだけです。
|
いよいよ終わりに近づいてきました。大丈夫、お約束します。最後のステップはゲームステートを1フレーム進める度にGGPOへ通知することです。1フレームを終えた後、次のフレームを開始する前に`ggpo_advance_frame`を呼び出すだけです。
|
||||||
|
|
||||||
GGPOは内部記録のパケットを送受信するために、一定の時間が必要になります。GGPOに許可したミリ秒単位で、最低でもフレームごとに1回は`ggpo_idle`関数を呼び出す必要があります。
|
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は、フレームごとにアプリケーションのロールバックとシングルステップ実行を必要とすることがあります。もしゲームステートを可変ティックレートで進めている場合、実行は困難になります。レンダーループがそうでない場合でも、フレームごとに固定時間単位でゲームステートを進めるようにしてください。
|
||||||
|
|
||||||
### ゲームループでのレンダリングから、ゲームステートの更新を分離する
|
### ゲームループ内にあるレンダリングからゲームステートの更新を分離する
|
||||||
|
|
||||||
GGPOはロールバック中、事前フレームコールバックを何度も呼び出します。ロールバックが完了するまで、ロールバック中に発生したエフェクトやサウンドは先延ばしする必要があります。これはレンダーステートからゲームステートを分離することで簡単に実現できます。完了したら、以下のようになるでしょう。
|
GGPOはロールバック中に、advance frameコールバックを何度も呼び出します。ロールバック中に発生するエフェクトやサウンドはロールバックが完了するまで先延ばしする必要があります。これはゲームステートとレンダーステートを分離することで最も簡単に実現できます。分離が出来たら、ゲームループは次のようになるでしょう。
|
||||||
|
|
||||||
```
|
```
|
||||||
Bool finished = FALSE;
|
Bool finished = FALSE;
|
||||||
@@ -221,29 +221,29 @@ GGPOはロールバック中、事前フレームコールバックを何度も
|
|||||||
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 @@ GGPOはロールバック中、事前フレームコールバックを何度も
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
次にこちらを。
|
次のように書き換えます。
|
||||||
```
|
```
|
||||||
// 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 @@ GGPOはロールバック中、事前フレームコールバックを何度も
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### GGPO同期テスト機能をたくさん使用しましょう
|
### GGPOの同期テスト機能をたくさん使いましょう
|
||||||
|
|
||||||
GGPOにあなたのアプリケーションを移植したら、漏れが出やすいゲームステートの可能性がある同期問題、この追跡ができる`ggpo_start_synctest`関数を使うことができます。
|
あなたのアプリケーションがGGPO上で動作するようになったら、`ggpo_start_synctest`関数を使ってゲームステートの漏れによる同期問題を追跡することができます。
|
||||||
|
|
||||||
この同期テストセッションは特別に作られもので、シミュレーション決定論におけるエラーを探すために設計された、シングルプレイヤーセッションです。同期テストセッションを実行すると、GGPOはあなたのゲーム内でフレームごとに1フレームのロールバックを行います。初回に行った時のフレームステートと、ロールバック中に行ったステートを比較して、異なっていればエラーを発生させます。ゲーム実行中に`ggpo_log`関数を使用した場合、エラーを追跡するために初期フレームのログとロールバックフレームのログを比較することができます。
|
この同期テストセッションは、シミュレーション決定論におけるエラーを探すために設計された特別なシングルプレイヤーセッションです。同期テストセッションで実行すると、GGPOは全てのフレームに対して1フレームのロールバックを行います。フレームが最初に実行されたときのステートとロールバック中に実行されたステートを比較し、それらが異なっていた場合はエラーを発生させます。ゲーム実行中に`ggpo_log`関数を使用すると、初回フレームのログとロールバックフレームのログを比較してエラーを追跡することができます。
|
||||||
|
|
||||||
ゲームコードを書いている時に開発システムで継続的に同期テストを実行することで、非同期を発生するバグをすぐに見つけることができます。
|
ゲームコードを書いている時に開発システム上で同期テストを継続的に実行することで、同期ズレの原因となったバグをすぐに見つけることができます。
|
||||||
|
|
||||||
## さらに詳しく知りたい方は
|
## さらに詳しく知りたい方は
|
||||||
|
|
||||||
このドキュメントはGGPOの基本的な機能を紹介しています。さらに知りたい方は、`ggponet.h`ヘッダーにあるコメント、そしてコードを直接読むことをお勧めします。それではみなさん頑張ってください!
|
このドキュメントではGGPOの基本的な機能について紹介しました。さらに知りたい方は、`ggponet.h`ヘッダーにあるコメント、そしてコードを直接読むことをお勧めします。それではみなさん頑張ってください!
|
||||||
|
|||||||
Reference in New Issue
Block a user