RNGセッション
はじめに
決定論的設定では、同じ入力に対して常に同じ出力が得られます。しかし、一部の状況では、予測不可能なイベントの影響をシミュレートするためや、システムに一定の変動性を提供するために、ランダム性を導入したい場合があります。ランダム数生成器、通称 RNG
は、Quantum内でこれを実現するための重要なコンポーネントです。
RNGセッション
Quantumは開発者に RNGSession
を提供します。これを使用して、シミュレーション内で決定論的に擬似ランダム数を生成できます。フレームの globals
には、グローバルセッションがあらかじめ読み込まれています。セッションは、シードが異なる場合を除き、常に同じ数列を生成します。
使用例:
C#
// システム内
public override void OnInit(Frame f)
{
int randomNum = f.RNG->Next(0, 100);
}
globals
にあるセッションは、RuntimeConfig.Seed
フィールドでシードされています。必要に応じて、特定のシードを設定するのはユーザーの責任です。QuantumRunnerLocalDebug
を使用してオフラインセッションを実行している場合、シードは変更されず、生成される数列は常に同じになります。
提供された例のメニューを通じて実行している場合、ユーザーが自分でシードを変更しない限りシードは常に再ランダム化されます(例: ゼロのままにする)。
ランタイムでのシードの変更
必要に応じて、セッションを新しいものに上書きすることで、ランタイムでシードを変更可能です。例:
C#
// シミュレーション内
public void ResetSeed(Frame frame)
{
int newSeed = 100;
frame.Global->RngSession = new Photon.Deterministic.RNGSession(newSeed);
}
これは、セッションを頻繁にリセットして生成をさらに予測不可能にする必要がある場合に便利です。
コンポーネントの使用
開発時には、グローバルセッションの代わりにコンポーネントごとにRNGを持つ方が好ましい場合があります。これは、各エンティティがわずかに異なる挙動を示すために役立ちます。例えば、農業ゲームで植物が同時に成長するのではなく、異なる間隔で成長するようにすることができます。
C#
// DSLコンポーネント
component MyComponent
{
RNGSession Session;
}
グローバルセッションと同じ方法でシードを設定することもできます:
C#
public void InitComponentWithSeed(MyComponent* component)
{
int newSeed = 100;
component->Session = new RNGSession(newSeed);
}
予測の問題を回避する
コンポーネントごとにRNGを持つことで、カリングが予測されたエンティティの最終位置に影響を与えないことが保証されます。実際にロールバックが必要でない限り。
詳細については、こちらを参照してください:RNGの問題を避ける
チート
決定論の性質上、ランダム性の予測は非常に容易です。例えば、ハッカーはシミュレーションをローカルで読み取り、その後シミュレーションの外で RNGSession
を複製し、他の誰よりも先に与えられた数列を知ることができます。これに対抗する一般的な方法は、予測しにくい数値にシードを頻繁にリセットすることです。例えば、プレイヤーの入力をハッシュ化してその結果をシードとして使用することができます。これにより、誰もシードが何になるかを制御したり予測したりするのが非常に難しくなります。
決定論についての注意
重要:RNGセッションはQuantumシミュレーションコード内からのみ進め、Unityやビューコードからは決して進めないでください。これは、RNGセッションが内部的に状態値を保持しており、それがランダム数のシーケンスを決定するためです。
つまり、frame.RNG->Next()
(またはコンポーネント内のセッション)を使用すると内部状態が進行するため、ビューコードで誤って使用するとゲームがデシンク(チェックサムエラー)を引き起こすことになります。