リプレイシステムの設計方法


75

それでは、リプレイシステムをどのように設計しますか?

Warcraft 3やStarcraftのような特定のゲームでは、ゲームが既にプレイされた後に再び見ることができます。

比較的小さなリプレイファイルになります。だから私の質問は:

  • データを保存する方法は?(カスタム形式?)(小さなファイルサイズ)
  • 何が救われるのでしょうか?
  • 期間を記録するために他のゲームで使用できるように汎用化するにはどうすればよいですか(たとえば、完全一致ではありません)?
  • 早送りと巻き戻しを可能にします(WC3は覚えている限り巻き戻しできませんでした)

3
以下の回答は多くの貴重な洞察を提供しますが、目標を達成するために不可欠であるため、ゲーム/エンジンを高度に決定論的に開発することの重要性を強調したかっただけです(en.wikipedia.org/wiki/Deterministic_algorithm)。
アリパトリック

2
また、物理エンジンは決定論的ではないことに注意してください(Havokによると...)、入力とタイムスタンプを保存するだけのソリューションでは、ゲームで物理を使用する場合は毎回異なる結果が生成されます。
サマーサ

5
固定タイムステップを使用している限り、ほとんどの物理エンジンは決定論的です。Havokがそうでない場合、私は非常に驚くでしょう。非決定論は...コンピュータ上で来ることがかなり困難である

4
確定的とは、同じ入力=同じ出力を意味します。あるプラットフォームで浮動小数点数を持ち、別のプラットフォームで浮動小数点数を持つ場合(たとえば)、IEEE浮動小数点標準実装を意図的に無効にした場合、それは同じ入力で実行していないことを意味し、決定論的ではありません。

3
それは私ですか、それともこの質問は隔週で報われますか?
共産主義者ダック

回答:


39

この優れた記事は多くの問題をカバーしています:http : //www.gamasutra.com/view/feature/2029/developing_your_own_replay_system.php

