Unityでゲームを作ってるので乱数について勉強を始めてみたよ
ビバ乱数!
と言いながら、乱数のどんな点を見ることでその性能を測ることができるのか、いまいち分かっていなかったのでまとめることにします。
一口に性能といっても、暗号化とかその辺まではダイブせず、あくまでゲーム作りの範囲内での性能を測ることを目的とします。
環境
macOS 10.13 High Sierra
Unity2018.1.0f2
乱数って何?
乱数、乱数と口にしていますが、何ですかねこれ。
やれ乱数2発だの、やれ高乱数1発だのと、よく聞く中に乱数という文字が多々含まれていますが、これを口にする彼らのうち、どれだけの人が乱数を真に理解しているのでしょうか。
私は理解してない方なので今回調べている訳ですが、乱数とはざっくり言うとランダムな数字の並び(数列)のことです。次に何が起こるか分からない数列を意味します。
現実の例だと、1回6面サイコロを振った時、次にサイコロを振って出てくる数字は1から6のどれか分かりません。1かもしれないし、5かもしれません。このように次に何が起こるか分からない状態を数列で表現したものが乱数です。
コンピュータは決められたことしかできないので、実際に使われているのは乱数を模した擬似乱数なのであります。擬似と付いているだけあって、乱数っぽく振舞っているだけで、本物の乱数ではないんですね。
じゃあ本当の乱数って何なのさ? というと、偏りがなく、次に何か来るかも分からないし、再現もできない乱数が本当の乱数なんだって。これを真の乱数と呼ぶみたい。
強い乱数、弱い乱数
そんなの人の勝手。本当に強い開発者なら、自分の好きな乱数で勝てるよう頑張るべき。
実は乱数には『弱い乱数』『強い乱数』『真の乱数』の3種類があるみたい。これは暗号化界隈で使われている言葉のようです。
『弱い乱数』は偏りのない乱数、『強い乱数』は偏りがなく次に何が来るか分からない乱数、『真の乱数』は上でも挙げたけど偏りなし、予測もできないし再現もできない乱数のこと。
このうち、『真の乱数』を実現するのは物理現象くらいで、コンピュータのソフトウェアで実現するのは無理そう。マイクから雑音を拾って乱数として使ったり、CPUの温度を見て乱数として使ったりなどハードウェアと協力すればギリできるけど、あくまでもソフトウェアの範囲だけだと無理っぽい。
なので、ゲームでは完全ランダムな『真の乱数』は無理でも、『強い乱数』あたりを狙うと良さそうです。次に来る値が分かっちゃったら、ゲームおもしろくないもんね。RTAとかTASでは乱数表を使って状況再現とかバンバンやってるけど、あれはひとまず置いておこう。
強い乱数の性能
性能に目を向けると、以下を確認しておきたいところ。
偏りのなさ
取り得る値の範囲内で、満遍なく値が返されることが望ましいですね。
確率に重み付けがなされていなければ、等しい確率で出してくれる乱数がいい乱数です。
例えば6面サイコロを使って1から6までの範囲でランダムに値を返して欲しいとき、3だけ全然出ません! となるとイケてない乱数と呼ばれます。
6面サイコロならどの面も1/6の確率で出て欲しい訳です。
なので、試行回数を増やして何度もサイコロを振らせ、より1/6、つまり16.666…..%に近付く乱数は性能が良いと言えそう。
20%, 30%, 50%のような重み付けがあるのであれば、その確率に近いほど性能が良さそうです。
次に何が来るか分からない
完全ランダムはソフトウェアでは実現できませんが、なるべくそれに近い形で乱数を生成することは可能です。
ソフトウェアで乱数を生成する場合、乱数の元になる数値を何らかのアルゴリズムに通すと、次の乱数が得られるようになっています。
アルゴリズムがシンプルだと次に来る値が予測できてしまいますが、複雑なら何が来るか分かりません。あ、ユーザーがゲームを遊んでいる範囲での「何が来るか分からない」なので、厳密な予測不可能性まではいらないと思います。
「3が出たから次は5が出る」とか、「偶数が出たから次は奇数が出る」みたいな規則性がない、くらいのイメージね。
暗号化界隈だったら全くの予測不可能であるべきですけど、そこまではいいかな。
周期が長い
上の『次に何が来るか分からない』とちょっと似てますが、ソフトウェアで生成している乱数はどうしても周期が生まれてしまいます。
例えばサイコロの出る目が「1-5-6-2-3-4-5-1-4-3-6-2-1-5-6-2-3-…」の順番になっていたら、12回で1周期の乱数になります。乱数がループする! とかのアレです。
周期が短いとすぐ予想がついてしまいますが、同じ並びの乱数が出るまでの長さが長ければ、なかなか予測はできないはず。
なのでこの周期が長い乱数生成法なら性能が良いと言えそうです。
乱数生成が早い
ゲーム的には非常に大事な特徴です。
いくら不規則で予測不可能なランダムの値を生成できると言っても、その生成に時間がかかるのでは台無し。
ゲームの実行中に、プレイヤーにストレスを与えない範囲で素早く乱数を生成するのが大事です。
乱数生成法の例
細かい特徴は置いといて、よく使われる乱数生成法を挙げます。
- 線形合同法
- メルセンヌ・ツイスタ
- Xorshift
上から下に向かうにつれ新しくなります。これらのアルゴリズムに沿って様々なライブラリが開発されています。
線形合同法は生成できる乱数のうち下位ビットに規則性が出やすく、奇数と偶数が交互に出るなど予測可能な乱数となりがち。ただ、昔からある生成法なので問題点も研究されていて、改善がしやすい方法でもあります。
メルセンヌ・ツイスタは既存の乱数生成法の弱点を補ったようなアルゴリズムで、周期が\({2}^{19937}-1\)という気が遠くなるような長さの乱数生成法です。統計的にも均等に分布しているので偏りが少ないです。
何より名前がめちゃくちゃかっこいい。声に出して読みたい。
Xorshift(エックスオアシフト)は高速な乱数生成法。メルセンヌ・ツイスタより周期は短いものの、生成速度は高速。品質も良いし、コードも短くて済み、ゲームを作るだけならこれで良いかも。
なおUnityであれば組み込みのUnityEngine.Randomを使うのが楽ちんです。(アルゴリズムは不明)
まとめ
乱数について、ゲーム作りの観点から、巷に存在する乱数生成アルゴリズムのどこを見て性能を判断すべきかをまとめました。
偏りのなさ、規則性のなさ、周期の長さ、生成の速さが大事なポイントかと思うので、自分で乱数を選択することがあればこの辺りを重点的に確認したいなーと。
とはいえUnityを使っている間はUnityのRandomクラスを使うのがお手軽なのでそれを使っちゃうかも。
Unity道場の資料が公開されているので、こちらも見ておくととても良いと思います。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【Unity】乱数を使って空間に点やオブジェクトを描画してみる実験 2018.06.27
-
次の記事
【Unity】RequireComponentで安全にコンポーネントを操作する 2018.06.30
コメントを書く