投稿日:

    カテゴリー 技術 / デザイン / 制作

    ああ素晴らしき乱数の世界

    乱数とは?世の中の予測できない色々のこと。

    土日はゲーム制作に勤しむ私ですが、ゲームと切っても切れない関係なのが乱数。ガチャを回したり、敵を出現させたり、カードをシャッフルしたりいろんな場面で使います。

    いろいろ奥が深いのですが、乱数って楽しいよ!
    ということを語っていきます。

    用語説明

    乱数

    人の手で操作できない、他の事象と関連がない、予測のできない数のこと。

    真の乱数

    自然界に存在する乱数。
    水の流れる様子や、木の葉のこすれる音、星のきらめきなどのこと。

    疑似乱数

    コンピュータで生成する乱数。
    計算によって作り出されているので、一見バラバラだが実は予測することができる。

    シード値

    疑似乱数を作る時に使う、元となる数字。
    シード値が同じなら常に同じ乱数が作られる。
    シードは種の意味。

    「シード」は開発者ならよく聞く言葉ですよね。

    真の乱数を求めて ~粒子編~

    粒子が動くときはランダムウォークという性質があるらしく、それゆえ自然界には完全な真の乱数があるといえそうです。

    ※酔っ払いみたいにふらふら動くので日本語で「酔歩」とも。他にもブラウン運動というのもあるらしい。

    この性質を利用した乱数生成器も売られています。
    仕組みは超簡単で、真ん中に向かって粒子を飛ばして

    Aの板に当たれば0、Bの板に当たれば1とすればいいわけです。
    粒子1個で1ビットですから、64ビットの乱数が欲しければ64個粒子を飛ばせばいいわけですね。

    他にも、粒子の「電子なだれ」という性質を利用した乱数生成器もあり、8千円ぐらいで買えます。

    8千円で真の乱数が買えるなら安いものだと思いますが、みなさんはどう思われますか?

    真の乱数を求めて ~アルゴリズム編~

    続いて、もう少し我々に馴染みのある話を。

    疑似乱数はほとんどの開発言語に標準装備されていますが、採用しているアルゴリズムはまちまちです。
    代表的なアルゴリズムは3つ。

    線形合同法

    C言語、Java

    メルセンヌツイスタ

    php、python、Ruby

    Xorshift

    JavaScript(Chrome)

    (ただし、JavaScriptで乱数をどのように実装するかはブラウザに委ねられているので、
    Chrome以外のブラウザでどのように実装されているかはわかりませんでした。)

    自分の使っている言語のアルゴリズムが気にくわなければ、好きなものを実装すればいいと思います。
    それぞれの数式は、ネットを探せば見つかります。

    Javaと乱数

    線形合同法は古いアルゴリズムで、生成される乱数に偏りがあるらしいです。
    (過去にはサイコロゲームのサイコロで奇数しか出なかったとかなんとか・・)

    Javaはモダン言語(?)なのに線形合同法を使っているため開発者から白い目で見られていますが、
    私は今までずっと使っていて特に不便を感じたことはないです。

    使うときは次のことに留意すればいいと思います。
    1.むやみにランダマイズしない。(1回の起動あたり1回でOK)
    2.最初の1回は捨てる。(米を砥ぐときは最初の水は捨てますよね。それと同じです。)

    ただし、暗号化などで使うのはやめましょうね。

    乱数のコクとは!?

    で、ここまでの話は余談で、
    乱数をどのように使うのか?というのがゲームの面白さを左右する重要なポイントになります。

    生成した疑似乱数は、全ての値の出る確率が均一なので、機械的な印象になるんですね。
    適度加工することによって、自然な乱数に感じるというわけです。

    これを「乱数にコクを出す」といいます。

    正規乱数

    正規乱数(正規分布)は、自然界によく見られる乱数です。

    ダーツをイメージすればわかると思いますが、
    中心を狙って何本も投げると、中心に近くなるほどダーツが集中しますよね。

    それを再現できるのがこちらのコード。

    final int DICE = 5;
    
    double d = 0;
    for(int i=0; i<DICE; i++) {
        d += Math.random();
    }
    return d / DICE;

    DICEの数を増やすほど中心に偏り、少なくするほどバラバラになります。
    TRPG(ボードゲームの一種)で遊んだことがある人はピンとくる乱数ではないでしょうか。

    ブラウンノイズ

    全ての値の出る確率が一定である「ホワイトノイズ」に対し、
    入力値が高ければ高いほど高い結果が得やすくなる乱数をブラウンノイズといいます。

    ブラウンノイズにはいろいろな数式が考えられますが、こういうのはどうでしょうか?

    double input = 10.0;  //入力値
    
    final double RATE = 0.5;  //定数
    
    return input * (1 - RATE + (RATE * 2 * Math.random()));

    攻撃力10のキャラクターが敵に攻撃した場合に与えるダメージをイメージしてみました。
    攻撃力に比例して、最大±50%のゆらぎがあります。
    Math.random()の部分を正規乱数にすればいい感じの乱数になりそうです。

    ピンクノイズ

    ピンクノイズはブラウンノイズと同様に、入力値が大きいほど大きい結果が得られますが、
    大きい数字が得られる確率がすごく少ないという特徴があります。

    特に決まった数式はないのですが、私が自分で作った数式を特別に公開しちゃいます。

    double input = 1.05;//入力値
    
    final double BREAK_RATE = 0.14;//定数
    final double UP_RATE = 1.2;//定数
    
    double d = 1;
    while (true) {
        if (Math.random() < BREAK_RATE) {
            break;
        }
        d *= UP_RATE;
    }
    return input * d;

    この数式を使うと非常に中毒性の高いゲームになるので、法律で規制した方がいいのでは?
    と思うほどです。(自画自賛ジョーク)

    なお、数値の上限がないため、
    オーバーフローが気になる方はwhileの条件式を調整すれば問題ありません。

    パーリンノイズ

    今一番気になるのはこれ!

    グラフィックの世界で自然現象を再現するのによく使われるのが「パーリンノイズ」。
    マインクラフトの地形生成にも使われているそうです。

    パーリンノイズで作られた水面。やばい。

    パーリンノイズを覚えれば、3Dモデルが作れなくてもリッチな3Dゲームが作れるかも!
    ・・と思うと夢が広がりますね~。

    最後に

    この記事の説明には聞きかじった情報と超訳が混ざっているのでご注意ください。
    へーそーなんだー、ぐらいにとどめといてください!