この記事が言及し、うまく機能するいくつかのこと:

  • あなたのゲームは決定的でなければなりません。
  • 最初のフレームでゲームシステムの初期状態を記録し、ゲームプレイ中のプレイヤー入力のみを記録します。
  • 入力をより低いビット数に量子化します。すなわち。さまざまな範囲内のフロートを表します(例:[0、1]または[-1、1]範囲が少ないビット内。実際のゲームプレイ中にも量子化された入力を取得する必要があります。
  • 入力ストリームに新しいデータがあるかどうかを判断するには、1ビットを使用します。一部のストリームは頻繁に変更されないため、これは入力の時間的一貫性を活用します。

ほとんどの場合、圧縮率をさらに改善する1つの方法は、すべての入力ストリームを分離し、独立して完全にランレングスエンコードすることです。これは、実行を8ビットでエンコードし、実行自体が8フレームを超える場合(おそらく、ゲームが実際のボタンマッシャーでない限り)、デルタエンコード手法に勝るものです。レースゲームでこの手法を使用して、2人のプレイヤーからの8分間の入力を圧縮し、トラックをわずか数百バイトまでレースしました。

そのようなシステムを再利用可能にするという点では、リプレイシステムに汎用入力ストリームを処理させましたが、ゲーム固有のロジックがこれらのストリームへのキーボード/ゲームパッド/マウス入力をマーシャリングできるようにするフックも提供しました。

高速巻き戻しまたはランダムシークが必要な場合は、Nフレームごとにチェックポイント(完全なゲーム状態)を保存できます。Nは、リプレイファイルのサイズを最小化するために選択する必要があります。また、状態が選択したポイントまでリプレイされる間、プレーヤーが待機しなければならない時間が妥当であることを確認します。これを回避する1つの方法は、これらの正確なチェックポイントの場所に対してのみランダムシークを実行できるようにすることです。巻き戻しは、問題のフレームの直前のチェックポイントにゲームの状態を設定し、現在のフレームに到達するまで入力を再生することです。ただし、Nが大きすぎると、数フレームごとにヒッチが発生する可能性があります。これらのヒッチを滑らかにする1つの方法は、現在のチェックポイント領域からキャッシュされたフレームを再生している間に、前の2つのチェックポイント間のフレームを非同期に事前キャッシュすることです。


RNGが関与している場合、そのRNGの結果をストリームに含める
ラチェットフリーク14

1
@ratchet freak:PRNGを決定論的に使用すると、チェックポイント中にシードのみを保存することで対応できます。
非数値

22

「キーストロークをリプレイ可能にする」ソリューションは驚くほど難しい場合がありますが、ゲームの状態全体をすべてのフレームで記録することもできます。少し巧妙な圧縮を使用すると、大幅に圧縮できます。これが、Braidが時間を巻き戻すコードを処理する方法であり、非常にうまく機能します。

とにかく巻き戻しにはチェックポイントが必要になるので、物事を複雑にする前に簡単な方法で実装してみてください。


2
+1巧妙な圧縮を使用すると、保存する必要があるデータの量を実際に減らすことができます(たとえば、現在のオブジェクトに対して保存した最後の状態と比較して状態が変化していない場合、状態を保存しないでください) 。私はすでに物理学でこれを試しました、そしてそれは本当にうまくいきます。物理学がなく、ゲーム全体を巻き戻したくない場合は、Joeのソリューションを使用します。可能な限り小さいファイルを作成するため、巻き戻しが必要な場合は、最後のn数秒だけを保存できます。ゲーム。
サマウルサ

@Samaursa-標準の圧縮ライブラリ(gzipなど)を使用すると、状態が変更されたかどうかを確認するなどのチェックを手動で行う必要なく、同じ(おそらくより良い)圧縮が得られます。
ジャスティン

2
@Kragen:本当ではありません。標準の圧縮ライブラリは確かに優れていますが、多くの場合、ドメイン固有の知識を活用することはできません。同様のデータを隣接させて、実際に変更されていないものを削除することで、それらを少しでも助けることができれば、物事を大幅に削減することができます。
ZorbaTHut

1
@ZorbaTHut理論的にははい、しかし実際には努力する価値は本当にありますか?
ジャスティン

4
努力する価値があるかどうかは、データの量に完全に依存します。数百または数千のユニットを備えたRTSを持っている場合は、おそらく重要です。Braidのようなメモリにリプレイを保存する必要がある場合は、おそらく重要です。

21

システムを一連の状態と関数で構成されているように表示できます。f[j]入力x[j]がある関数は、次のようにシステム状態s[j]をstateに変更しますs[j+1]

s[j+1] = f[j](s[j], x[j])

状態は、あなたの全世界の説明です。プレイヤーの位置、敵の位置、スコア、残りの弾薬など。ゲームのフレームを描くために必要なものすべて。

関数は、世界に影響を与える可能性のあるものです。フレーム変更、キー押下、ネットワークパケット。

入力は、関数が取得するデータです。フレームの変更には、最後のフレームが経過してから時間がかかる場合があります。キーを押すには、実際に押されたキーと、シフトキーが押されたかどうかが含まれます。

この説明のために、次のことを想定します。

仮定1:

ゲームの特定の実行の状態の量は、機能の量よりもはるかに大きくなります。おそらく数十万の状態がありますが、機能(フレーム変更、キー押下、ネットワークパケットなど)は数十個しかありません。もちろん、入力の量は、状態の量から1を引いた値に等しくなければなりません。

仮定2:

単一の状態を保存する空間的コスト(メモリ、ディスク)は、関数とその入力を保存するよりもはるかに大きくなります。

仮定3:

状態を提示する時間的コスト(時間)は、状態の関数を計算する場合よりも1桁または2桁長いだけです。

リプレイシステムの要件に応じて、リプレイシステムを実装する方法がいくつかあります。そのため、最も単純なものから始めることができます。また、紙に記録されたチェスのゲームを使用した小さな例を作成します。

方法1:

ストアs[0]...s[n]。これは非常に簡単で、非常に簡単です。仮定2のため、これの空間コストは非常に高くなります。

チェスの場合、これは動きごとにボード全体を引くことで達成されます。

方法2:

フォワードリプレイのみが必要な場合は、単に保存してs[0]から保存することができますf[0]...f[n-1](これは関数のIDの名前にすぎないことを思い出してください)およびx[0]...x[n-1](これらの各関数の入力は何でしたか)。再生するには、単にから始めてs[0]、計算します

s[1] = f[0](s[0], x[0])
s[2] = f[1](s[1], x[1])

等々...

ここで小さな注釈を付けたいと思います。他の数人のコメント者は、このゲームは「決定論的でなければならない」と述べた。あなたのゲームが量子コンピューターで実行されることを意図されていない限り、すべてのコンピュータープログラムは決定論的である¹ため、コンピューターサイエンス101をもう一度取る必要があると言う人。それがコンピューターを素晴らしいものにしているのです。

ただし、ライブラリから実際のCPUの実装に至るまで、プログラムは外部プログラムに依存する可能性が高いため、プラットフォーム間で関数が同じように動作することを確認するのは非常に困難です。

擬似乱数を使用する場合、生成された数値を入力の一部として保存するかx、prng関数の状態をstateの一部として保存し、sその実装をfunctionの一部として保存できますf

チェスの場合、これは最初のボード(既知のボード)を描画し、次に各駒がどの駒がどこに行ったのかを記述することによって達成されます。ところで、これは彼らが実際に行う方法です。

方法3:

今、あなたはおそらくあなたのリプレイにシークできるようにしたいでしょう。つまりs[n]、任意のについて計算しnます。方法2を使用すると、を計算するs[0]...s[n-1]前に計算する必要がありますs[n]。これは、仮定2によると非常に遅い可能性があります。

これを実装するために、メソッド3はメソッド1とメソッド2を一般化したものであり、特定の定数すべてに対して、ストアf[0]...f[n-1]x[0]...x[n-1]メソッド2と同様にストアもs[j]行います。簡単に言えば、これはすべての状態のうちの1つにブックマークを保存することを意味します。たとえば、のためのあなたの店、j % Q == 0QQQ == 100s[0], s[100], s[200]...

s[n]任意の値を計算するには、nまず以前に保存されたものをロードし、s[floor(n/Q)]次にからfloor(n/Q)までのすべての関数を計算しnます。最大で、あなたはQ関数を計算します。の値が小さいQほど計算は速くなりますが、より多くのスペースを消費しますが、値が大きいほどQ消費するスペースは少なくなりますが、計算に時間がかかります。

方法3 Q==1は方法1と同じですが、方法3 Q==infは方法2と同じです。

チェスの場合、これはすべての動きを描画することによって達成され、10枚のボードごとに1枚を描画します(for Q==10)。

方法4:

リプレイを逆にしたい場合は、方法3の小さなバリエーションを作ることができます。仮にQ==100、逆方向に計算s[150]したいとしますs[90]。変更されていない方法3では、取得するには50回の計算をs[150]行い、取得するにはさらに49回の計算を行う必要がありますs[149]。ただし、s[149]取得するためs[150]にすでに計算しているため、初めてs[100]...s[150]計算するときにキャッシュを作成し、それを表示する必要があるときにキャッシュ内にs[150]既に作成することができますs[149]

あなたは計算する必要があるたびにキャッシュを再生成する必要がありますs[j]j==(k*Q)-1与えられたk。今回は、Qサイズを大きくするとサイズが小さくなり(キャッシュのみ)、時間が長くなります(キャッシュの再作成のみ)。Q状態と関数の計算に必要なサイズと時間を知っていれば、の最適値を計算できます。

チェスの場合、これはすべての動きと10枚ごとに1枚(Q==10)を引くことで達成されますが、計算した最後の10枚の紙を別々に描く必要があります。

方法5:

状態が単純にスペースを消費しすぎる場合、または関数が時間を消費しすぎる場合、逆再生を実際に実装する(偽物ではない)ソリューションを作成できます。これを行うには、持っている各関数に対して逆関数を作成する必要があります。ただし、これには各関数がインジェクションであることが必要です。これが実行可能な場合f'、functionの逆を示すためのf計算s[j-1]は次のように簡単です。

s[j-1] = f'[j-1](s[j], x[j-1])

ここでは、関数と入力は両方でありj-1、ではないことに注意してくださいj。この同じ関数と入力は、計算する場合に使用したものです

s[j] = f[j-1](s[j-1], x[j-1])

これらの関数の逆を作成するのは難しい部分です。ただし、通常、ゲームの各機能の後に一部の状態データが失われるため、できません。

このメソッドはそのままで、を逆に計算できs[j-1]ますが、がある場合のみですs[j]。つまり、逆方向に再生することを決定した時点から、逆方向にのみ再生を見ることができます。任意のポイントから逆方向にリプレイしたい場合は、これを方法4と組み合わせる必要があります。

チェスの場合、これを実装することはできません。これは、特定のボードと前の移動で、どのピースが移動したかを知ることができますが、どこから移動したかはわかりません。

方法6:

最後に、すべての関数がインジェクションであることを保証できない場合は、小さなトリックを行うことができます。各関数が新しい状態のみを返すようにする代わりに、次のように破棄したデータを返すようにすることもできます。

s[j+1], r[j] = f[j](s[j], x[j])

r[j]破棄されたデータはどこにありますか。そして、次のように、破棄されたデータを取得するように逆関数を作成します。

s[j] = f'[j](s[j+1], x[j], r[j])

f[j]およびx[j]に加えて、r[j]関数ごとに保存する必要があります。もう一度、シークできるようにしたい場合は、方法4などでブックマークを保存する必要があります。

チェスの場合、これは方法2と同じになりますが、どのピースがどこに行くかだけを示すメソッド2とは異なり、各ピースがどこから来たのかを保存する必要もあります。

実装:

これは、特定のゲームのすべての種類の機能を備えたすべての種類の状態で機能するため、いくつかの仮定を行うことができ、実装が容易になります。実際、ゲーム全体の状態で方法6を実装すると、データをリプレイできるだけでなく、特定の瞬間から時間を遡ってプレイを再開できます。それはかなり素晴らしいでしょう。

すべてのゲームの状態を保存する代わりに、特定の状態を描画するために必要な最小限の値を保存し、一定時間ごとにこのデータをシリアル化できます。状態はこれらのシリアル化になり、入力は2つのシリアル化の違いになります。これが機能するための鍵は、世界の状態もほとんど変わらない場合、シリアル化はほとんど変わらないということです。この違いは完全に可逆的であるため、ブックマークを使用した方法5の実装は非常に可能です。

これはいくつかの主要なゲームで実装されており、主にイベント(fpsのフラグ、またはスポーツゲームのスコア)が発生したときに最近のデータを即座に再生するために使用されています。

この説明が退屈ではないことを願っています。

¹これは、一部のプログラムが非決定的であるように動作することを意味するものではありません(MS Windows ^^など)。真剣に、決定論的コンピューターで非決定論的プログラムを作成できれば、フィールズメダル、チューリング賞、そしておそらくオスカーとグラミー賞を同時に獲得することになるでしょう。


「すべてのコンピュータープログラムは決定的です」では、スレッド化に依存するプログラムを考慮することを怠っています。スレッドは、リソースのロードやレンダリングループの分離に主に使用されますが、例外があり、その時点で、決定論の実施について適切に厳密でない限り、真の決定論を主張できなくなる可能性があります。ロックメカニズムだけでは十分ではありません。追加の追加作業なしに、任意の可変データを共有することはできません。多くのシナリオでは、ゲームはそれ自体のためにそのレベルの厳密さを必要としませんが、リプレイのようなものにはそうすることができます。
krdluzni

1
@krdluzniスレッド化、並列処理、および真のランダムソースからの乱数は、プログラムを非決定的にしません。スレッドのタイミング、デッドロック、初期化されていないメモリ、さらには競合状態は、プログラムが受け取る追加の入力にすぎません。(何らかの理由で)これらの入力を破棄するか、まったく考慮しないという選択は、まったく同じ入力を与えられたプログラムがまったく同じように実行されるという事実には影響しません。「非決定論的」は非常に正確なコンピューターサイエンス用語であるため、意味がわからない場合は使用しないでください。

@oscar(多少簡潔で忙しいかもしれませんが、後で編集するかもしれません):厳密な理論的な意味では入力としてスレッドのタイミングなどを主張することができますが、これは一般的には監視できないため、実用的な意味では役に立ちませんプログラム自体または開発者によって完全に制御されます。さらに、決定論的ではないプログラムは、非決定論的である(ステートマシンの意味で)プログラムとは大きく異なります。私はこの用語の意味を理解しています。既存の用語をオーバーロードするのではなく、他の何かを選択してほしい。
-krdluzni

@krdluzniスレッドタイミング(リプレイを正確に計算する能力に影響する場合)などの予測不可能な要素を備えたリプレイシステムを設計する私のポイントは、ユーザー入力のように他の入力ソースと同様に扱うことです。プログラムが完全に予測不可能なユーザー入力を必要とするため、プログラムが「非決定的」であると不満を言う人はいません。この用語に関しては、不正確でわかりにくいです。むしろ、「実際には予測不可能」なもの、またはそのようなものを使用してもらいたいです。いいえ、不可能ではありません。VMWareのリプレイデバッグを確認してください。

9

他の答えがまだカバーしていないことの1つは、フロートの危険です。floatを使用して完全に確定的なアプリケーションを作成することはできません。

フロートを使用すると、次の場合にのみ完全に確定的なシステムを作成できます。

  • まったく同じバイナリを使用する
  • まったく同じCPUを使用する

これは、フロートの内部表現がCPUごとに異なるためです-AMDとIntel CPUの間で最も劇的です。値がFPUレジスタにある限り、C側から見た場合よりも正確であるため、中間計算はより高い精度で実行されます。

これがどのようにAMD対Intelビットに影響するかは非常に明白です-例えば、1つが80ビットのフロートを使用し、他の64を使用するとしましょう-しかし、なぜ同じバイナリ要件ですか?

前述のとおり、値がFPUレジスタにある限り、より高い精度が使用されます。これは、再コンパイルするたびに、コンパイラーの最適化によってFPUレジスターの値が入れ替わり、結果が微妙に異なる可能性があることを意味します。

_control87()/ _ controlfp()フラグを設定して、可能な限り低い精度を使用すると、これを支援できる場合があります。ただし、一部のライブラリもこれに影響を与える可能性があります(少なくともd3dの一部のバージョンはそうしました)。


3
GCCを使用すると、-ffloat-storeを使用して、レジスタから値を強制的に削除し、32/64ビットの精度に切り捨てることができます。他のライブラリが制御フラグを乱すことを心配する必要はありません。明らかに、これはあなたの速度にマイナスの影響を与えます(しかし、他の量子化も同様です)。

8

乱数ジェネレーターの初期状態を保存します。次に、各入力(マウス、キーボード、ネットワークなど)にタイムスタンプを付けて保存します。ネットワーク化されたゲームをお持ちの場合は、おそらくこれがすべて既に用意されています。

RNGを再設定し、入力を再生します。それでおしまい。

これは巻き戻しを解決しません。巻き戻しには、できる限り速く最初から再生する以外、一般的な解決策はありません。ゲームの状態全体をX秒ごとにチェックポイントすることでパフォーマンスを向上させることができます。その数だけ再生する必要がありますが、ゲームの状態全体を取得するのは非常に高価な場合もあります。

ファイル形式の詳細は重要ではありませんが、ほとんどのエンジンにはコマンドや状態を既にシリアル化する方法があります-ネットワーキング、保存、その他何でも。それを使ってください。


4

決定論的なリプレイに反対します。すべてのエンティティの状態を1 / N秒ごとに保存すると、FARがより簡単になり、FARがエラーを起こしにくくなります。

再生時に表示したいものだけを保存します-位置と見出しだけの場合は問題ありません。統計も表示したい場合はそれも保存しますが、一般的にはできるだけ保存しないでください。

エンコードを微調整します。すべてにできるだけ少ないビットを使用します。再生は、見栄えが良ければ完璧である必要はありません。たとえば、見出しにフロートを使用する場合でも、1バイトで保存し、256個の値(1.4º精度)を取得できます。それはあなたの特定の問題には十分かもしれませんし、多すぎるかもしれません。

デルタエンコーディングを使用します。エンティティがテレポートしない限り(およびケースを個別に処理する場合)、新しい位置と古い位置の差として位置をエンコードします-短い動きの場合、フル位置に必要なものよりもはるかに少ないビットで逃げることができます。

簡単に巻き戻したい場合は、Nフレームごとにキーフレーム(完全なデータ、デルタなし)を追加します。この方法により、デルタやその他の値の精度を下げることができます。定期的に「true」値にリセットしても、丸め誤差はそれほど問題になりません。

最後に、すべてをgzipします:)


