【Unity】乱数(Random)をループ内で使用すると戻り値が同じになる

Unity

Unity初心者です。

初心者の為、いろいろな場所で躓くので、備忘録として実施した技術についてブログに記載するようにしています。

今回は乱数で詰まったので、それについて記載をしてみたいと思います。

ゲームを作る上でランダム要素は必須と言っても過言ではないので、Randomを使用した乱数作成は何処かで使用することになるかと思います。

故にUnity(C#)の乱数に関する情報は、Web上に多く掲載されていたのですが、なかなか解決できなかった現象が発生しました。

それは、ループ内で乱数(Random)を使用すると戻り値が常に同じになってしまうという現象です。

静的なインスタンスを使えば乱数の戻り値が同じではなくなる

まず、自分が作ってしまったバグソース(NGパターン)がこちらです。

public static class Utility
{
    public static int getRamdom(int _max){
        System.Random random = new System.Random((int)DateTime.Now.Ticks);
        return random.Next(0, _max);
    }
}

「0~引数で与えた数値-1」の範囲にある乱数を返却してくれる関数です。これ自体はうまく動いていたので、プロジェクト内で使いまわしていました。

しかし、ループ内で乱数を作成する関数を呼び出してみるとバグが発生してしまいました。

以下は10回乱数を取得して、デバッグに書き込むというコードですが…

void Start()
{

    for (int i = 0; i < 10; i++)
    {
        Debug.Log(Utility.getRamdom(10));
    }
}

↑なんか5に滅茶苦茶偏った。

これは偶然なんだろうかと思いもう1回実行すると

↑7と0しか出現しない。

明らかに乱数の戻り値が同じになっているとしか考えられないです。

そこで色々と調べて改良したコード(OKパターン)がこちらです。

public static class Utility
{
    private static System.Random random;
    public static int getRamdom(int _max){
        if (random == null) random = new System.Random((int)DateTime.Now.Ticks);
        return random.Next(0, _max);
    }
}

乱数を生成する時に使用するRandomクラスのインスタンスを静的な変数に入れて使い回すだけで解決をすることが出来ました。

こちらのコードを先程と同じ用にループ内で使用すると以下のような結果になります。

駄目だったときと比較すると、明らかに乱数がバラバラになっていることがわかります。

何故乱数の戻り値が同じになってしまったのか

public static class Utility
{
    public static int getRamdom(int _max){
        System.Random random = new System.Random((int)DateTime.Now.Ticks);
        return random.Next(0, _max);
    }
}

上記のNGソースがなぜ駄目なのかが当初理解できませんでした。

今回はSystem.Randomに対して、DateTime.Now.Ticksをシードに与えています。

DateTime.Now.Ticksについては以下のサイトによるとナノ秒単位で値を戻してくれるため、それをシードに使えば問題なさそうに思えました。

DateTimeクラスのTicksプロパティは、0001年1月1日00:00:00からの経過時間を100ナノ秒単位で返してくれる便利な奴ですが、ミリ秒程度ならともかく、マイクロ秒を飛ばしていきなり「ナノ秒」なんて言われても、ぱっと計算できるものではありません。

[Silverlight]Ticksを秒に変換 - 秘密結社ぎゅう☆ぎゅう倶楽部

現在のPCがいくら高性能と言っても、ナノ秒単位であれば別の値が与えられると思ったからです。

しかし、更に良く調べてみるとナノ秒単位で戻してくれるのは間違いないですが、精度としてはミリ秒単位までしか追従しないという情報もありました。

ミリ秒であればループのスピードに負けてシードが同じ値となってしまうということも納得できます。

静的な変数を使用して解決したのは何故か

何故静的な関数を使用したら乱数の戻り値が同じでなくなったか。

これは「そりゃそうだろう」と言われそうですが、Nextメソッドを押す度に乱数を作成するからです。

値が同じになってしまうというのは、シードが同じ場合にはNextで返される数値の順番が一緒になってしまうということです。

System.Random random = new System.Random(10);
for (int i = 0; i < 5; i++)
{
    Debug.Log(random.Next(0, 10));
}

random = new System.Random(10);
for (int j = 0; j < 5; j++)
{
    Debug.Log(random.Next(0, 10));
}

1つ目のループと2つ目のループでデバッグログに記載される値は全くおなじになるはずです。

例えばですが

8,5,4,2,0

8,5,4,2,0

という感じですかね。

しかし、これはシードを与え直した時に同じ順番で取れてしまうというのが問題なので、今回のように静的な変数で1つのインスタンスを使い回す場合には、ほぼ関係がないことです。

凄く基本的な部分だと思いますが、うまく理解できておらずかなり時間を使ってしまいました(´・ω・`)

コメント

タイトルとURLをコピーしました