【Unity】乱数の基本的な使い方と応用

Unityで乱数を扱いたい場合、通常はUnity標準のUnityEngine.Randomクラスを用いれば問題ないでしょう。
しかし、毎回同じ順序で結果が出力される乱数や、重みを持たせた乱数を扱いたいケースがあるかもしれません。

例えば、地形の自動生成を毎回同じパターンで出力したいけど、爆発やエフェクトはランダムで出力したい場合などです。

このような場合、乱数生成器を独立して持たせる必要がありますが、UnityEngine.Randomでは対応できません。
代わりにMathematicsパッケージのUnity.Mathematics.Randomクラスを用いることで実現可能です。

重み付け乱数は乱数の出力結果を加工することで実現可能です。

本記事では、乱数の基本から前述で示した応用方法について解説していきます

スポンサーリンク

Unity標準の乱数を使う

Unityには、乱数を扱うクラスUnityEngine.Randomが用意されています。
乱数を取得するには、次のメソッドを使います。

public static float Range(float min, float max);
public static float Range(int min, int max);

minとmaxにはそれぞれ取得する乱数の下限と上限を指定します。
上限値の境界を含むかどうかは引数型によって異なり、floatの場合は上限を含みますが、intの場合は含みません

例えば、minに0、maxに100が指定されていたとして、

両者がfloat型なら、乱数の範囲は0以上100以下になりますが、
両者がint型の場合、乱数の範囲は0以上100未満になります。

また、範囲が0以上1以下の場合に限り、次のプロパティで乱数を取得できます。

UnityEngine.Random.value

使用例

RandomTest.cs
using UnityEngine;
using UnityEngine.UI;

public class RandomTest : MonoBehaviour
{
    /// <summary>
    /// 次の乱数に更新するボタン
    /// </summary>
    [SerializeField] private Button _button;

    /// <summary>
    /// 乱数を表示するテキスト
    /// </summary>
    [SerializeField] private Text _text;

    /// <summary>
    /// 初期化
    /// </summary>
    private void Awake()
    {
        // 更新ボタンが押されたとき
        _button.onClick.AddListener(() =>
        {
            // Unity標準のランダム関数で[0, 100)の範囲の値を取得&表示
            _text.text = UnityEngine.Random.Range(0, 100).ToString();
        });
    }
}

実際に動かすと、ボタンを押すたびに乱数の表示が更新されていきます。

複数の乱数生成器を用いる

Unity標準のRandom.Range()はstaticメソッドであり、例えばキャラの攻撃パターンとマップ生成用に独立した乱数を使いたい場合、対応できないというデメリットがあります。

このような場合、Unity標準以外の乱数生成器で代用する必要があります。

このように複数の乱数生成器が欲しい場合、Unity.Mathematics.Randomが使えます。

Struct Random | Package Manager UI website

このクラスはPackage Managerで管理されているため、使用する前にMathematicsパッケージをあらかじめ追加してください。

乱数生成にはXorShiftが採用されているようなので、高速演算可能かつ、それなりに高精度な乱数が得られます。

高精度と言えば、メルセンヌツイスターが有名ですが、 ゲームのような用途ではXorShiftが主流と言って過言ではないでしょう。

使用例

RandomTest2.cs
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.UI;

public class RandomTest2 : MonoBehaviour
{
    [System.Serializable]
    private struct RandomInfo
    {
        /// <summary>
        /// 次の乱数に更新するボタン
        /// </summary>
        public Button button;

        /// <summary>
        /// 乱数を表示するテキスト
        /// </summary>
        public Text text;
        
        /// <summary>
        /// 乱数の種
        /// </summary>
        public uint seed;

        /// <summary>
        /// 乱数生成器
        /// </summary>
        [System.NonSerialized]
        public Unity.Mathematics.Random _random;
    }

    /// <summary>
    /// 乱数情報
    /// </summary>
    [SerializeField] private RandomInfo[] _randomInfo;

    /// <summary>
    /// 初期化
    /// </summary>
    private void Awake()
    {
        for (var i = 0; i < _randomInfo.Length; i++)
        {
            var info = _randomInfo[i];

            // 乱数生成器作成
            info._random = new Unity.Mathematics.Random(info.seed);

            // 更新ボタンが押されたとき
            info.button.onClick.AddListener(() =>
            {
                // 独立した乱数生成器から[0, 100)の範囲の値を取得&表示
                info.text.text = info._random.NextInt(0, 100).ToString();
            });
        }
    }
}

各々に独立した乱数が生成できるようになります。
乱数の種の値が一緒なら、取得される数値の順序が全く一緒になります。

乱数に重みをもたせる

これまで紹介した乱数は、指定範囲から一様な確率で決定される値でした(一様分布)
しかしながら、値によって出現確率を変動させたいケースも存在するでしょう。

実現方法はいくつかありますが、ここでは比較的実装が簡単な累積分布関数を用いる方法をご紹介します。

累積分布関数

確率的に取りうる値X (これを確率変数と言います)が、あるx値以下になる確率を表す関数です。

次の数式で定義されます。

F(X)=P(X \leq x)

確率変数Xには、例えば乱数値を指定したりします。
今回のケースでは、前述のRandom.Range()の戻り値を指定すればよいでしょう。

確率変数Xが下限値の場合はF(X)=0、上限値の場合はF(X)=1となります。

実装例

アニメーションカーブを用いて累積分布関数を定義できるようにしてみました。
その一例のサンプルです。

RandomTest3.cs
using UnityEngine;
using UnityEngine.UI;

public class RandomTest3 : MonoBehaviour
{
    /// <summary>
    /// 次の乱数に更新するボタン
    /// </summary>
    [SerializeField] private Button _button;

    /// <summary>
    /// 乱数を表示するテキスト
    /// </summary>
    [SerializeField] private Text _text;

    /// <summary>
    /// 累積分布関数
    /// カーブの指定範囲が下限と上限に相当
    /// </summary>
    [SerializeField] private AnimationCurve _cdf;

    /// <summary>
    /// 初期化
    /// </summary>
    private void Awake()
    {
        // 更新ボタンが押されたとき
        _button.onClick.AddListener(() =>
        {
            if (_cdf.length <= 0) return;

            // Unity標準のランダム関数を確率変数にとり、
            // 累積分布関数から値を取得&表示
            var min = 0f;
            var max = _cdf.keys[_cdf.length - 1].time;
            var randVariable = UnityEngine.Random.Range(min, max);
            _text.text = _cdf.Evaluate(randVariable).ToString();
        });
    }
}

0.5の値が非常に出やすい累積分布関数を用いたデモです。

まとめ

Unity標準の乱数で事足りることが殆どですが、今回はその応用も含めてご紹介させていただきました。

ご参考にして頂ければ幸いです。

関連記事

参考サイト

スポンサーリンク