Merge branch 'master' into dev/crossplatform
This commit is contained in:
40
README.ja.md
Normal file
40
README.ja.md
Normal file
@@ -0,0 +1,40 @@
|
||||

|
||||
|
||||
## GGPOとは?
|
||||
|
||||
従来の技術はプレイヤーの入力に遅延を織り込んで通信を行っており、その結果反応が遅く、ラグを感じるプレイ感になっていました。ロールバックネットワーキングはプレイヤーの入力を即座に送信するため、入力予測と投機的実行を行うことにより、遅延ゼロの回線環境をもたらせるのです。ロールバックがあれば、タイミングや相手の動きや効果音に対しての反応、指が覚えている入力、これらオフラインで行えた内容が、そのままオンラインでも行えます。GGPOネットワーキングSDKは、ロールバックネットワーキングを新作や発売されているゲームに極力簡単に組み込めるよう作られています。
|
||||
|
||||
これまでのGGPOについてさらに知りたい方は、http://ggpo.net/をご覧ください。
|
||||
|
||||
このリポジトリにコードやドキュメント、SDKのサンプルアプリケーションが収められています。
|
||||
|
||||
|
||||
## ビルド
|
||||
|
||||
GGPOのビルドは現在のところWindowsのみになりますが、他プラットフォームへのポートも現在行っています。
|
||||
|
||||
|
||||
### Windows
|
||||
|
||||
Windowsのビルドは[Visual Studio 2019](https://visualstudio.microsoft.com/downloads/)と[CMake](https://cmake.org/download/)が必要になります。ご使用の前に、どちらもインストールされていることをご確認ください。またインストール時、パスにCMakeを追加してください。
|
||||
|
||||
- Visual Studio 2019のソリューションファイルを作成するため、SDKのルートディレクトリで`build_windows.cmd`を実行します。
|
||||
- コンパイルをするため、Visual Studio 2019で`build/GGPO.sln`ソリューションを開きます。
|
||||
|
||||
好みにあわせて`cmake-gui`で実行も出来ます。
|
||||
|
||||
|
||||
## サンプルアプリケーション
|
||||
|
||||
ソースディレクトリ内のVector Warアプリケーションでは、2つのクライアントを同期するGGPOが搭載されています。コマンドライン引数は
|
||||
|
||||
```
|
||||
vectorwar.exe <localport> <num players> ('local' | <remote ip>:<remote port>) for each player
|
||||
```
|
||||
|
||||
2~4プレイヤーゲームの始め方についての例は、binディレクトリにある.cmdファイルをご覧ください。
|
||||
|
||||
|
||||
## ライセンス
|
||||
|
||||
GGPOはMITライセンスの元で利用ができます。つまり、GGPOは商用、非商用のどちらでも無料で利用ができます。クレジットの掲載、帰属は必要ありませんが、してくださると大変うれしく思います。
|
||||
@@ -1,5 +1,7 @@
|
||||

|
||||
|
||||
_[](https://ci.appveyor.com/project/pond3r/ggpo/branch/master)_
|
||||
|
||||
## What's GGPO?
|
||||
Traditional techniques account for network transmission time by adding delay to a players input, resulting in a sluggish, laggy game-feel. Rollback networking uses input prediction and speculative execution to send player inputs to the game immediately, providing the illusion of a zero-latency network. Using rollback, the same timings, reactions visual and audio queues, and muscle memory your players build up playing offline translate directly online. The GGPO networking SDK is designed to make incorporating rollback networking into new and existing games as easy as possible.
|
||||
|
||||
|
||||
24
appveyor.yml
Normal file
24
appveyor.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
# GGPO AppVeyor CI Configuration
|
||||
|
||||
version: 2.4.3.{build}
|
||||
|
||||
image: Visual Studio 2019
|
||||
|
||||
skip_commits:
|
||||
files:
|
||||
- doc/*
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
configuration:
|
||||
- Debug
|
||||
- Release
|
||||
platform:
|
||||
- x64
|
||||
|
||||
before_build:
|
||||
- configure_windows.cmd --no-prompt
|
||||
|
||||
build:
|
||||
project: build\GGPO.sln
|
||||
@@ -1,3 +0,0 @@
|
||||
@echo off
|
||||
cmake -G "Visual Studio 16 2019" -A x64 -B build -DBUILD_SHARED_LIBS=off
|
||||
pause
|
||||
14
ci/build_windows.sh
Normal file
14
ci/build_windows.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -x
|
||||
|
||||
WORKSPACE="workspace-${RANDOM}"
|
||||
AGENT_IP="$(gcloud compute instances describe ggpo-ci-build-win-01 --zone=us-central1-a --format='get(networkInterfaces[0].accessConfigs.natIP)')"
|
||||
SSH="ssh -o StrictHostKeyChecking=no"
|
||||
tar -cvf ggpo.tar . &> /dev/null
|
||||
|
||||
${SSH} ponder@${AGENT_IP} "mkdir C:\\workspace\\${WORKSPACE}"
|
||||
scp ggpo.tar "ponder@${AGENT_IP}:/workspace/${WORKSPACE}"
|
||||
${SSH} ponder@${AGENT_IP} "cd C:\\workspace\\${WORKSPACE} && tar -xvf ggpo.tar && ci\\build_windows_agent.cmd"
|
||||
${SSH} ponder@${AGENT_IP} "rmdir /q/s C:\\workspace\\${WORKSPACE}"
|
||||
|
||||
rm ggpo.tar
|
||||
11
ci/build_windows_agent.cmd
Normal file
11
ci/build_windows_agent.cmd
Normal file
@@ -0,0 +1,11 @@
|
||||
cmake -G "Visual Studio 16 2019" -A x64 -B build -DBUILD_SHARED_LIBS=off
|
||||
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat"
|
||||
echo on
|
||||
|
||||
devenv.exe "build\GGPO.sln" /build Release /out release.log
|
||||
type release.log
|
||||
|
||||
devenv.exe "build\GGPO.sln" /build Debug /out debug.log
|
||||
type debug.log
|
||||
|
||||
BIN
ci/id_rsa.enc
Normal file
BIN
ci/id_rsa.enc
Normal file
Binary file not shown.
30
cloudbuild.yaml
Normal file
30
cloudbuild.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# Decrypt the file containing the key
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/gcloud'
|
||||
args:
|
||||
- kms
|
||||
- decrypt
|
||||
- --ciphertext-file=./ci/id_rsa.enc
|
||||
- --plaintext-file=/root/.ssh/id_rsa
|
||||
- --location=global
|
||||
- --keyring=ggpo-ci-keyring
|
||||
- --key=buildagent-key
|
||||
volumes:
|
||||
- name: 'ssh'
|
||||
path: /root/.ssh
|
||||
- name: 'gcr.io/cloud-builders/git'
|
||||
entrypoint: bash
|
||||
args:
|
||||
- '-c'
|
||||
- |
|
||||
chmod 600 /root/.ssh/id_rsa
|
||||
volumes:
|
||||
- name: 'ssh'
|
||||
path: /root/.ssh
|
||||
- name: 'gcr.io/cloud-builders/git'
|
||||
entrypoint: bash
|
||||
args:
|
||||
- ./ci/build_windows.sh
|
||||
volumes:
|
||||
- name: 'ssh'
|
||||
path: /root/.ssh
|
||||
9
configure_windows.cmd
Normal file
9
configure_windows.cmd
Normal file
@@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
|
||||
cmake -G "Visual Studio 16 2019" -A x64 -B build -DBUILD_SHARED_LIBS=off
|
||||
|
||||
IF "%1"=="--no-prompt" goto :done
|
||||
:: pause so the user can see the output if they double clicked the configure script
|
||||
pause
|
||||
|
||||
:done
|
||||
302
doc/DeveloperGuide.ja.md
Normal file
302
doc/DeveloperGuide.ja.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# GGPO開発者向けガイド
|
||||
|
||||
GGPOネットワークライブラリ・開発者向けガイドは、アプリにGGPOネットワークライブラリを実装する開発者向けに用意されたテキストです。
|
||||
|
||||
## ゲームステートと入力
|
||||
|
||||
ゲームは数多くの変化するパートがあると思います。GGPOはこれら2つだけに依存します。
|
||||
|
||||
- **ゲームステート**はゲーム内で現在のステートすべてを描写します。シューティングゲームの場合、画面上にある自機と敵機の位置、ショットや敵弾の位置、敵機の残り体力、現在のスコアなどになります。
|
||||
|
||||
- **ゲーム入力**はゲームステートを変更する一連のものを指します。プレイヤーが操作したジョイスティックやボタンも間違いなく含みますが、目に見えない入力も含みます。例えば、ゲーム内で何かを計算するためにその日の時刻を使用したならば、フレームの最初になるその日の時刻も、また入力になります。
|
||||
|
||||
ゲームエンジンの中にはゲームステートでも入力でもないものが多数あります。例えばオーディオやビデオレンダラーはゲームの結果に影響を及ぼさないので、ゲームステートではありません。ゲームに影響を及ぼさないような、特別効果を生成する特別効果エンジンがあれば、ゲームステートから除外することができます。
|
||||
|
||||
|
||||
## 同期にステートと入力を使用する
|
||||
|
||||
GGPOを使用したゲームで遊ぶ各プレイヤーは、現在遊んでいるゲームの完全なコピーを持っています。両プレイヤーが同じゲーム内容を遊べるよう、GGPOは両プレイヤーにあるゲームステートのコピーを同期し続ける必要があります。プレイヤー間で各フレームごとにゲームステート全コピーを送信することは大きな負担になってしまいます。代わりにGGPOはお互いの入力を送信し、互いのゲームを一つ一つ進めています。この機能のために、ゲームエンジンは3つの条件を満たさねばなりません。
|
||||
|
||||
- ゲームシミュレーションが完全に決定性(deterministic)であること。与えられたゲームステートと入力があった場合、1フレームゲームステートを進めた際に全プレイヤーのゲームステートが全く同一の結果にならなければいけません。
|
||||
- ゲームステートが完全にカプセル化(encapsulated)されていること、そして直列化が可能(serializable)であること。
|
||||
- ゲームエンジンがそのフレーム時のゲーム内容をレンダリングすることなく、ロード、セーブ、フレームのシミュレーションができること。これはロールバック実行の際に使用します。
|
||||
|
||||
|
||||
## プログラミングガイド
|
||||
|
||||
以下のセクションはあなたのアプリをGGPOにポーティング、移植する一連の流れを紹介します。GGPO APIの詳細な説明は、以下のGGPO参照セクションをご覧ください。
|
||||
|
||||
|
||||
### GGPOとのインターフェイス
|
||||
|
||||
GGPOは新旧のゲームエンジンと簡単にインターフェイスできるよう設計されています。GPOSessionCallbacksのフックを介してアプリに呼び出すことにより、大分のロールバックの実装を行います。
|
||||
|
||||
|
||||
### GGPOSessionオブジェクトの生成
|
||||
|
||||
`GGPOSession`オブジェクトはGGPOフレームワークへのインターフェイスです。ローカル、IPアドレス、対戦したいプレイヤーをバインドするためのポートを渡す`ggponet_start_session`ファンクションを使用して、作成します。またセッションが1プレイヤー側、2プレイヤー側いずれの場合でも、ゲームステートを管理するコールバック関数で満たされた`GGPOSessionCallbacks`オブジェクトに渡す必要があります。全GGPOSessionCallbackファンクションは実行をしなければなりません。詳細は以下を参照してください。例として、ポート8001にバインドしている別プレイヤーと同じホストで新しいセッションをスタートする場合、次のようになります:
|
||||
|
||||
```
|
||||
GGPOSession ggpo;
|
||||
GGPOErrorCode result;
|
||||
GGPOSessionCallbacks cb;
|
||||
|
||||
/* fill in all callback functions */
|
||||
cb.begin_game = vw_begin_game_callback;
|
||||
cb.advance_frame = vw_advance_frame_callback;
|
||||
cb.load_game_state = vw_load_game_state_callback;
|
||||
cb.save_game_state = vw_save_game_state_callback;
|
||||
cb.free_buffer = vw_free_buffer;
|
||||
cb.on_event = vw_on_event_callback;
|
||||
|
||||
/* Start a new session */
|
||||
result = ggpo_start_session(&ggpo, // the new session object
|
||||
&cb, // our callbacks
|
||||
"test_app", // application name
|
||||
2, // 2 players
|
||||
sizeof(int), // size of an input packet
|
||||
8001); // our local udp port
|
||||
```
|
||||
|
||||
`GGPOSession`オブジェクトは1つのゲームセッションだけに用いられるべきです。 別の相手と接続する必要がある場合、`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`を使用する際の例になります:
|
||||
|
||||
```
|
||||
GGPOPlayer p1, p2;
|
||||
GGPOPlayerHandle player_handles[2];
|
||||
|
||||
p1.size = p2.size = sizeof(GGPOPlayer);
|
||||
p1.type = GGPO_PLAYERTYPE_LOCAL; // local player
|
||||
p2.type = GGPO_PLAYERTYPE_REMOTE; // remote player
|
||||
strcpy(p2.remote.ip_address, "192.168.0.100"); // ip addess of the player
|
||||
p2.remote.ip_address.port = 8001; // port of that player
|
||||
|
||||
result = ggpo_add_player(ggpo, &p1, &player_handles[0]);
|
||||
...
|
||||
result = ggpo_add_player(ggpo, &p2, &player_handles[1]);
|
||||
```
|
||||
|
||||
### ローカルと遠隔プレイヤーによる入力の同期
|
||||
|
||||
入力の同期は各ゲームフレームの最初に行われます。各ローカルプレイヤーの`ggpo_add_local_input`の呼び出しと、 遠隔プレイヤーの入力を取得するために`ggpo_synchronize_input`の呼び出しをすることで行われます。`ggpo_synchronize_inputs`の戻り値を必ず確認するようにしてください。`GGPO_OK`以外の値を返す場合、ゲームステートを次に進めるべきではありません。わずかなあいだ、遠隔プレイヤーからパケットを受信していなかったり、内部で行われる予測の限界に達することがあるために、こういったことは頻繁に発生します。
|
||||
|
||||
例えば、ローカルゲームのコードがこのような場合:
|
||||
|
||||
```
|
||||
GameInputs &p1, &p2;
|
||||
GetControllerInputs(0, &p1); /* read p1’s controller inputs */
|
||||
GetControllerInputs(1, &p2); /* read p2’s controller inputs */
|
||||
AdvanceGameState(&p1, &p2, &gamestate); /* send p1 and p2 to the game */
|
||||
```
|
||||
|
||||
以下のように変更するべきです:
|
||||
|
||||
```
|
||||
GameInputs p[2];
|
||||
GetControllerInputs(0, &p[0]); /* read the controller */
|
||||
|
||||
/* notify ggpo of the local player's inputs */
|
||||
result = ggpo_add_local_input(ggpo, // the session object
|
||||
player_handles[0], // handle for p1
|
||||
&p[0], // p1's inputs
|
||||
sizeof(p[0])); // size of p1's inputs
|
||||
|
||||
/* synchronize the local and remote inputs */
|
||||
if (GGPO_SUCCEEDED(result)) {
|
||||
result = ggpo_synchronize_inputs(ggpo, // the session object
|
||||
p, // array of inputs
|
||||
sizeof(p)); // size of all inputs
|
||||
if (GGPO_SUCCEEDED(result)) {
|
||||
/* pass both inputs to our advance function */
|
||||
AdvanceGameState(&p[0], &p[1], &gamestate);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
ロールバック中に発生したものでも、フレームごとに`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`ファンクションは以前にセーブしたバッファからゲームステートを復元します。例として:
|
||||
|
||||
```
|
||||
struct GameState gamestate; // Suppose the authoritative value of our game's state is in here.
|
||||
|
||||
bool __cdecl
|
||||
ggpo_save_game_state_callback(unsigned char **buffer, int *len,
|
||||
int *checksum, int frame)
|
||||
{
|
||||
*len = sizeof(gamestate);
|
||||
*buffer = (unsigned char *)malloc(*len);
|
||||
if (!*buffer) {
|
||||
return false;
|
||||
}
|
||||
memcpy(*buffer, &gamestate, *len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool __cdecl
|
||||
ggpo_load_game_state_callback(unsigned char *buffer, int len)
|
||||
{
|
||||
memcpy(&gamestate, buffer, len);
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
これ以上必要がなくなった時は、GGPOがあなたの`save_game_state`コールバックにある割り当てたメモリを破棄するために、`free_buffer`コールバックを呼び出します。
|
||||
|
||||
```
|
||||
void __cdecl
|
||||
ggpo_free_buffer(void *buffer)
|
||||
{
|
||||
free(buffer);
|
||||
}
|
||||
```
|
||||
|
||||
### 残っているコールバックの実装
|
||||
|
||||
先ほど挙げたように、`GGPOSessionCallbacks`構造内にはオプショナルコールバックがありません。 最低でも`return true`である必要がありますが、残っているコールバックは即座に実行される必要がありません。詳しい情報はggponet.hのコメントをご覧ください。
|
||||
|
||||
|
||||
### GGPOアドバンスとアイドル関数の呼び出し
|
||||
|
||||
いよいよ終わりに近づいてきています。大丈夫、お約束します。最後のステップはゲームステートが1フレームごとに進んだことを終えたら、毎回GGPOに通知することです。1フレームを終えた後、次のフレームを始める前に`ggpo_advance_frame`を呼び出すだけです。
|
||||
|
||||
GGPOは内部記録のパケットを送受信するために、一定の時間が必要になります。GGPOに許可したミリ秒単位で、最低でもフレームごとに1回は`ggpo_idle function`を呼び出す必要があります。
|
||||
|
||||
|
||||
## アプリケーションのチューニング: フレーム遅延 vs 投機的実行
|
||||
|
||||
遅延を感じさせないようにするために、GGPOはフレーム遅延と投機的実行の両方を用います。アプリ開発者が遅延入力のフレーム数はどの程度にするか、については選択ができます。もしゲームのフレーム数よりパケットの送信に時間がかかる場合は、GGPOが投機的実行を用いて残りの遅延を感じさせないようにします。もし希望すれば、ゲーム中でもこの数字を変更できます。フレーム遅延の適切な値はゲームによって依存します。以下は役に立つヒントです。
|
||||
|
||||
まずはゲームを遊ぶ感覚に影響を与えない範囲で、フレーム遅延を出来るだけ高く設定するようにしてみてください。例えば格闘ゲームであればドット単位の精度を要する操作、寸分狂いもないタイミング、極めて素早く動かすアーケードコントローラーといった要素があります。こういったゲームの場合、中級者の大半は2フレームの遅延に気づき、ベテランプレイヤーであれば1フレームの遅延に気づくこともあります。かたや、厳密なタイミング操作を要求しないボードゲームやパズルゲームなら、4か5のフレーム遅延であればユーザーが遅延に気づき始める前に上手くゲームを進められるかもしれません。
|
||||
|
||||
フレーム遅延を高く設定するもう一つの理由は、ロールバック中に発生しうるグリッチ(不具合)を排除することにあります。ロールバックが長くなればなるほど、間違った予測フレームを一時的に表示したことで、本来ないシーンを継ぎ接ぎした様子が表示される可能性が高くなるからです。例えば、ユーザーがボタンを押した瞬間にちょうど2フレーム分全画面がフラッシュする仕様のゲームがあったとします。フレーム遅延を1に設定し、パケット送信に4フレームかかったとすると、この場合はロールバックは約3フレーム分(4 - 1 = 3)になります。フラッシュがロールバックの最初のフレームに発生した場合、2フレームのフラッシュはロールバックで消えてしまい、遠隔で遊ぶプレイヤーはフラッシュの演出が見えなくなってしまいます。この場合、さらに高いフレーム遅延値を設定するか、ロールバック発生後までフラッシュを遅らせるようビデオレンダラーを再設計するか、のどちらかになります。
|
||||
|
||||
|
||||
## サンプルアプリケーション
|
||||
|
||||
ソースディレクトリ内のVector Warアプリケーションでは、2つのクライアントを同期するGGPOが搭載されています。コマンドライン引数は
|
||||
|
||||
```
|
||||
vectorwar.exe <localport> <num players> ('local' | <remote ip>:<remote port>) for each player
|
||||
```
|
||||
|
||||
2~4プレイヤーゲームの始め方についての例は、binディレクトリにある.batファイルをご覧ください。
|
||||
|
||||
|
||||
## ベストプラクティスとトラブルシューティング
|
||||
|
||||
以下はGGPOへアプリを移植する際に検討をしたい、推薦できるベストプラクティスの一覧です。最初の段階からゲームを始めなくとも、ここで推奨している多くは簡単にまねることができます。多くのアプリケーションは推薦している以下の手法に準拠しています。
|
||||
|
||||
|
||||
### 非ゲームステートとゲームステートを分ける
|
||||
|
||||
GGPOは定期的にゲームにおける全ステートのセーブとロードを要求します。多くのゲームにとって、セーブが必要となるステートはゲーム全体のなかでも小さな要素にあたります。ビデオやオーディオレンダラー、テーブルの検索、テクスチャ―サウンドデータ、コードセグメントは大半の場合、フレームからフレームまで一定か、ゲームステートの計算には関与しません。これらはセーブや復元する必要がありません。
|
||||
|
||||
ゲームステートから非ゲームステートを出来る限り分けましょう。例えば、全ゲームステートをC構造体へカプセル化を考えるかもしれません。この二つは、ゲームステートであるもの、そうでないもの、セーブやロード呼び出しの実行に必要でないものをはっきりと分けてくれます(さらなる情報はリファレンスガイドをご覧ください)。
|
||||
|
||||
|
||||
### ゲームステートを進める際の固定のタイムクオンタムを定義する
|
||||
|
||||
GGPOは時折、ロールバックやフレームごとにアプリに対してシングルステップ実行をする必要があります。もしあなたのゲームステートが変数のチックレートで進めているのであれば難しい実行です。レンダーループがそうでなくとも、ゲームステートの進行はフレームにつき固定のタイムクオンタムでするようにしてください。
|
||||
|
||||
|
||||
### ゲームの一連の流れをレンダリングするところから、ゲームステートのアップデートを分離する
|
||||
|
||||
GGPはロールバック中、事前フレームコールバックを何度も呼び出します。ロールバックが終わるまで、ロールバック中に発生したエフェクトやサウンドは遅らせる必要があります。これはレンダーステートからゲームステートを分離することで簡単に行うことができます。そうできたら、以下のようになるでしょう。
|
||||
|
||||
```
|
||||
Bool finished = FALSE;
|
||||
GameState state;
|
||||
Inputs inputs;
|
||||
|
||||
do {
|
||||
GetControllerInputs(&inputs);
|
||||
finished = AdvanceGameState(&inputs, &state);
|
||||
if (!finished) {
|
||||
RenderCurrentFrame(&gamestate);
|
||||
}
|
||||
while (!finished);
|
||||
```
|
||||
|
||||
言い換えると、ゲームステートは入力のみで決定され、レンダリングコードは現在のゲームステートによって動作をします。レンダリングなしで一連の入力をもとに簡単にゲームステートを進める方法であるべきです。
|
||||
|
||||
|
||||
### ゲームステートの進行は決定性であること
|
||||
|
||||
ゲームステートを特定したら、必ず次のゲームステートはゲーム入力のみで計算されるようにしてください。ゲームステートと入力がすべて正しく特定できたなら、このことは自然と発生しますが、時として手が込む作業です。以下は見落としやすい内容です。
|
||||
|
||||
|
||||
#### 乱数ジェネレーターに気を付ける
|
||||
|
||||
次のゲームステートを計算するうえで、多くのゲームは乱数を使用します。もし乱数を使っていたら、それが両プレイヤーがフレーム0の時に乱数ジェネレーターのシードが同一であること、そしてあなたのゲームステート内に乱数ジェネレーターのステートが含まれていること、といった完全に決定性であることを必ず確認してください。この両方を確認すれば、GGPOが特定のフレームでロールバックが何回も必要になろうとも、そのフレームで生成された乱数は必ず同一になります。
|
||||
|
||||
|
||||
#### 外部時間ソース(壁時計時刻)に気を付ける
|
||||
|
||||
ゲームステートの計算にその日の現在時刻を用いる場合は注意してください。このことでゲームに影響を与える、または他のゲームステートを導く可能性があります(例: タイマーを乱数ジェネレーターのシードに用いる)。2つのコンピューターもしくはゲームコンソールの時刻は、ほぼ同期することはなく、ゲームステート計算に時刻を使用すると同期の問題につながります。ゲームステートに時刻の使用する、またはフレームに入力する一部として、プレイヤーの1人のために現在時刻を含める、そして計算に時刻を常に用いるといったことは排除するべきです。
|
||||
|
||||
非ゲームステート計算における外部時刻ソースの使用は構いません(例: 画面上の効果時間を計算、またはオーディオサンプルの減衰)。
|
||||
|
||||
|
||||
### ダングリングリファレンスに気を付ける
|
||||
|
||||
ゲームステートが動的に割り当てられたメモリを含んでいる場合、データをセーブ/ロードする際には、ポインタをリベースするセーブとロードファンクションに十分気を付けてください。緩和をする一つの方法として、ポインタの代わりに割り当てられたメモリを参照するため、ベースとオフセットを使用します。これでリベースが必要なポインタの数が大幅に削減できます。
|
||||
|
||||
|
||||
### 静的変数、またはほかの隠れたステートに気を付ける
|
||||
|
||||
あなたのゲームで用いられている言語は、全ステートを追跡するのを難しくさせている特徴があるかもしれません。Cにある静的自動変数はこの挙動の一例にあたります。すべてのロケーションを追跡して、セーブできるフォームへ変換する必要があります。例として、この2つを比較してください。まずはこちらから:
|
||||
|
||||
```
|
||||
// This will totally get you into trouble.
|
||||
int get_next_counter(void) {
|
||||
static int counter = 0; /* no way to roll this back... */
|
||||
counter++;
|
||||
return counter;
|
||||
}
|
||||
```
|
||||
|
||||
次にこちらを:
|
||||
```
|
||||
// If you must, this is better
|
||||
static int global_counter = 0; /* move counter to a global */
|
||||
|
||||
int get_next_counter(void) {
|
||||
global_counter++;
|
||||
return global_counter; /* use the global value */
|
||||
}
|
||||
|
||||
bool __cdecl
|
||||
ggpo_load_game_state_callback(unsigned char *buffer, int len)
|
||||
{
|
||||
...
|
||||
global_counter = *((int *)buffer) /* restore it in load callback */
|
||||
...
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### GGPO同期テスト機能をたくさん使用しましょう
|
||||
|
||||
GGPOにあなたのアプリを移植したら、リークがありやすいゲームステートの可能性がある同期問題、この追跡ができる`ggpo_start_synctest`関数を使うことができます。
|
||||
|
||||
この同期テストセッションは特別に作られもので、シミュレーション決定性内にあるエラーを探すために設計された、シングルプレーヤーセッションです。同期テストセッションを実行すると、GGPOはあなたのゲーム内でフレームごとに1フレームのロールバックを行います。初回に行った時のフレームステートと、ロールバック中に行ったステートを比較して、異なっていればエラーが浮上します。ゲーム実行中に`ggpo_log`関数を使用した場合、エラーを追跡するために初期フレームのログとロールバックフレームのログをdiffすることができます。
|
||||
|
||||
ゲームコードを書き込んでいる時に開発システムで継続的に同期テストを実行することで、非同期を発生するバグをすぐに見つけることができます。
|
||||
|
||||
|
||||
## さらに詳しく知りたい方は
|
||||
|
||||
この文章はGGPOの基本的な特徴を紹介しています。さらに知りたい方は、ggponet.hヘッダーにあるコメント、そしてコードを直接読むことをお勧めします。それではみなさん頑張ってください!
|
||||
@@ -1,90 +1,101 @@
|
||||
# GGPOとは何か?
|
||||
# GGPOとは
|
||||
|
||||
2009年に作成されたGGPOネットワーキングSDKは、ピアツーピアゲームでのロールバックネットワーキングの使用を開拓しました。 非常に正確な入力とフレームの完全な実行を必要とする速いペースの単収縮スタイルのゲームでネットワーク遅延を隠すために特別に設計されています。
|
||||
2009年に開発されたGGPOネットワーキングSDKは、P2Pゲームにおけるロールバックネットワーキング実用化の先駆けとなったシステムです。正確な入力や、フレームごとの完璧な処理を必要とし、ゲーム展開が速くかつ配信に適したゲームにおいて、ネットワーク遅延を目立たなくさせることに重点を置いて開発されました。
|
||||
|
||||
従来の手法では、プレーヤーの入力に遅延を追加することでネットワークの送信時間を考慮し、結果としてゲームの動きが鈍くなります。 ロールバックネットワーキングは、入力予測と投機的実行を使用して、プレーヤーの入力を直ちにゲームに送信し、ゼロ遅延ネットワークの錯覚を与えます。ロールバックネットコードを用いて、プレイヤーがオフラインでプレイすることで蓄積した筋肉のメモリ、タイミング、リアクション、およびをビジュアルキューとオーディオキューオンラインでも直接に利用できます。 GGPOネットワーキングSDKは、ロールバックネットワーキングをできるだけ簡単に、新規および既存のゲームに組み込むことができるように設計されています。
|
||||
従来の技術はプレイヤーの入力に遅延を織り込んで通信を行っており、その結果反応が遅く、ラグを感じるプレイ感になっていました。ロールバックネットワーキングは入力予測と投機的実行を行って、プレイヤーの入力を即座に送信するため、遅延を感じさせないネット環境をもたらします。ロールバックがあれば、タイミングや相手の動きや効果音に対する反応、指が覚えている入力、これらオフラインで行えた内容が、そのままオンラインでも行えます。GGPOネットワーキングSDKは、ロールバックネットワーキングを新作や発売されているゲームに極力簡単に組み込めるよう作られています。
|
||||
|
||||
|
||||
# 仕組みは?
|
||||
# 仕組み
|
||||
|
||||
ロールバックネットワーキングは、完全に決定的なピアツーピアエンジンに統合されるように設計されています。完全な決定論により、単に同じ入力をフィードするだけで、すべてのプレイヤーコンピューターで同じ方法でゲームがプレイされることが保証されます。これを実現する1つの方法は、ネットワーク経由ですべてのプレーヤーの入力を交換し、すべてのプレーヤーがピアからすべての入力を受け取ったときにのみゲームプレイロジックのフレームを実行することです。そうすると、多くの場合、結果としてゲームプレイの反応が遅く、応答が遅くなります。ネットワークを介して入力を取得するのに時間がかかるほど、ゲームは遅くなります。
|
||||
ロールバックネットワーキングは決定的P2Pエンジンに統合できるよう設計されています。完全に決定的なエンジンなら、同じ入力をした場合にゲームは必ず同じ内容のプログラム再生をします。その内容を実現する一つの方法としては、ネットワーク上の全プレイヤーと入力のやりとりをする方法があげられますが、これは全プレイヤーがピアから入力を全て受け取った時にのみゲームプレイロジックが1フレームだけ実行される形になります。この方法ではゲーム内でキャラの動きがぎくしゃくし、反応の悪いゲーム内容になりがちです。ネットワークを介して入力を受け取る時間が長くなるほど、ゲーム展開も遅くなってしまいます。
|
||||
|
||||
|
||||
## インプットディレイによるネットワーキング使用の場合
|
||||
## 入力遅延を用いたネットワーキング
|
||||
|
||||
### 理論的に…
|
||||
### 理論上では…
|
||||
|
||||
以下の系統図をご覧ください。統計図に理想的な0msの遅延のネットワークで2つのクライエントが同期されています。 プレヤー1のインプットとゲームステートは青い色で、プレヤー2のは赤い色で、ネットワークレイヤが緑色で記述されています。黒い矢はシステムを経由しているインプットとステート遷移を表します。フレームずつは破線で分けられています。統計図はプレヤー1の視点しか見せていませんが、プレヤー2は同一のステップに従っています。
|
||||
下の図を見てください。2つのクライアントが遅延0msの理想的なネットワークで同期されている図になっています。1プレイヤー側の入力が青、2プレイヤー側の入力は赤、ネットワーク層は緑です。黒の矢印は入力がシステム内で送信され、ゲームステートが推移する流れを表します。各フレームは破線で区切られています。図は1プレイヤー側から見たものになっていますが、2プレイヤー側も全く同じ手順になっています。
|
||||
|
||||

|
||||
|
||||
ゲームエンジンに放送される前、プレヤー1のインプットはネットワークレイヤでプレヤー2のインプットと併合されます。エンジンは現在のフレームのゲームステートをそのインプットを使って異動します。プレヤー2は同様、自分のインプットとプレヤー1のインプットを併合して、組み合わせたインプットをゲームエンジンに放送します。ゲームはそのふうに毎フレームをプレヤーインプットによって前のフレームを加工して、続きます。プレヤー1とプレヤー2は同じゲームステートから始まりまして、両方のゲームエンジンに放送したインプットが一致していますので、二人のプレヤーのゲームステートは自動的にフレームづつに同期化されています。
|
||||
1プレイヤーの入力は、ネットワーク層によって2プレイヤーの入力とマージされ、ゲームエンジンに送信されます。エンジンはその入力を用いて現在のフレームのゲームステートを変更します。2プレイヤー側も同様に自分と1プレイヤーの入力をマージしてゲームエンジンに送信します。プレイヤーの入力に応じたロジックを適用し、過去のフレームのゲームステートを変更しながら、フレームごとにゲームが進行します。1プレイヤーと2プレイヤー両方が同じゲームステートで、それぞれのエンジンに送信される入力が同じなので、両プレイヤーのゲームステートは毎フレーム同期されたままになります。
|
||||
|
||||
### 実践的に…
|
||||
|
||||
以上の理想的なネットワークのたとえで、パッケットが遅延なくネットワーク介して転送されているとする。現実はそんあに甘くはない。インフラ性質とプレヤーの間の距離によって、普通のブロードバンド接続で、パッケットを転送するのは5msから150msがかかります。あなたのゲームが60FPSでしたら、その遅延は1から9フレームとなります。
|
||||
### 実際は…
|
||||
|
||||
ゲームが全てのプレヤーのインプットが受け取るまで続けることができませんので、ゲームは1から9フレームまでの遅延をプレヤーのインプットに入道しなければならなくなります。
|
||||
理想的なネットワークの例では、パケットがネットワークを介して即時に送信されるものとされていますが、現実はそう甘くはありません。一般的なブロードバンド接続では、プレイヤー間の距離や回線の品質に応じて、パケットの送信に5~150msかかります。ゲームが1秒間に60フレームで実行されるとするならば、遅延は1~9フレーム相当になります。
|
||||
|
||||
ゲームは両プレイヤーの入力を受信するまでフレームを処理することができないので、各プレイヤーの入力に1~9フレームの遅延、つまり「ラグ」を適用しなければなりません。遅延を考慮に入れ、先程の図を変更してみると…
|
||||
|
||||

|
||||
|
||||
この例でパケットを転送するにはフレーム3枚が経過する。つまり、第一フレームに送った、リモートのプレヤー2のインプットが、プレヤー1のゲームエンジンの三フレーム後にしか到着しません。プレヤー1のゲームエンジンがプレヤー2のインプットを受け取る前は続くことができませんので、第一フレームを3フレームで遅延しなければなりません。従って、全部の後続のフレームも3フレームで遅延されています。ネットワークレイヤーは併合したインプットを,最大のプレヤーの間に交換されたパケットの片方向輸送時間で遅延しなければなりません。この遅延は多くのゲームタイプのゲームエクスペリエンスに著しい悪影響を与えることになりかねます。
|
||||
この例では、パケットの送信に3フレームかかります。2プレイヤーによって遠隔から送信された入力は、1フレーム目に1プレイヤー側に届かず、3フレーム後になるまでゲーム機に届きません。1プレイヤー側のゲームエンジンは入力を受信するまでゲームを進めることができないので、1フレーム目を3フレーム遅延せざるを得なくなります。続きのフレームも同様に3フレームの遅延が発生します。ネットワーク層は両プレイヤー間で送信されるパケットの最長転送時間だけ、マージされた入力を遅延せざるを得なくなります。理想的なネットワーク環境を除いては、大半のゲームジャンルにおいてこのラグはプレイ感に大きく影響を与えることとなります。
|
||||
|
||||
## インプット遅延をロールバックネットワーキングで減縮する
|
||||
|
||||
### 予測実行
|
||||
## ロールバックネットワーキングで入力遅延を取り除く
|
||||
|
||||
GGPOライブラリは、インプットラグを、パケットを転送するに必要な遅延を予測実行を使って隠すことで防ぎます。次の統計図を見ましょう。
|
||||
### 投機的実行
|
||||
|
||||
GGPOは投機的実行を用いることで、パケット送信に必要な遅延を隠し、入力ラグの発生を防ぎます。それでは、もう一つの図を見てみましょう。
|
||||
|
||||

|
||||
|
||||
リモートプレヤーからインプットを受け取るのを待つ代わりに、GGPOライブラリは過去のインプットに基づいて、リモートプレヤーがどんなインプットを入力するのを予測します。GGPOは予測したインプットをプレヤー1のロカルノのインプットと併合して、直接に併合したインプットをゲームエンジンに送ります。そうして、ゲームエンジンは次のフレームを、他のプレヤーのインプットが含まれるパケットがまだ受け取れなくても、加工できます。
|
||||
If GGPO’s prediction were perfect, the user experience playing online would be identical to playing offline. Of course, no one can predict the future! GGPO will occasionally incorrectly predict player 2’s inputs. Take another look at the diagram above. What happens if GGPO send the wrong inputs for player 2 at frame 1? The inputs for player 2 would be different on player 1’s game than in player 2’s. The two games will lose synchronization and the players will be left interacting with different versions of reality. The synchronization loss cannot possibly be discovered until frame 4 when player 1 receives the correct inputs for player 2, but by then it’s too late.
|
||||
This is why GGPO’s method is called “speculative execution”. What the current player sees at the current frame may be correct, but it may not be. When GGPO incorrectly predicts the inputs for the remote player, it needs correct that error before proceeding on to the next frame. The next example explains how that happens.
|
||||
GGPOは遠隔のプレイヤーから入力が届くのを待つ代わりに、過去の入力に基づいて他プレイヤーが行いそうな入力を予測します。予測された入力と1プレイヤーの入力をマージし、すぐにゲームエンジンへ渡すので、仮に他プレイヤーの入力が届かなくとも次のフレームへ進めることができます。
|
||||
GGPOの予測が完璧であれば、オンラインで遊ぶユーザー体験はオフラインと同一のものになります。もちろん、未来を予測することは誰にもできません!GGPOも2プレイヤーの入力を間違って予測することがあります。上の図をもう一度見てください。もしGGPOが1フレーム目に2プレイヤーに間違った入力を送信したらどうなるでしょうか。1プレイヤー側に表示される2プレイヤーの入力は、2プレイヤー側で表示されるものと異なってしまいます。両サイドのゲームは同期を失い、プレイヤーは違ったゲーム画面を見ながら相手の動きに反応することになります。同期のズレは、1プレイヤー側が2プレイヤーの正しい入力が届く4フレーム目まで検出することができませんが、それでは遅すぎます。
|
||||
そういうことから、GGPOの手法は「投機的実行(speculative execution)」と呼ばれます。遊んでいるプレイヤーがその時に見ているものは正しいかもしれませんが、そうでないこともあります。GGPOが遠隔プレイヤーの入力を誤って予測した場合、次のフレームへ進める前にエラーを修正する必要があります。次の例では、その方法を説明します。
|
||||
|
||||
### Correcting Speculative Execution Errors with Rollbacks
|
||||
|
||||
GGPO uses rollbacks to resynchronize the clients whenever it incorrectly predicts what the remote player will do. The term "rollback" refers to the process of rewinding state and predicting new outcomes based on new, more correct information about a player's input. In the previous section we wondered what would happen if the predicted frame for remote input 1 was incorrect. Let’s see how GGPO corrects the error:
|
||||
### 投機的実行エラーをロールバックで修正する
|
||||
|
||||
GGPOは遠隔プレイヤーの入力を間違って予測する度に、ロールバックを使ってクライアントを再同期します。「ロールバック」という単語は、ステートを巻き戻し、プレイヤーの入力に関する、より正しく新しい情報を元に結果を予測する過程を指します。前のセクションでは、遠隔の入力1における予測したフレームが間違っていたらどうなるか、ということについて考えました。それでは、GGPOがエラーを修正する過程を見てみましょう。
|
||||
|
||||

|
||||
|
||||
GGPO checks the quality of its prediction for previous frames every time receives a remote input. As mentioned earlier, GGPO doesn’t receive the inputs for player 2’s first frame until player 1’s fourth. At frame 4, GGPO notices that the inputs received from the network do not match the predicted inputs sent at earlier. To resynchronize the two games, GGPO needs undo the damage caused by running the game with incorrect inputs for 3 frames. It does this by asking the game engine to go back in time to a frame before the erroneously speculated inputs were sent (i.e. to "rollback" to a previous state). Once the previous state has been restored, GGPO asks the engine to move forward one frame at a time with the corrected input stream. These frames are shown in light blue. Your game engine should advance through these frames as quickly as possible with no visible effect to the user. For example, your video renderer should not draw these frames to the screen. Your audio renderer should ideally continue to generate audio, but it should not be rendered it until after the rollback, at which point samples should start playing n frames in, where n is the current frame minus the frame where the sample was generated.
|
||||
Once your engine reaches the frame it was on before GGPO discovered the error, GGPO drops out of rollback mode and allows the game to proceed as normal. Frames 5 and 6 in the diagram show what happens when GGPO predicts correctly. Since the game state is correct, there’s no reason to rollback.
|
||||
GGPOは遠隔の入力を受信したら、その都度前回のフレームで予測した品質をチェックします。先程触れたように、GGPOは4フレーム目まで2プレイヤー側の入力が届きません。4フレーム目で、GGPOは以前に予測した入力とネットワークから受信した入力が一致しないことに気付きます。両サイドのゲームを再同期するため、GGPOは3フレーム分の誤った入力によって発生したダメージや間違いを取り消す必要があります。誤って予測した入力を送信する前のフレームまで戻るよう、ゲームエンジンに要求します(つまり過去のステートまで「ロールバック」します)。以前のステートを復元したら、GGPOはエンジンに正しい入力で1フレーム進めるよう要求します。このフレームは水色で示しています。ゲームエンジンはこのフレームをユーザーに見えない形で出来る限り素早く進める必要があります。例えば、ビデオレンダラーはこのフレームを画面に描写するべきではありません。オーディオレンダラーは原則、音声を生成し続けるべきですが、ロールバックが終わるまでレンダーすべきではなく、サンプルが生成されたフレームを引いた現在のフレームであるnフレームでサンプルがスタートする必要があります。エンジンがGGPOがエラーを見つける前のフレームまで到達したら、GGPOはロールバックモードを止め、ゲームを通常どおり進めることを許可します。図の5フレームと6フレーム目はGGPOの予測が正しく行われた場合を示しています。ゲームステートが正しいので、ロールバックをする理由はありません。
|
||||
|
||||
# Code Structure
|
||||
|
||||
The following diagram shows the major moving parts in the GGPO session object and their relationship to each other. Each component is describe in detail below.
|
||||
# コード構造
|
||||
|
||||
次の図はGGPOセッションで主に動作するパーツ、また各パーツごとの関連性を示しています。各コンポーネントの詳細は以下に示しています。
|
||||
|
||||

|
||||
|
||||
## GGPO Interface
|
||||
|
||||
The GGPO interface abstracts away the implementation details between the P2P and the Sync Test backends. The proper backend is created automatically when you call the ggpo_start_session or ggpo_start_synctest entry points.
|
||||
## GGPOインタフェース(GGPO Interface)
|
||||
|
||||
## P2P Backend
|
||||
GGPOインターフェイスはP2Pと同期テストバックエンド間の詳細な実装を抽象化しています。適切なバックエンドは`ggpo_start_session`か`ggpo_start_synctest`エントリーポイントを呼び出した時に、自動的に生成されます。
|
||||
|
||||
The P2P backend orchestrates a game between players. It is created by the ggpo_start_session API call. Most of the heavy lifting is done by the contained helper classes.
|
||||
|
||||
## Poll Object
|
||||
## P2Pバックエンド(P2P Backend)
|
||||
|
||||
(not pictured). The poll object is a registration mechanism used by the other objects in the code. It delivers timers and notifications when waitable objects become ready. For example, the UDP backend uses the Poll object to receive notifications when new packets arrive.
|
||||
P2Pバックエンドはプレイヤー間でゲームを調整します。`ggpo_start_session` APIの呼び出しによって生成されます。大きな情報の処理の大半は含まれているヘルパークラスによって行われます。
|
||||
|
||||
## Sync Object
|
||||
|
||||
The sync object is used to keep track of the last n-frames of game state. When its embedded prediction object notifies it of a prediction error, the Sync backend rewinds the game to the more-correct state and single-steps forward to correct the prediction error.
|
||||
## ポーリングオブジェクト(Poll Object)
|
||||
|
||||
## Input Queue Object
|
||||
(図にはありません)ポーリングオブジェクトはコード内で他のオブジェクトによって用いられる登録方式です。待機可能なオブジェクトが準備できたときに通知とタイマーを送信します。例としてUDPバックエンドは新たなパケットが到着したときに、通知を受信するためポーリングオブジェクトを使用します。
|
||||
|
||||
The InputQueue object keeps track of all the inputs received for a local or remote player. When asked for an input which it doesn't have, the input queue predicts the next input, and keeps track of this information for later so they sync object will know where to rollback to if the prediction was incorrect. The input queue also implements the frame-delay if requested.
|
||||
|
||||
## UDP Protocol Object
|
||||
## 同期オブジェクト(Sync Object)
|
||||
|
||||
The UDP protocol object handles the synchronization and input exchange protocols between any two players. It also implements the game input compression and reliable-UDP layer. Each UDP Protocol object has a contained TimeSync object which is uses to approximate the wall clock time skew between two players.
|
||||
同期オブジェクトはゲームステートのnフレームを追跡するために用いられます。埋め込まれた予測(prediction)オブジェクトが予測エラーを通知された時、同期バックエンドがより正確なステートまでゲームを巻き戻し、予測エラーを修正するためシングルステップ処理を進めます。
|
||||
|
||||
## UDP Object
|
||||
|
||||
The UDP object is simply a dumb UDP packet sender/receiver. It’s divorced from UDP protocol to ease ports to other platforms.
|
||||
## 入力キューオブジェクト(Input Queue Object)
|
||||
|
||||
## Sync Test Backend
|
||||
入力キューオブジェクトはローカル、または遠隔プレイヤー用に受信した全入力を追跡します。所持していない入力を要求された場合、入力キューは次の入力を予測し、後の情報を追跡します。そうすることで同期オブジェクトは予測が誤った場合にどこまでロールバックすればよいのか分かります。リクエストがあった場合、入力キューはフレーム遅延も実行します。
|
||||
|
||||
(not pictured) The Sync Test backend uses the same Sync object as the P2P backend to verify your application’s save state and stepping functionally execute deterministically. For more information on sync test uses, consult the Programmers Guide.
|
||||
|
||||
## UDPプロトコルオブジェクト(UDP Protocol Object)
|
||||
|
||||
UDPプロトコルオブジェクトは両プレイヤー間の同期と入力交換プロトコルを扱います。また、ゲーム入力の圧縮と信頼できるUDP層も実装しています。各UDPプロトコルオブジェクトにはTimeSyncオブジェクトが含まれ、プレイヤー間の時間のずれを推測するために利用しています。
|
||||
|
||||
|
||||
## UDPオブジェクト(UDP Object)
|
||||
|
||||
UDPオブジェクトは単純なUDPパケットの送受信を行います。他のプラットフォームへの移植を簡単にするため、UDPプロトコルから切り離されています。
|
||||
|
||||
|
||||
## 同期テストバックエンド(Sync Test Backend)
|
||||
|
||||
(図にはありません)同期テストバックエンドは、P2Pバックエンドがアプリのセーブステートと決定的に機能上実行していることを確認するときに同じ同期オブジェクトを使用します。同期テストの使用に関する詳しい情報は、開発者向けガイドを参照してください。
|
||||
|
||||
@@ -85,4 +85,4 @@ The UDP object is simply a dumb UDP packet sender/receiver. It’s divorced fro
|
||||
|
||||
## Sync Test Backend
|
||||
|
||||
(not pictured) The Sync Test backend uses the same Sync object as the P2P backend to verify your application’s save state and stepping functionally execute deterministically. For more information on sync test uses, consult the Developer Guide.
|
||||
(not pictured) The Sync Test backend uses the same Sync object as the P2P backend to verify your application’s save state and stepping functionality execute deterministically. For more information on sync test uses, consult the Developer Guide.
|
||||
|
||||
@@ -74,7 +74,7 @@ vw_on_event_callback(GGPOEvent *info)
|
||||
ngs.UpdateConnectProgress(info->u.synchronizing.player, progress);
|
||||
break;
|
||||
case GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER:
|
||||
ngs.UpdateConnectProgress(info->u.synchronizing.player, 100);
|
||||
ngs.UpdateConnectProgress(info->u.synchronized.player, 100);
|
||||
break;
|
||||
case GGPO_EVENTCODE_RUNNING:
|
||||
ngs.SetConnectState(Running);
|
||||
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
int size;
|
||||
|
||||
switch (hdr.type) {
|
||||
case SyncRequest: return sizeof(u.sync_reply);
|
||||
case SyncRequest: return sizeof(u.sync_request);
|
||||
case SyncReply: return sizeof(u.sync_reply);
|
||||
case QualityReport: return sizeof(u.quality_report);
|
||||
case QualityReply: return sizeof(u.quality_reply);
|
||||
@@ -104,4 +104,4 @@ public:
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user