ランダムな地形を毎回同じパターンで生成する方法はないの?
いくつか方法があるわ。Unityの乱数は疑似乱数と呼ばれ、計算によって乱数を求めるから可能なの。
再現可能な乱数を得る方法の紹介です。
本記事の内容を実践すると、毎回同じパターンの乱数生成が実装できるようになります。
乱数にはUnity標準のUnityEngine.Randomクラスのほか、Mathematicsパッケージ提供のUnity.Mathematics.Random構造体が存在します。
両者とも乱数の内部状態を持っており、状態が一緒なら次に求まる乱数値は毎回同じ(再現可能)です。
また、後者のUnity.Mathematics.Random構造体はメインスレッド以外でも使用可能というメリットがあります。
本記事では、UnityEngine.RandomクラスおよびUnity.Mathematics.Random構造体それぞれにおいて再現可能な乱数として扱う方法を解説していきます。
- Unity 2022.1.7f1
目次 非表示
乱数の内部処理について
乱数再現の実装を理解するにあたって、まず既存の乱数の内部処理について軽く触れておきます。不要な方は手順の解説までスキップしてください。
Unityが扱う乱数は疑似乱数であり、ある規則に従って乱数が計算されます。
例えば、UnityEngine.RandomクラスはXorshift 128アルゴリズムをにより疑似乱数を実現しています。
乱数の内部状態が同じなら、次に求まる乱数も同じです。
疑似乱数では、初期状態をシード値によって決定することで、毎回同じ乱数列を得ることができます。
Unityが提供する乱数には、最初から使えるUnityEngine.Randomクラスのほか、Mathematicsパッケージをインポートして使えるUnity.Mathematics.Random構造体が存在します。
どちらも再現可能な乱数を得られますが、後者のUnity.Mathematics.Random構造体のほうがマルチスレッドに対応している、独立した乱数生成器として扱うのが簡単というメリットがあります。
Mathematicsパッケージが使える環境ならUnity.Mathematics.Random構造体を使う方が実装も手軽で得られるメリットも大きいです。
シード値を指定して乱数を再現する
UnityEngine.Randomクラスでは、シード値を指定して状態を初期化することができます。これはUnityEngine.Random.InitStateメソッドを通じて行います。
引数にはシード値を指定します。このシード値が同じなら、得られる乱数列も同じになります。
参考:Random-InitState – Unity スクリプトリファレンス
実際の使い方は以下の通りです。
指定されたシードで乱数を初期化し、スペースキーが押されるたびに乱数値をログ出力する例です。
上記スクリプトをRandomExample.csという名前で保存し、適当なゲームオブジェクトにアタッチすると機能するようになります。
必要に応じて、インスペクターからシード値を設定します。
実行結果
この方法の問題点
例で示した方法では、複数の目的で乱数を使用した場合、乱数の再現ができなくなる可能性がある欠点があります。
例えば、毎回同じ地形のパターンを生成するときに、不定期に発生するエフェクト類などで乱数を活用した場合、異なる地形が生成されてしまうという問題が生じます。
これは、取得する乱数の順序が異なってしまうためです。
次に、この問題を解決する方法について解説します。
独立した乱数列を再現する
UnityEngine.Randomクラスはシングルトンであり、提供されているメソッドやプロパティはすべてstaticです。
そのため、複数の目的で再現可能な乱数を扱えるようにしたい場合、そのまま乱数取得では上手くいかず、乱数の状態を別々で保持しておく必要があります。
乱数の内部状態は、UnityEngine.Random.stateプロパティとしてアクセスできます。
参考:Random-state – Unity スクリプトリファレンス
このように複数の再現可能な乱数を扱いたい場合、乱数の使用箇所で次のようなステップを踏む必要があります。
- UnityEngine.Random.stateプロパティの値を一時退避
- UnityEngine.Random.stateプロパティに内部状態を一時的に設定
- 乱数取得
- UnityEngine.Random.stateプロパティに一時退避した値を戻す
毎回この操作を行うのは面倒なので、メソッド化しておくなどで使いやすくしておくと良いでしょう。
以下、複数の再現可能な乱数を使用できるようにするヘルパークラスの実装例です。
使用の際は、上記スクリプトを適当な場所に保存しておきます。
呼び元からは以下のようにして使います。
2つの乱数をそれぞれキーボードの1キーと2キーで独立して取得し、ログ出力する例です。
上記スクリプトをReproduceRandomExample.csという名前で保存し、適当なゲームオブジェクトにアタッチすると機能するようになります。
必要に応じてシード値を設定します。
実行結果
それぞれの乱数で同じ順序の値を返していることを確認できました。
Unity.Mathematics.Randomで独立した乱数を管理する
Mathematicsパッケージをインストールする必要がありますが、Unity.Mathematics.Random構造体を使うと、簡単に独立した乱数を扱うことができます。
更に、メインスレッド以外からも使用可能というメリットも存在します。
参考:Struct Random| Mathematics | 1.2.6
Unity.Mathematics.Random構造体はUnityEngine.Randomクラスとは違い、シングルトンではありません。使用時にはインスタンス化して使います。
以下、Unity.Mathematics.Random構造体を使って乱数を取得する例です。
スクリプトの使い方は先述の例と一緒です。
実行結果
同様に乱数の再現が実現できていることを確認できました。
さいごに
再現可能な乱数を使用する方法を解説しました。
乱数を使いたい場所で、それぞれ状態を持ったインスタンスを作成して管理できるようにすれば実現できます。
差支えが無ければ、Unity.Mathematics.Randomを使用するのが手軽で確実です。