Unity 製のゲームを対象として、乱数の再現ができないかを調べてみた
先に結果を述べておくが、現時点ではシード値の再現ができず失敗している
乱数のアルゴリズム
128 bit 版の Xorshift
Xorshift は乱数として品質が高く(値の偏りなどが無い)、周期もそれなりの長さ(2^32-1 or 2^64-1 or 2^128-1)で、値を生成する計算時間も短い(xor とシフトのみ)という特徴を持つ比較的新しい(2003年開発)乱数生成アルゴリズム
Unity で使用されているのは 128 bit 版で、周期は 2^128 - 1≒3.4 * 10^38
とてもじゃないけど、1周目の乱数を記録して2周目を利用するには周期が長すぎてムリがある
参考
乱数のシード
シードの初期値
アルゴリズムは分かっているので、単純に、シード値が再現できれば乱数も再現できるハズ
シードの初期値には、ゲームのプレイヤーが直接指定できる場合もあれば、ハードや OS などゲームが動く環境で持っている時計から取得した値を利用する場合や、なにも値を指定せずにデフォルトのシード値を使ってる場合がある
ゲームプレイヤーがシード値を指定できないタイプだった場合、シード値がどのように決められているかを知ることは、乱数を再現するにあたり有効であると考える
Unity の乱数のシード初期値は Unity のドキュメントに「自力でランダムな初期化をする場合は (int)DateTime.Now.Ticks を使ってね」と書かれているが、シードを指定しなかった時の初期値についての記載が見当たらず不明
デフォルトシード値の検証
環境の時計から取得した値をシード値に利用するのがよくある印象
なので、デフォルトシードで生成した乱数と、Windows の時計から取得した値を利用したシードで生成した乱数とが一致するような時計の値を探そうとした
(Unity 上で実行すると、前に実行した時のシード値の影響を受けるらしいので、検証では出力した exe を毎回実行)
デフォルトのシードと DateTime.Now.Ticks を使ったシードとで乱数を生成して比べてみたものの、(おそらくは)乱数の初期化が行われるタイミングが異なるぽくて、同じ乱数を生成できない
Windows の時計のカウントを止めようとしたが、失敗している
1 ms ごとに時計の現在値を上書きすることで DateTime.Now のカウントを止めようと思い、C# で自作プログラムを作って試してみた
DateTime.Now の書き換え自体はできたが、1 ms ごとに時計を上書きすることができなかった
また、時計の上書きプログラムを動かしている間は PC が重くなって、他の exe を起動できないという問題も発生した
(時計の書き換えプログラムの中でゲームの exe 起動も行ってみたが、当然ながらゲームの立ち上がり自体も重い)
プログラムでの記述は 1 ms でも実際の精度は 1 ms にはならず、Windows が複数のプログラムを同時に動かすためのしくみによるものらしい
そのしくみによって標準の分解能が 10 ms くらいに設定されていて精度も出ないらしく、分解能を 1 ms に設定することも可能だが、結局は精度の保証はされない
参考
- システム時計の日時を設定する
- Sleepの精度を上げる
- Windows, C++ で処理時間を計測。そしてtimeBeginPeriod()。
- 時間処理の落とし穴 - 時間精度とtick
- 1msの分解能を得る方法について
- マイクロ秒単位のスリープについて
- マイクロ秒単位で制御できる時間関数
ここまでやった段階で中断
0 件のコメント:
コメントを投稿