1
ただし、これはゲームのタイプに少し依存します。
ヤリコンパ

私はこの声明に非常に注意します。特に、サードパーティの依存関係を持つ大規模なプロジェクトでは、状態を保存することは不可能です。入力のリセットと再生は常に可能です。
-TomSmartBishop

2

それは難しい。最初に、そして何よりもJari Komppaの回答を読んでください。

私のコンピューターで行われたリプレイは、フロートの結果がわずかに異なるため、コンピューターで動作しない場合があります。それは大したことです。

しかし、その後、乱数がある場合は、リプレイにシード値を保存します。次に、すべてのデフォルト状態をロードし、乱数をそのシードに設定します。そこから、現在のキー/マウスの状態とその方法の時間の長さを簡単に記録できます。次に、それを入力として使用してすべてのイベントを実行します。

ファイルを飛び回るには(はるかに難しい)、THE MEMORYをダンプする必要があります。すべてのユニットがどこにあるか、お金、時間の長さ、ゲームの状態のすべてが好きです。その後、早送りしますが、目的の時間の目的地に到達するまでレンダリング、サウンドなどをスキップしますが、すべてを再生します。これは、転送の速さに応じて1分ごとまたは5分ごとに発生する可能性があります。

主なポイントは-乱数の処理-入力のコピー(プレイヤー、およびリモートプレイヤー)-ファイルの周りをジャンプするためのダンプ状態、および...-フロートが壊れることはありません


