【Unity】C# Job Systemからでもランダムを使いたい
今回はC# Job Systemでランダムを使うアプローチについてです。
UnityEngine.Randomがメインスレッドでしか動作しない
シューティングの弾や移動の判断、様々な要因でランダムを使いたくなります。特に C# Job Systemでデモ的な何かを作ろうと思った時など、要素が同じ動きにならないように動きをバラけさせたくなります。
ここで UnityEngine.Randomがメインスレッドでしか動作しない という問題にぶち当たります。
UnityException : RandomRangeInt can only be called from the main thread
強引なアプローチではランダムテーブルを作ってズラすという物もありますが、今回は異なるアプローチを試します。
Unity.Mathematics.Random
Mathematicsに追加された新しいAPI Unity.Mathematics.Random を試します。
このAPIはUnityEngine.Random
と異なり、以下のような特徴があります。
- 複数のインスタンスを持てる
(他の箇所でRandomを実行しても再現性がある) - 別スレッドからも呼べる
- Burstで最適化される
特に有り難いのがインスタンスを持てる事による再現性ですが、これは逆を言えば インスタンスを用意し初期化しないと使えません 。static関数ではないので、インスタンスの管理や生成といった手間が入ります。
RandomをC# Job Systemで使ってみる
Randomを使用してみます。
作ったのは下のような物です。
- ボールが跳ねる
- 着地時に別の座標(ランダム)へ移動
とりあえずジョブにランダムを突っ込む。しかし
ランダムをとりあえず突っ込んでみます。普通に C# Job Systemのフィールドとして登録 し、中で使用するという感じです。
この時、UnityEngine.Random
とUnity.Mathematics.Random
の宣言が重複するので、using Random = Unity.Mathematics.Random;
とか宣言しておきます。
これで実行してみますが、Random自体は使えてるのですが 値が非常に偏ります 。 対策にと毎フレームSeedを設定しても下のような感じ。対策しないと本当に偏る…というか最早固定値?
同じ結果が出ないようにする
というのもジョブは実行時にコピーを作るので、Nextで次の値を取得しても大本に反映されない ため、全てのジョブで 最初のRandom を取得してしまってるからだと思われます。実際、上のはジョブ生成時に新しいSeedを与えた場合で、Seedを与えなければ全てのジョブが毎回同じ結果を返します。
毎回同じ結果を返さない為にジョブ毎にSeedを変えるかインスタンスを共有するかする必要があり、スレッドセーフにしてアクセスとか色々考えましたが、最終的に NativeArrayでインスタンスを共有してシングルスレッドでBurstかけてぶん回したほうが早いなー という結論に至りました。Burst強い。並列処理だと複数のスレッドからRandomにアクセスしちゃうのでヤバイですが、まぁIJobProcessComponentData.ScheduleSingle
でシングルスレッドで動かせばまあ良いかなという発想です。
あ、別にStateを共有しても問題ないです。進行を保存出来れば良いので。
コード
感想
Randomを毎回維持してくれるのは有り難い事なのですが、インスタンスが独立してるせいで若干面倒くさい目に合いました。
なお .NET 4系を要求 します。.NET 3系の場合は諦めてランダムテーブルなり自前実装なりをするのが良いです。
関連
Randomのソースコード。xorshiftベース github.com
”ランダム”についての詳しい解説
www.slideshare.net乱数の独自実装が紹介されています。 kan-kikuchi.hatenablog.com