見出し画像

【簡単な数学の応用】特定確率分布に従う乱数の生成について

こんにちは。G2 Studios xRディビジョンの泉です。

NPCキャラクターの動作や周辺環境がなんとなく不自然になって困ったことはありませんか?
今回はNPCキャラクターの動作や周辺環境を自然に見せるために、特定の確率分布になる乱数を発生させる方法をご紹介します。


はじめに

ゲーム内でのNPC動作や環境データの自動生成などに、乱数を用いることは一般的に行われると思います。
この乱数を一様な乱数といい、横を何らかの値、縦をその出現確率とすると、下図のようになります。確率分布上で、同じ確率のものしか返さない状態です。
例えば、サイコロ振った分布は⅙、⅙…で全て同じになります。

ですが、自然な状態というのは単純な乱数ではありません。
学校や新聞、テレビなどで出てきたことがあるのを見たことがあるかと思いますが、自然や社会統計の布図を並べると下のようなものがよく出てきます。

※グラフはGeoGebpraでのプロット

つまるところ、単純に一様な乱数を適用した場合、NPCキャラクターの動作や周辺環境がなんとなく不自然になる可能性があるということです。
それなら逆に、特定の確率分布になる乱数を発生させて、それを使えば自然になるのでは?
ということで、数理的な内容でゲームやAR/VRなどに応用できるネタとして、特定確率分布に従う乱数の生成を行う方法を紹介していこうと思います。

特定の確率分布に従う乱数を生成する方法

基本的に以下の手順になります。

①対象となる確率分布の確率密度関数から累積分布を作る。

②累積分布の逆関数を取る。
ただ逆関数はとれるが初等関数で表せない場合があり、この場合は通常計算ができないので近似関数を使う。

③逆関数に一様分布乱数を入力すると、出力が対象の確率分布に従う確率変数になる。

という感じですが、
・大体の人にとっては、既に知らない単語が並んでる。
・”∮”の記号を見て「なに、このワラビのマーク?」と思う人もいる。
と思うので、用語と数式などは途中に軽くまとめますが、基本的に概要とイメージをメインで記載します。

上記、①、②、③の累積分布への変換と、逆関数からの目的の分布に従う乱数を得るイメージは、こんな感じです。

※グラフはGeoGebpraでのプロット

↓ 逆関数を取るので、累積分布のY軸側から入力してx軸値が取得値。

Y軸の乱数の入力値が累積分布の傾きによって、X軸側で偏りがでるのがイメージできると思います。

【用語(分布について)】

逆関数と擬似逆関数

指数分布、標準分布について逆関数と擬似逆関数を見ていきます。

<指数分布>

数式を見ていても、実際にどう実装すればいいのか?結果がどうなるのか?がイメージしにくいと思いますので、プログラムに落として内容を出力してみます。

プログラムで書くとこんな感じです。

double exprand(double lambda) {
        return -lambda * System.Math.Log(random());
     }

単純にプロットしてみます。

  string ln = "";
    for(int i=0;i<1000;i++) {
      ln += exprand(2).ToString() + "\n";
    }
    Debug.Log(ln);

エクセルに貼り付け(A列)、ざっと見たいだけなので、intにキャストし(B列)、区間として1から10(C列)として、Frequencey(度数)を出すと、D列になります。

グラフにすると左下の形で、最初の指数分布グラフ(右下)と傾向が同じことが見て取れると思います。

例は1000件ですが、件数を増やすと分布グラフに近づいて行きます。

<標準分布>

プログラムで書くとこんな感じです。

 //mean 平均  sd 標準偏差
   double rand_normal(double mean,double sd) {
       double z = System.Math.Sqrt(-2.0f * Mathf.Log(random())) 
            * System.Math.Sin(2.0 * System.Math.PI * random());
       return mean + sd * z;
   }

先ほどプロットが下の分布に近づくことを見ましたので、今度はプロットに従う値をもつオブジェクトを並べてみます。

ものすごく適当ですが、1万個くらいのオブジェクトの高さを標準分布に従う乱数で決めたもの(上)と、単純乱数で決めたもの(下)を比べると、こんな感じになります。

 for (int y = 0; y < 100; y++) {
      for (int x = 0; x < 100; x++) {
        float h = (float)rand_normal(0, 1);
        GameObject g = Instantiate(refObj);
        float modh = Mathf.Abs(h) * 0.1f + 0.1f; //標準偏差だと(-5,5)くらいの幅で中央0だから0.1が普通高に
        g.transform.localScale = new Vector3(0.01f, modh, 0.01f);
        g.transform.position = new Vector3(x * 0.01f, modh / 2f, y * 0.01f);
  }}


[単純ランダム]  float modh = random() * 0.6f 
(上式の通り、見た目合わせに標準分布側を[0.1, 0.6)にしているので

上と下とで、だいぶ見え方の印象が違うと思います。草っ原だとすると、上の方が長さがバラバラで自然な感じに見えますよね。まあ、だからなんだという感じですが…(笑)

最後に 

今回は標準分布や指数分布だけを扱いましたが、動物の採取行動や、粒子の拡散運動に関するレヴィ分布などから動物を動かしたり、探索アルゴリズムを作成している例もあります。レヴィ系は図の交換と出力元を記載しました。

※グラフはGeoGebpraでのプロット重ね 
右はpyhonによる移動パターン反映プロット

擬似関数などを求めるには専門家の力を借りる必要がありますが、簡易なものであれば証明などを放置すれば自分でも計算できますし、関連する内容はweb上に多数あるので、一端でも興味があれば、出てきた言葉などで検索したり、プログラムコピーして、是非ご自身で実験していただければと思います。

▼G2 Studiosについてはこちら


X(Twitter)にて最新のお知らせを配信しております。ぜひフォローしてください!