2

誰もこのオプションについて言及していないことに少し驚いていますが、ゲームにマルチプレイヤーコンポーネントがある場合、この機能に必要な多くのハードワークを既に行っている可能性があります。結局のところ、マルチプレイヤーとは何ですか?しかし、自分のコンピューター上で(わずかに)異なる時間に他の誰かの動きを再現しようとしますか?

また、これにより、帯域幅に優しいネットワークコードで作業していると仮定すると、副作用としてファイルサイズが小さくなるという利点も得られます。

多くの点で、「非常に確定的である」オプションと「すべての記録を保持する」オプションの両方を組み合わせています。それでも決定性が必要です-リプレイが本質的にボットが元のゲームとまったく同じ方法で再びゲームをプレイする場合、ランダムな結果をもたらす可能性のあるアクションはすべて同じ結果になる必要があります。

データ形式は、ネットワークトラフィックのダンプと同じくらい簡単かもしれませんが、少しクリーンアップしても害はないと思います(結局、再生の遅れを心配する必要はありません)。他の人が言及したチェックポイントメカニズムを使用すると、ゲームの一部のみを再プレイできます。通常、マルチプレイヤーゲームは、とにかくゲーム更新の完全な状態を頻繁に送信するため、この作業を既に行っている可能性があります。


0

