【Unity】少ない試行回数で乱数が偏っているように感じる件を調査
UnityではRandomクラスを使って乱数を取得することができます。
上の記事ではRandomクラスの使い方を簡単に紹介し、サイコロを振るサンプルも扱いました。
その中で気付いたのが、サイコロを振る試行回数が少ない時には結構偏るなーということ。
もちろん現実にサイコロを振っても同じように偏りはありますし、確率ってそもそも1/6で出ることを保証するものでもないので、ありうる現象なのです。あの人だけやたらとSSRいっぱい引いててガチャ運いいぜ! なんてのもありえる話なのです(血涙)
そうは言っても、ある人にはよく発生して、別のある人では全く発生しないなんてこともあるので、それがどのくらいの確率で起こりうるのか実験してみます。
今回調査する「偏り」とは確率的な偏りで、同じ乱数が続く、乱数のパターンが見られる、という話ではないので注意。それはまた別途確認します。
なお、今回もUnityのRandomクラスを使って実験しますが、これはあくまでUnityでこの乱数を使った時のケースなので注意。
環境
macOS 10.13 High Sierra
Unity2018.1.0f2
どんな実験を行うか
乱数はマクロ的な視点、すなわち試行回数が大きい場合には確率が収束しますが、少ない試行回数では必ずしも期待される確率で事象が発生するとは限りません。
例えば6面サイコロを10回振るとき、それぞれの目が出る確率は1/6 ≒ 16.67%ですが、6回サイコロを振った時点で1から6までの目が1回ずつ出ているとは限りません。
試行回数が10回の時、出た目の種類が1つから6つまでのケースでは以下の確率になります。
出た目の種類 | 試行回数 : 10 |
1 |
0.0000099229% |
2 |
0.0253530172% |
3 |
1.8516137022% |
4 |
20.3052364350% |
5 |
50.6365740741% |
6 |
27.1812128487% |
小数点以下の位を大きく取っているのは、1種類の目だけ出続ける確率がかなり小さいため。
この計算にあたっては、こちらのブログの記事を参考にさせていただきました。感謝です。
さて、この確率を使って、同じ抽選テーブルから6つのドロップアイテムを集めることを考えます。それらを素材として使い強力な武器を作れる、みたいなケース。
アイテムをドロップする確率もサイコロと同じく1/6にしておきましょうか。ドロ率16.67%とか大盤振る舞いですね(白目)
このアイテムを落とすのはなかなかの強敵という設定にしましょ。割と鍛えたキャラクターでも苦戦する感じで。イメージとしては世界樹の迷宮に出てくる3竜が1体に合体した感じで、そのドラゴンがドロップする6色の逆鱗を集めて加工すると真龍の剣が手に入る設定です。
ゲームをプレイするユーザーがこの敵に10回挑み、素材を回収して武器を作れる確率を求めます。1ユーザーあたり10回の抽選、ユーザー数が10000人として、武器を作れたユーザーはどれだけいるのか確認します。
試行回数が10回の時に全て揃う確率
上の表から、試行回数が10回の時に全ての素材が揃っている確率は約27.18%です。10000人のユーザーが挑んだら2718人が達成できているくらいの確率。
検証の方法として、スクリプト内でサイコロの目に対応する1から6の乱数を取得して配列に格納します。
その配列はユーザーごとに作成され、各ユーザーが10回ボスを倒して素材が集まったかどうかを確認します。
最後に素材を集めきったユーザーの数、および各ユーザーが何種類の素材を集めたかを集計し、CSVファイルに出力することにしましょ。
CSVに出力するメッセージを書いていたら長くなっちゃいましたが、スクリプトはこんな感じ。
ジャグ配列を使って配列の配列を作ります。1つ目の実験では一人当たりの試行回数が10回ですが、2つ目の実験では素材を集め終わるまでサイコロを振り続けるため、一人当たりの配列の長さが変わります。複数の長さが混在する配列も管理できるのでジャグ配列は便利。
各ユーザーは10回サイコロを振ったとして、RollADice()の中でRandom.Rangeを使って1から6の値を取得します。10回終了時点で獲得した素材を確認し、その数を辞書に登録します。この時、6つ集まっていたら達成扱いとしています。
OutputLine()の中では、CSVファイルに出力するための文字列を生成しています。その中でCheckAchievedを呼んで、達成状況を確認しています。
達成状況の確認では、各ユーザーの配列を確認し、その中で一致する数字があれば辞書にtrueを格納しています。辞書のキーである1から6までがtrueになったら達成したことになるので、達成人数に1を追加します。
各ユーザーの処理が終わったら、最後にサマリーとして集まった素材の種類ごとに人数と確率を計算しています。これを上で挙げた理論値と比較してみたいと思います。
結果の比較
上のスクリプトを実行した結果は以下の通り。左からそれぞれ、出た目の種類、試行回数(討伐回数)が10回のときの理論値、結果の確率、結果の人数です。理論値は結果と合わせて桁を減らしています。
出た目の種類 | 試行回数 : 10 (理論値) | 結果の確率 | 結果の人数 |
1 |
0.0000% | 0% | 0 |
2 |
0.0254% | 0.0100% | 1 |
3 |
1.8516% | 1.9700% | 197 |
4 |
20.3052% | 20.2700% | 2027 |
5 |
50.6366% | 50.6200% | 5062 |
6 |
27.1812% | 27.1300% | 2713 |
こうして結果を眺めると、面白いくらいに理論値に沿っています。これをみるとUnityのRandomクラスで生成される乱数は確率的に優れている気がします。
ただ機械の方ではそうであっても、2, 3種類の素材しかドロップしなかった約200人のユーザーにとってはそう感じないでしょう。おそらく彼らは「乱数が偏っている!」と声をあげるに違いありません。
Twitterなどで「乱数偏ってるぜ!」なんて呟けば、「俺も!」「俺も!」と呼応するツイートが出てくるはずです。なぜなら彼らには心強く、そして運の悪い仲間が200人もいるのですから!
ドロップ率が16.67%だとあっさり揃いそうな気がしてしまうのも罠ですね。1/6だから6回引けばだいたい揃うくらいの確率、だなんて誤解が蔓延していそうですし。
確率論的に起こりうることでも、人間の体感だとこれも「偏り」だと感じてしまうんです。少ない試行回数だとこれがより顕著に感じられると思います。
まとめ
確率論的には起こりうることでも、ユーザーの体感としては偏りと感じられることもあります。
特に試行回数が少ない場合にそれが顕著に見られます。
10,000人のユーザーがいたら200人くらいは「偏り」を感じる結果になったので、ゲーム内のドロップ率などは単純に数字だけではなく、体感としてどうなるかも考慮に入れる必要がありそう。
ちなみに、今回取り上げた『全部揃えると別のアイテムが手に入る方式』はゲーム内の(金銭の授受が発生しない)アイテムドロップだったらセーフですが、金銭のやりとりが発生するガチャなどに適用するといわゆるコンプガチャとなり、景品表示法違反になるので絶対にやらないように。
2012年頃ソシャゲ界隈で話題となっていたあの悪名高きコンプガチャです。サイコロですらこんなに揃わないのに、ソシャゲのガチャの確率を考えたら……あぁ、なんと恐ろしい時代があったのでしょうか。
……話がそれましたが、今回分かったのはUnityのRandomクラスによる乱数は、確率論的にいい感じだったことです。
おまけ
乱数を使った脱出ゲームを作りました。脱出に必要なのはゴールを引き当てる運だけです。
ブラウザで遊べるのでこちらも是非。
ゲーム開発の攻略チャートを作りました!
-
前の記事
GitHubでアラート! Your account has been flaggedが表示されたよ 2018.06.21
-
次の記事
【Unity】乱数を使ってサイコロの1から6が全部出るまで走る逆鱗マラソン 2018.06.22
コメントを書く