【Unity】Random.Stateの内容を取得する

Unity の乱数のシードを保持している Random.State という型があります。

定義は以下の通りでシリアライズして保存することはできますが、中身を見ることはできません。中身を認識する必要は本来ありませんが、リフレクションを使えば中身を取得できそうです。ただ単純な構造体なので Unsafe.As が使えそうなので試してみようと思います。

namespace UnityEngine
{
    [Serializable]
    public static class Random
    {
        [Serializable]
        public struct State
        {
            [SerializeField] int s0;
            [SerializeField] int s1;
            [SerializeField] int s2;
            [SerializeField] int s3;
        }

以下のように並び順が同じ(メモリレイアウトが同じ)構造体を用意して変換を実行し Random.State の内容を RandomState に移し替えます。

// RandomState.cs

[System.Serializable]
public readonly struct MyState
{
    // Random.Stateと同じ並び順でフィールドを宣言する
    public readonly int s0;
    public readonly int s1;
    public readonly int s2;
    public readonly int s3;

    public MyState(int s0, int s1, int s2, int s3)
    {
        this.s0 = s0;
        this.s1 = s1;
        this.s2 = s2;
        this.s3 = s3;
    }

    public static implicit operator MyState(Random.State state)
    {
        return ToMyState(state);
    }

    // Random.State → MyStateに変換する
    public static MyState ToMyState(Random.State state)
    {
        return Unsafe.As<Random.State, MyState>(ref state);
    }

    // MyState → Random.Stateに変換する
    public static Random.State ToRandomState(MyState state)
    {
        return Unsafe.As<MyState, Random.State>(ref state);
    }

    // 内容を文字列に変換する
    public override string ToString()
    {
        return $"{nameof(s0)}={s0}, {nameof(s1)}={s1}, {nameof(s2)}={s2}, {nameof(s3)}={s3}";
    }
}

使い方は以下の通りです。

RandomState state = RandomState.Convert(Random.state);
Log.Trace(state.ToString());

// Random.StateをMyStateに変換する
MyState state1 = MyState.ToMyState(Random.state);
Log.Trace(state1.ToString());
// s0=1282967314, s1=-563284738, s2=-1278752992, s3=241467906

// MyStateをRandom.Stateに戻す
Random.State state2 = MyState.ToRandomState(state1);

Unsafe.As はコストがほぼゼロで高速かつ GCAlloc もゼロで動作するのでこれで簡単に中身の値がコピーされます。