Version: Unity 6.0 (6000.0)
言語 : 日本語
一般的な数学関数の使用
コンパイルとコードリロード

ランダム性の使用

Switch to Scripting

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

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

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

単純な乱数

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

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

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

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

Random.insideUnitSphere は半径 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 配列による方法を使用してこれを行うには Float 配列 (紙の区切られた部分) を 100 個設定する必要がありますが、これでは扱いづらくなってしまいます。また、整数に制限せずに範囲内の任意の数を得たい場合は、この方法は使用できません。

連続した結果に対するより適切なアプローチは、AnimationCurve を使用して ‘未処理’ のランダムな値を ‘重み付けした’ 値に変換することです。さまざまなカーブの形状を描画することで、さまざまな重み付けを行うことができます。コードの記述も簡単です。

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

0 と 1 の間の ‘未処理’ のランダムな値を Random.value から読み込んで選択します。次に、水平座標としてそれを処理する curve.Evaluate() に渡すと、その水平位置でカーブに対応する垂直座標が返されます。カーブの浅い部分は選択される可能性が高まり、急な部分は選択される可能性が低くなります。

カーブが直線になっている場合は値の重み付けが行われません。水平座標はカーブ上の各点の垂直座標に等しくなります。
カーブが直線になっている場合は値の重み付けが行われません。水平座標はカーブ上の各点の垂直座標に等しくなります。
このカーブは、開始地点では浅く、終了地点では急であるため、低い値を得る可能性が高くなり、高い値を得る可能性は低くなります。x = 0.5 の直線上のカーブの高さは約 0.25 であることから、0 から 0.25 の間の値を取得する可能性が 50% であることがわかります。
このカーブは、開始地点では浅く、終了地点では急であるため、低い値を得る可能性が高くなり、高い値を得る可能性は低くなります。x = 0.5 の直線上のカーブの高さは約 0.25 であることから、0 から 0.25 の間の値を取得する可能性が 50% であることがわかります。
このカーブは始まりと終わりの両方が浅いため、開始地点と終了地点に近い範囲の値を得る可能性が高くなります。中央のカーブは急になっているため、この範囲の値が出る可能性は低くなります。前のカーブで生成される値の範囲は 0 から 1 でしたが、このカーブでは高さの値が増えています。カーブの開始地点は 1 で終了地点は 10 になっているため、カーブで生成される値は 1 から 10 の範囲になります。
このカーブは始まりと終わりの両方が浅いため、開始地点と終了地点に近い範囲の値を得る可能性が高くなります。中央のカーブは急になっているため、この範囲の値が出る可能性は低くなります。前のカーブで生成される値の範囲は 0 から 1 でしたが、このカーブでは高さの値が増えています。カーブの開始地点は 1 で終了地点は 10 になっているため、カーブで生成される値は 1 から 10 の範囲になります。

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

スクリプトの 1 つにパブリック変数 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 つずつ順番に使用する場合、順序は部分的に予測可能になるため、使用前に配列をシャッフルする必要があるかもしれません。

空間におけるランダムな点

キューブ内のランダムな点は、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;

一般的な数学関数の使用
コンパイルとコードリロード