3

入力シーケンスを保存して再生し、ネットワークを簡単にするために、ゲーム エンジンで決定論を実現したいと考えています。

私のエンジンは現在、可変タイムステップを使用しています。フレームごとに、最後のフレームを更新/描画するのにかかった時間を計算し、それをエンティティの update メソッドに渡します。これにより、1000FPS のゲームが 30FPS の高速ゲームのように見えますが、不確定な動作が発生します。

解決策として、ゲームを 60FPS に修正することが考えられますが、入力がさらに遅くなり、より高いフレームレートのメリットが得られなくなります。

そこで、スレッド (常に update(1) を呼び出してから 16 ミリ秒スリープする) を使用して、ゲーム ループ内で可能な限り高速に描画しようとしました。ある程度は機能しますが、頻繁にクラッシュし、ゲームがプレイできなくなります。

エンジンに依存するすべてのゲームを書き直さなくても決定論を実現するために、ゲーム ループにスレッドを実装する方法はありますか?

4

4 に答える 4

5

ゲーム フレームをグラフィック フレームから分離する必要があります。グラフィカル フレームは、グラフィックのみを表示し、他には何も表示しないようにします。リプレイの場合、コンピューターが実行できたグラフィック フレームの数は関係ありません。1 秒あたり 30 であろうと、1 秒あたり 1000 であろうと、再生するコンピューターは異なるグラフィック フレーム レートで再生する可能性があります。

しかし、実際にはゲームフレームを修正する必要があります。たとえば、毎秒 100 ゲームフレーム。ゲーム フレームでは、ゲーム ロジックが実行されます。これは、ゲーム (およびリプレイ) に関連するものです。

ゲーム フレームが必要ないときはいつでも、ゲームループはグラフィカル フレームを実行する必要があるため、ゲームを 1 秒あたり 100 ゲームフレームに修正すると、ゲーム フレームあたり 0.01 秒になります。コンピューターがゲームフレームでそのロジックを実行するのに 0.001 しか必要としなかった場合、残りの 0.009 秒はグラフィック フレームを繰り返すために残されます。

これは小さいが不完全であり、100% 正確ではない例です。

uint16_t const GAME_FRAMERATE = 100;
uint16_t const SKIP_TICKS = 1000 / GAME_FRAMERATE;

uint16_t next_game_tick;

Timer sinceLoopStarted = Timer(); // Millisecond timer starting at 0
unsigned long next_game_tick = sinceLoopStarted.getMilliseconds();

while (gameIsRunning)
{
    //! Game Frames
    while (sinceLoopStarted.getMilliseconds() > next_game_tick)
    {
        executeGamelogic();

        next_game_tick += SKIP_TICKS;
    }

    //! Graphical Frames
    render();
}

次のリンクには、正確なゲームループの作成に関する非常に優れた完全な情報が含まれています。

http://www.koonsolo.com/news/dewitters-gameloop/

于 2013-03-15T07:22:26.383 に答える
4

ネットワーク全体で決定論的であるためには、一般に「サーバー」と呼ばれる単一の真実のポイントが必要です。ゲーム コミュニティでは、「クライアントは敵の手にある」ということわざがあります。それは本当だ。公正なゲームのためにクライアントで計算されたものを信頼することはできません。

たとえば、何らかの理由でスレッドが 1 秒間に 60 回ではなく 59 回しか更新されない場合にゲームがより簡単になる場合、人々はそれを知るでしょう。たぶん、最初は悪意さえないでしょう。当時、彼らはマシンを全負荷状態にしていただけで、あなたのプロセスは 1 秒間に 60 回に達しませんでした。

グラフィックスや更新サイクルを気にせず、独自の速度で実行するサーバー (おそらくシングル プレーヤーのスレッドとしてインプロセスであっても) があれば、少なくともすべてのプレーヤーで同じ結果を得るのに十分決定論的です。コンピューターがリアルタイムではないという事実に基づいて、まだ 100% 決定論的ではない可能性があります。$frequence ごとに更新するように指示したとしても、コンピューター上の他のプロセスの負荷が大きすぎるため、更新されない場合があります。

サーバーとクライアントは通信する必要があるため、サーバーはその状態のコピーを各クライアントに送信する必要があります (パフォーマンスのために、最後のコピーからのデルタかもしれません)。クライアントは、利用可能な最高速度でこのコピーを描画できます。

ゲームがスレッドでクラッシュする場合、実際に「サーバー」をプロセスから外してネットワーク経由で通信するオプションがあるかもしれません。この方法では、どの変数がロックを必要としていたかを非常に迅速に見つけることができます。別のプロジェクトでは、クライアントはコンパイルされなくなります。

于 2013-03-15T07:21:52.853 に答える
2

ゲームのロジックとグラフィックを別々のスレッドに分けます。ゲーム ロジック スレッドは一定の速度で実行する必要があります (たとえば、1 秒あたり 60 回、またはロジックが複雑すぎない場合はさらに高速に更新して、よりスムーズなゲーム プレイを実現します)。次に、グラフィックス スレッドは常に、ロジック スレッドによって提供される最新の情報をできるだけ速く描画して、高いフレームレートを実現する必要があります。

部分的なデータが描画されるのを防ぐために、ロジック スレッドが 1 つのバッファーに書き込み、グラフィックス スレッドが別のバッファーから読み取る、ある種のダブル バッファリングを使用する必要があります。次に、ロジック スレッドが 1 つの更新を行うたびにバッファを切り替えます。

これにより、コンピュータのグラフィック ハードウェアを常に最大限に活用できるようになります。もちろん、これは最小 CPU 速度に制約を課していることを意味します。

于 2013-03-15T07:05:22.643 に答える
0

これが役立つかどうかはわかりませんが、私の記憶が正しければ、Doom は入力シーケンスを保存し、それらを使用して AI の動作やその他のものを生成しました。Doom のデモ ランプは、ゲームの状態ではなく、ユーザーの入力を表す一連の数字です。その入力から、ゲームは何が起こったのかを再構築し、ある種の決定論を達成することができます.

于 2013-03-15T07:05:52.317 に答える