可能な限り最小のリプレイファイルを取得するには、ゲームが確定的であることを確認する必要があります。通常、これには、乱数ジェネレーターを見て、それがゲームロジックのどこで使用されているかを確認する必要があります。

ほとんどの場合、ゲームロジックRNGと、GUI、パーティクルエフェクト、サウンドなどのその他すべてのRNGが必要になります。これが完了したら、ゲームロジックRNGの初期状態を記録し、次にフレームごとにすべてのプレーヤーのゲームコマンドを記録する必要があります。

多くのゲームでは、入力とコマンドに変換されるゲームロジックとの間に抽象化のレベルがあります。たとえば、コントローラーのAボタンを押すと、「ジャンプ」デジタルコマンドがtrueに設定され、コントローラーを直接確認せずにゲームロジックがコマンドに反応します。これを行うと、ゲームロジックに影響を与えるコマンドのみを記録する必要があり(「一時停止」コマンドを記録する必要はありません)、おそらくこのデータはコントローラーデータを記録するよりも小さくなります。また、プレーヤーがボタンの再マッピングを決定した場合に、制御スキームの状態を記録することについて心配する必要もありません。

巻き戻しは、決定論的な方法を使用して、ゲームの状態のスナップショットを使用して、見たい時点まで早送りする以外に、フレームごとにゲームの状態全体を記録する以外にできることは多くありません。

