システムを一連の状態と関数で構成されているように表示できます。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 == 0
Q
Q
Q == 100
s[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 ^^など)。真剣に、決定論的コンピューターで非決定論的プログラムを作成できれば、フィールズメダル、チューリング賞、そしておそらくオスカーとグラミー賞を同時に獲得することになるでしょう。