RNGセッション
はじめに
決定論的な設定では、同じ入力に対して常に同じ出力が得られます。しかし、予測不能なイベントの影響をシミュレートしたり、システムに一定のばらつきを与えるために、ランダム性を導入したい状況があります。乱数生成器、通称RNG
は、Quantum内でこれを実現するための重要なコンポーネントです。
RNGSession
Quantumが提供しているRNGSession
を使用すると、シミュレーション内で疑似乱数を決定論的に生成できます。フレームのglobals
には、グローバルセッションがプリロードされています。同じシードを使用するセッションは、常に同じ数列を生成します。
使用方法:
C#
// システム内
public override void OnInit(Frame frame)
{
int randomNum = frame.RNG->Next(0, 100);
}
globals
のセッションはRuntimeConfing.Seed
フィールドからシードが与えられます。必要に応じて、特定のシードを与えることもできます。QuantumRunnerLocalDebug
でオフラインセッションを実行している場合は、シードは変更されず、常に同じ数列が生成されます。
サンプルメニューから実行している場合、ユーザーが自身でシードを変更しなければ(例:0のままにする)、常にランダムなシードが与えられます。
実行時のシード変更
必要に応じて、セッションを上書きすることで、実行時にシードを変更できます。以下がその例です。
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
を複製することで、事前に乱数列を知ることができます。これに対抗する一般的な方法は、シードを頻繁にリセットして、数値を簡単に予測できないようにすることです。例えば、プレイヤーの入力をハッシュ化して、その結果をシードとして使用します。これによって、シードを操作したり予測したりすることが非常に困難になります。
決定論性についての備考
重要: RNGSession
は、Quantumシミュレーションコード内でのみ使用し、Unity/ビューコードでは絶対に使用しないでください。RNGSession
は乱数列を決定するステート値を内部的に持っているため、これは非常に重要です。
つまり、frame.RNG->Next()
(またはコンポーネントのセッション)や同様のAPIを使用すると、内部ステートが進行するため、ビューコードで誤って使用するとゲームの同期ズレ(チェックサムエラー)を引き起こします。