一方、早送りは確かに実行可能です。ゲームロジックがレンダリングに依存していない限り、ゲームの新しいフレームをレンダリングする前に何度でもゲームロジックを実行できます。早送りの速度はマシンによって制限されます。少しずつスキップしたい場合は、巻き戻しに必要なのと同じスナップショット方式を使用する必要があります。

おそらく、決定性に依存するリプレイシステムを記述する上で最も重要なのは、データのデバッグストリームを記録することです。このデバッグストリームには、各フレーム(RNGシード、エンティティ変換、アニメーションなど)の可能な限り多くの情報のスナップショットが含まれており、記録されたデバッグストリームをリプレイ中のゲームの状態に対してテストできます。これにより、特定のフレームの終わりで不一致をすばやく知らせることができます。これにより、未知の非決定的なバグから髪を引き抜こうとする無数の時間を節約できます。初期化されていない変数のような単純なものは、11時間目にすべてを台無しにします。

注:ゲームにコンテンツの動的ストリーミングが含まれる場合、または複数のスレッドまたは異なるコアにゲームロジックがある場合は、幸運を祈ります。


0

記録と巻き戻しの両方を有効にするには、すべてのイベント(ユーザー生成、タイマー生成、通信生成など)を記録します。

イベントの各イベント記録時間、変更内容、以前の値、新しい値。

