Version: 2022.1
言語: 日本語
重要なクラス - Mathf
重要なクラス - Debug

重要なクラス - Random

Random クラスは、一般的に必要とされる様々な種類の乱数値を簡単に生成する方法を提供します。

このページでは、Random クラスの概要と、Random クラスを使ってスクリプトを作成する際の一般的な使い方について説明します。Random クラスのすべてのメンバーの詳細なリファレンスや、技術的な詳細については、Random スクリプトリファレンス を参照してください。

これらの便利な方法の詳細や例については、後述のリンクを参照してください。

単純な乱数

Random.value は、0.0 から 1.0 の間のランダムな 浮動小数点数 を生成します。一般的な使い方は、その結果を乗算して、0 から選択した範囲の数値に変換します。

Random.Range は、最小値と最大値の間 のランダムな浮動小数点数を生成します。指定した最小値と最大値が整数 (int) か浮動小数点数 (float) かによって、int または、float のいずれかを返します。

円または球体内のランダムな点

Random.insideUnitCircle は、半径 1 の 円内でランダムに選択された点 を返します (ここでもその結果を乗算することで、任意の大きさの円内でランダムな点を得ることができます)。

Random.onUnitSphere は、半径 1 の 球体の内部でランダムに選択された点 を返します。

Random.onUnitSphere は、半径 1 の 球体の表面でランダムに選択された点 を返します。

その他の種類の乱数値

Unity の Random クラスには、他にもいくつかのランダム値の種類があります。

ランダムな 回転 を生成するには、Random-rotation を使用します。

ランダムな を生成するには、Random.ColorHSV を使用します。

配列からのランダムな選択

