投稿者 tel | 2017年10月31日

Unityで乱数の状態を保存する

UnityでRandomの状態を保存する方法について考えてみた。

目的

ゲーム中に適当なタイミングでアプリを落としても、再度起動したときに続きからできるようにしたい場合、ゲーム中で使っている乱数の状態を保存しておく必要がある。

自前で乱数生成器を用意しているならそのインスタンスのメンバーを保存しておけばいいが、Unity標準のRandomだと状態を表すメンバー(Random.state)をどうにかして保存する必要がある。

https://docs.unity3d.com/ScriptReference/Random.html

Jsonにする

Random.State自体はシリアライズ可能なのでJsonUtilityでテキストにできる。

string jsonText = JsonUtility.ToJson(Random.state);
//{"s0":1508130514,"s1":-328652069,"s2":493493096,"s3":56588297}
Debug.Log(jsonText);

//0.4200476
Debug.Log(Random.value);
Random.state = JsonUtility.FromJson<Random.State>(jsonText);

//0.4200476
Debug.Log(Random.value);

Random.Stateのメンバーを保存する

JSONだと文字列なのでバイナリで保存するためにメンバーを取得する。Random.Stateの定義は以下のようになっているので内部のプライベートな値を取り出して保存する。

[Serializable]
public struct State
{
    [SerializeField]
    private int s0;

    [SerializeField]
    private int s1;

    [SerializeField]
    private int s2;

    [SerializeField]
    private int s3;
}

リフレクションでメンバーをとりだす

プライベートになっているメンバーをリフレクションで取り出す。Random.Stateは構造体なので値を設定するときはobject型にキャストする必要がある。

var type = Random.state.GetType();
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
int s0 = (int)type.GetField("s0", flags).GetValue(Random.state);
int s1 = (int)type.GetField("s1", flags).GetValue(Random.state);
int s2 = (int)type.GetField("s2", flags).GetValue(Random.state);
int s3 = (int)type.GetField("s3", flags).GetValue(Random.state);

// -1061744947 -490763294 580657707 -1041360120
Debug.LogFormat("{0} {1} {2} {3}", s0, s1, s2, s3);

// 0.6847113
Debug.Log(Random.value);

var state = (object)Random.state;
type.GetField("s0", flags).SetValue(state, s0);
type.GetField("s1", flags).SetValue(state, s1);
type.GetField("s2", flags).SetValue(state, s2);
type.GetField("s3", flags).SetValue(state, s3);
Random.state = (Random.State)state;

// 0.6847113
Debug.Log(Random.value);

キャストしてメンバーにアクセスする

リフレクション使うとパフォーマンスの問題がつきまとうのでunsafeコードで無理やりキャストしてメンバーにアクセスする。

[System.Serializable]
public struct SerializableRandomState
{
    [SerializeField]
    public int s0;
    [SerializeField]
    public int s1;
    [SerializeField]
    public int s2;
    [SerializeField]
    public int s3;

    unsafe public static SerializableRandomState Serialize(Random.State state)
    {
        return *((SerializableRandomState*)&state);
    }

    unsafe public static Random.State Deserialize(SerializableRandomState state)
    {
        return *((Random.State*)&state);
    }
}

メンバーをpublicにしてあるので値を保存できる。

var state = Random.state;
var ss = SerializableRandomState.Serialize(state);

// -490763294 580657707 -1041360120 -1185438562
Debug.LogFormat("{0} {1} {2} {3}", ss.s0, ss.s1, ss.s2, ss.s3);

// 0.3264405
Debug.Log(Random.value);
Random.state = SerializableRandomState.Deserialize(ss);

// 0.3264405
Debug.Log(Random.value);
広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

カテゴリー

%d人のブロガーが「いいね」をつけました。