計算がランダムでない限り、計算値を記録する必要はありません
(これらの場合、計算値も記録するか、各ランダム計算後にシードする変更を記録することができます)。

保存されるデータは、変更のリストです。
変更はさまざまな形式(バイナリ、xmlなど)で保存できます。
変更は、エンティティID、プロパティ名、古い値、新しい値で構成されます。

システムがこれらの変更を再生できることを確認してください(目的のエンティティにアクセスし、目的のプロパティを新しい状態に変更するか、古い状態に変更します)。

例:

  • 開始からの時間= t1、エンティティ=プレーヤー1、プロパティ=位置、aからbに変更
  • 開始からの時間= t1、エンティティ=システム、プロパティ=ゲームモード、cからdに変更
  • 開始からの時間= t2、エンティティ=プレーヤー2、プロパティ=状態、eからfに変更
  • より高速な巻き戻し/早送りまたは特定の時間範囲のみの記録を有効にするには、
    キーフレームが必要です-常に記録する場合、時々ゲームの状態全体を保存します。
    特定の時間範囲のみを記録する場合、最初に初期状態を保存します。


    -1

    再生システムの実装方法に関するアイデアが必要な場合は、アプリケーションで元に戻す/やり直しを実装する方法をグーグルで検索してください。元に戻す/やり直しはゲームのリプレイと概念的に同じであることは一部の人には明らかかもしれませんが、すべてではないかもしれません。これは、巻き戻すことができる特別なケースであり、アプリケーションによっては、特定の時点までシークします。

    元に戻す/やり直しを実装する誰も、確定的/非確定的、フロート変数、または特定のCPUについて文句を言わないことがわかります。


    元に戻す/やり直しは、それ自体が基本的に決定論的で、イベント駆動型で、状態が軽いアプリケーションで発生します(たとえば、ワードプロセッサドキュメントの状態は、再計算可能なレイアウト全体ではなく、テキストと選択のみです)。

    そして、CAD / CAMアプリケーション、回路設計ソフトウェア、モーショントラッキングソフトウェア、またはワードプロセッサよりも高度な元に戻す/やり直しを行うアプリケーションを使用したことがないことは明らかです。元に戻す/やり直しのコードをコピーしてゲームで再生できると言っているのではなく、概念的には同じです(状態を保存して後で再生します)。ただし、メインのデータ構造はキューではなくスタックです。
    弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
    Licensed under cc by-sa 3.0 with attribution required.