配列要素のランダムな選択は、つまり、0 から配列の最大のインデックスの値 (配列の長さから 1 を引いた値) の範囲でランダムな整数を選ぶことです。これはビルトインの Random.Range 関数により簡単に行えます。

 `var element = myArray[Random.Range(0, myArray.Length)];``

Random.Range は最初のパラメーターを含み、 かつ、2 つめのパラメーターを除く範囲から値を返します。そのため、myArray.Length を使うと、正しい結果を得ることができます。

確率の異なるアイテムの選択

アイテムをランダムに選択する場合に、あるアイテムの選択確率を他よりも高くしたい場合があります。例えば、NPC キャラクターがプレイヤーに遭遇するときに、いくつかの異なる反応を示すとします。

  • 50% の確率で挨拶する
  • 25% の確率で逃げだす
  • 20% の確率で急に攻撃する
  • 5% の確率でお金をプレゼントする

これらのさまざまな結果を、細長い紙がその結果によって分割されていると考えて視覚化できます。それぞれの全体の長さに占める割合は、その結果が選択される確率に等しくなります。 選択を行うことは、細長い紙全体でランダムな点を選択し (例えば、ダーツを投げることによって)、それがどの部分にあるかを確認することと同じです。

スクリプトでは、細長い紙は実際には、アイテムのさまざまな確率を順番に含む float の配列です。 ランダムな点は、Random.value に配列内のすべての float の合計を掛けることによって取得できます (1 まで加算する必要はありません。重要なのは、さまざまな値の相対的なサイズです)。どの配列要素に “入っているか” 調べるために、最初に、点の値が最初の要素の値より小さいか比較します。入っていれば、選択されるのは最初の要素です。そうでなければ、最初の要素の値を点の値から引き算して、それを 2 つめの要素の値より小さいか比較することを繰り返して最終的に正しい要素を見つけます。コードでは次のようになります。

float Choose (float[] probs) {

    float total = 0;

    foreach (float elem in probs) {
        total += elem;
    }

    float randomPoint = Random.value * total;

    for (int i= 0; i < probs.Length; i++) {
        if (randomPoint < probs[i]) {
            return i;
        }
        else {
            randomPoint -= probs[i];
        }
    }
    return probs.Length - 1;
}

Random.value は 1 の値を返す可能性があるため、最後の return ステートメントが必要であるということに注意してください。このケースでは、検索はランダムな点をどこにも見つけることができないので、次の行を変更します。

 `if (randomPoint < probs[i])`

… 値を <= 記号に変更することで余分な return ステートメントを回避できますが、確率が 0 の場合でもアイテムを選択することも許容してしまいます。

連続的なランダム数の重み付け

個別の結果を持っている場合、float 配列の手法は上手く動作しますが、より連続的な結果を生成したい状況もあります。すなわち、宝箱で見つかった金貨の枚数をランダム化したいときや 1 - 100 の範囲のランダム値でもより低い値を取得する可能性が高いことを望む場合です。これを行うには float 配列の手法を使用すると、100 つの float から成る配列を作る必要があり (例えば、細長い紙の分割のように) 面倒です。また、整数に限らず範囲内のすべての数が必要な場合は、そのアプローチを使うことは不可能です。

連続した結果に対するもっと良いアプローチは、AnimationCurve を使用して ‘Raw’ なランダム値を重みを付けたものに変換することです。違うカーブの形状を描画することで、異なる重み付けを行うすることができます。コードももっと簡単です。

float CurveWeightedRandom(AnimationCurve curve) {
    return curve.Evaluate(Random.value);
}

0 と 1 の間の ‘Raw’ ランダムな値は、Random.value から読み込むことで選択されます。次に、curve.Evaluate() に渡され、水平座標として扱われます。 水平位置でカーブに対応する垂直座標を返します。カーブの浅い部分は選択される確率が高い一方、カーブの急な部分は選択される確率が低くなります。

リニアカーブは値の重み付けをまったく行いません。水平l座標はカーブ上の各点の垂直座標に等しい。
リニアカーブは値の重み付けをまったく行いません。水平l座標はカーブ上の各点の垂直座標に等しい。
このカーブでは始まりのとき浅く、終わりになると急カーブになっているので、小さい値で確率が高く、大きい値で確率が低くなります。x = 0.5 のときのカーブの高さは約 0.25 です。これは、0 から 0.25 の間の値を取得する可能性が 50% であることを意味します。
このカーブでは始まりのとき浅く、終わりになると急カーブになっているので、小さい値で確率が高く、大きい値で確率が低くなります。x = 0.5 のときのカーブの高さは約 0.25 です。これは、0 から 0.25 の間の値を取得する可能性が 50% であることを意味します。
このカーブは始まりと終わりの両方が浅く、極端に近い値がより一般的になり、中央で急カーブになるため、これらの値はまれになります。このカーブでは、高さの値が上にシフトされていることにも注意してください。カーブの一番下は 1 で、一番上は 10 です。これは、カーブによって生成される値が、前の例のように 0-1 ではなく、1-10 の範囲になることを意味します。
このカーブは始まりと終わりの両方が浅く、極端に近い値がより一般的になり、中央で急カーブになるため、これらの値はまれになります。このカーブでは、高さの値が上にシフトされていることにも注意してください。カーブの一番下は 1 で、一番上は 10 です。これは、カーブによって生成される値が、前の例のように 0–1 ではなく、1–10 の範囲になることを意味します。

これらのカーブは、確率論のガイドで見つかるかもしれない確立分布曲線ではなく、もっと逆累積分布曲線のようであることに注意してください。

スクリプトのうち 1 つに public のAnimationCurve 変数を定義することにより、値を計算する代わりに、視覚的に Inspector ウィンドウを通してカーブを見ながら編集することができます。

この手法では、浮動小数点数が得られます。整数の結果を計算したい場合 (例えば、82.1214 金貨ではなく、82 金貨が欲しい場合) は、計算した値を Mathf.RoundToInt() のような関数に渡せばよいだけです。

リストのシャッフル

よくあるゲームシステムに、既に決まったアイテム群からランダムな順番で選択するというものがあります。例えば、カードの山は一般的にはシャッフルして予想できる順番にならないようにします。各要素を他のランダムな配列インデックスの要素と入れ替えることによって、配列のアイテムをシャッフルできます。

void Shuffle (int[] deck) {
    for (int i = 0; i < deck.Length; i++) {
        int temp = deck[i];
        int randomIndex = Random.Range(i, deck.Length);
        deck[i] = deck[randomIndex];
        deck[randomIndex] = temp;
    }
}

アイテム群から繰り返しなしに選択

同じものを何度も選択することなしに、アイテム群から複数のアイテムをランダムに選択する、という作業があります。例えば、ランダムな生成地点で複数の NPC を生成したいが、各地点において 1 つの NPC のみを生成したい場合があります。これは、アイテムを順番に繰り返し、選択したアイテム群に追加するかどうかをランダムに決定することで実行できます。 各アイテムを順番にチェックするので、そのアイテムが選択される確率は、まだ必要なアイテムの数を、まだ選択可能な残りのアイテム数で割ったものに等しくなります。

例として、10 つの使用可能な生成地点から、5 つだけを選択する必要があるとします。最初のアイテムが選択される確率は、5/10 または 0.5 になります。それが選択されると、2 番目のアイテムの確率は 4/9 または 0.44 になります (つまり、4 つのアイテムがまだ必要で、残りの 9 つから選択できます)。ただし、最初のものが選択されなかった場合、2 番目の確率は 5/9 または 0.56 になります (つまり、5 つがまだ必要で、残りの 9 つから選択できます)。これは、必要な 5 つのアイテムが選択されるまで続きます。次のコードで行えます。

Transform[] spawnPoints;

Transform[] ChooseSet (int numRequired) {
    Transform[] result = new Transform[numRequired];

    int numToChoose = numRequired;

    for (int numLeft = spawnPoints.Length; numLeft > 0; numLeft--) {

        float prob = (float)numToChoose/(float)numLeft;

        if (Random.value <= prob) {
            numToChoose--;
            result[numToChoose] = spawnPoints[numLeft - 1];

            if (numToChoose == 0) {
                break;
            }
        }
    }
    return result;
}

選択がランダムであるにも関わらず、選択されたアイテム群は元の配列と同じ順序であることに注意してください。アイテムを 1 度に 1 つずつ順番に使用する場合は、順序は部分的に予測可能であるため、使用する前に配列をシャッフルする必要があるかもしれません。

空間上のランダムな点

立方体の体積内のランダムな点は、Vector3 の各コンポーネントをRandom.value が返す値に設定することによって、選択できます。

` var randVec = Vector3(Random.value, Random.value, Random.value);`

これにより、辺の長さが 1 ユニットの立方体内部の点が返されます。立方体は、ベクトルの X、Y、Z 成分に任意の辺の長さを掛けるだけでスケールできます。軸の 1 つを 0 に設定すると、点は常に 1 つの平面上にあります。例えば、“地面” のランダムな点の選択は、通常、X 成分と Z 成分をランダムに設定し、Y 成分を 0 に設定します。

ボリュームが球体の場合 (つまり、原点から指定された半径内にあるランダムな点が欲しい場合)、 Random.insideUnitSphere に任意の半径を掛けたものを使用します。

` var randWithinRadius = Random.insideUnitSphere * radius;`

結果として得られるベクトルの成分の 1 つを 0 にすると、円内のランダムな点を正しく得ることはできません。点は確かにランダムで正しい半径内にあるのですが、確率が円の中心に大きく偏っているため、点が非常に不均等に広がってしまいます。このタスクには代わりに Random.insideUnitCircle を使う必要があります。

 `var randWithinCircle = Random.insideUnitCircle * radius;`
重要なクラス - Mathf
重要なクラス - Debug