乱数完全マスター
ユニティ・テクノロジーズ・ジャパン合同会社
フィールド・エンジニア 安原 祐二
今回のおはなし
仕組み
使いかた
前半
後半
今回のおはなし
仕組み
使いかた
前半
後半
コンピュータ計算のきほん
0か1しか入れられない箱
ビット(bit)と数える
1?0
32ビットの場合
2 -1
まで数えられる
(4,294,967,295)
32
32個
二進数で32桁
32ビットの場合
32個
限界を越えちゃったらどうなる?
2 -1
まで数えられる
(4,294,967,295)
32
二進数で32桁
繰り上がったぶんは無視
2 で割った余りになっている
32
無視! 計算結果
32個
9 9 90
3桁しかないデジタル表示は
1000(10 )で割った余りになっている
3
999の次は000
例えば限界のところに1加えると・・
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
無視! 計算結果
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
無視!
1
循環する
ゼロにもどる
計算結果
乱数を作ってみよう
乱数使用例
計算でバラバラの数を作ること!
簡単な乱数を作ってみよう
var a = Random.value;
そのたびに異なる値になる
擬似乱数とは
0〜7の範囲で乱数を作りたい
前回の値に1を加える
新しい値
前回の値
a←a+1
範囲を越えた
番目 値
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
0〜7の範囲で乱数を作りたい
a←a+1
前回の値に1を加える
前回の値に1を加えて8で割った余り
0〜7の範囲になる
a←(a+1)%8
番目 値
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 0
0〜7の範囲で乱数を作りたい
循環している
前回の値に2を加えて8で割った余り
0246しか出てこない…
0〜7の範囲で乱数を作りたい
a←(a+2)%8
番目 値
0 0
1 2
2 4
3 6
4 0
5 2
6 4
7 6
8 0
前回の値に3を加えて8で割った余り
0〜7の範囲で乱数を作りたい
a←(a+3)%8
番目 値
0 0
1 3
2 6
3 1
4 4
5 7
6 2
7 5
8 0
前回の値に3を加えて8で割った余り
すべての数字がまんべんなく現れた
乱数の完成!
0〜7の範囲で乱数を作りたい
a←(a+3)%8
番目 値
0 0
1 3
2 6
3 1
4 4
5 7
6 2
7 5
8 0
class Random {
int s = 0;
int get() {
s = (s+3)%8;
return s;
}
}
プログラム例
まんべんなく現れるためには
「前回の値に3を加えて8で割った余り」
ふたつの数が互いに素であればよい
互いに素とは
ふたつの数の共通の約数が存在しない
ーという分数が約分できない
3
8
マメ知識
同じ値が続けて出ることもないと…
大きな値で循環させて
必要な範囲を取り出す
ちょっとまって!
乱数ってそういうもの?
まんべんなく出ればいいって…
a%6
50で循環させて 0~5を取り出す
番目 値 値
0 0 0
1 37 1
2 24 0
3 11 5
4 48 0
5 35 5
6 22 4
7 9 3
8 46 4
9 33 3
10 20 2
11 7 1
12 44 2
13 31 1
14 18 0
15 5 5
a←(a+37)%50
0~2 -1の範囲で乱数を作りたい
32
前回の値にQを加えて2 で割った余り
32
0~2 -1の範囲で乱数を作りたい
32
前回の値にQを加えて2 で割った余り
32
a←(a+Q)%2
32
0~2 -1の範囲で乱数を作りたい
32
前回の値にQを加えて2 で割った余り
32
Qは2 と互いに素のものを選ぶ
32
Qは奇数であればいい
a←(a+Q)%2
32
32ビットなら余りを計算する必要なし!
前回の値にQを加えて2 で割った余り
32
0~2 -1の範囲で乱数を作りたい
32
Qは2 と互いに素のものを選ぶ
32
Qは奇数であればいい
a←(a+Q)%2
32
a←a+Q
32ビットなら余りを計算する必要なし!
前回の値にQを加えて2 で割った余り
32
0~2 -1の範囲で乱数を作りたい
32
Qは2 と互いに素のものを選ぶ
32
Qは奇数であればいい
a←(a+Q)%2
32
a←a+Q
32ビットで循環できた
ただ奇数を足すだけで乱数ができた
乱数のきほん
Unity の Random クラス
1. Random.InitStateで初期化
2. Random.valueなど
呼ぶ度に異なる値を返す
シード
151
乱数のしくみ
シードで初期値を与える
シード
151 260
260
乱数のしくみ
シードで初期値を与える
乱数を取得できる
シード
151 260
260
37
37
乱数のしくみ
シードで初期値を与える
あとは取得し続ける
シード
151 260
260
37
37
180
周期
乱数のしくみ
シードで初期値を与える
あとは取得し続ける
ある周期で循環する
乱数の特徴
再現する
(シードが同じなら同じパターン)
周期がある
(同じパターンを繰り返す)
乱数を使うときは
常に意識する
再現させるかどうか
ごとに
ユーザ
起動 乱数を発生させたい
同じ
別の
プレイ
再現させたい場合
シードを固定する
シードを変化させる
再現させたくない場合
現在時刻
ボタンを押すまでの時間
システムクロック
シードを設定せずに
乱数を使わない!
ぐらいの気持ちで
シードはだいじ
いろいろな生成法
LCG(線形合同法) 19??年
さすがに使われなくなってきた
高速
品質(バラバラ度)に問題あり
例 a←a×1103515245+12345
メルセンヌ・ツイスタ 1997年
周期が超絶の2 -1
19937
十分に高速(数百回に一度ちょっと重くなる)
品質は最上級
メモリ使用大きめ(4.8KiB)
Xorshift 2003年
シンプル&高速
質も高い
周期は2 -1版、2 -1版、2 -1版32 64 128
例 a = a ^ (a<<13);
  a = a ^ (a>>17);
  a = a ^ (a<<15);
PCG 2014年
高速
極めて質が高いらしい
決定版かもしれない?
今後採用事例が増えるかも
Unityで生成器を自作
実装の理由
生成法を掌握
系統を分ける
エフェクト
サイコロ
敵キャラ
再現不能
あちこちで使用すると…
系統をわける
var randA = new MyRandom();
var randB = new MyRandom();
ふたつ作る
UnityEngine.Random
はこれができない
ゲーム進行用
エフェクト用
影響を受けない
再現可能に
そうは言っても
自作はなあ…
UnityEngine.Randomには便利関数がそろっている
作るのは面倒だしバグも怖い
組み込みRandomを
オブジェクト化する例
public class MyRandom {
private Random.State state;
public MyRandom() : this((int)System.DateTime.Now.Ticks)
public MyRandom(int seed) {
setSeed(seed);
}
public void setSeed(int seed) {
var prev_state = Random.state;
Random.InitState(seed);
state = Random.state;
Random.state = prev_state;
}
public int Range(int min, int max) {
var prev_state = Random.state; // 使用前の状態
Random.state = state; // 前回の状態にセット
var result = Random.Range(min, max);
state = Random.state; // 現在の状態を記録
Random.state = prev_state; // 使用前の状態に
return result;
}
Random.state を
保存・復活する
Xorshift
C#実装例
public class MyRandom {
private uint x, y, z, w;
public MyRandom() : this((uint)DateTime.Now.Ticks) {}
public MyRandom(uint seed) {
setSeed(seed);
}
public void setSeed(uint seed) {
x = seed;
y = x*3266489917U+1;
z = y*3266489917U+1;
w = z*3266489917U+1;
}
public uint getNext() {
uint t = x ^ (x << 11);
x = y; y = z; z = w;
w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
return w;
}
}
参考
間違った用法
良さげな結果
乱数っぽい
どうしてこうなった
失敗例良さげな結果
乱数っぽい
for (var y = 0; y < texture.height; ++y) {
for (var x = 0; x < texture.width; ++x) {
texture.SetPixel(x, y,
Random.Range(0, 2) == 0 ? white : black);
}
}
良さげな結果
すべての座標で白か黒を
乱数で決定
var i = 0;
for (var y = 0; y < texture.height; ++y) {
for (var x = 0; x < texture.width; ++x) {
Random.InitState(i);
++i;
texture.SetPixel(x, y,
Random.Range(0, 2) == 0 ? white : black);
}
}
失敗例
シードを毎回設定している
シードを設定
シードを単純にセットしまくるのは
想定していない生成法が多い
とはいえ。
シードの設定が必要なゲームの例
100面あるダンジョンの…
迷路や敵の強さを…
その都度生成しつつ…
パターンを固定したい。
シード設定が一度だけの場合
ステージ1 ステージ2 ステージ3 ステージ4
シード
ステージジャンプで問題
ステージ1 ステージ2 ステージ3 ステージ4
シードA
シードB シードC シードD
各ステージでのシード設定は必要
var i = 0;
for (var y = 0; y < texture.height; ++y) {
for (var x = 0; x < texture.width; ++x) {
Random.InitState(i);
++i;
texture.SetPixel(x, y,
Random.Range(0, 2) == 0 ? white : black);
}
}
失敗例のコード(再掲)
自然にこうなる
最初にボスの性別を決めるとする
何がいけないのか
var i = 0;
for (var y = 0; y < texture.height; ++y) {
for (var x = 0; x < texture.width; ++x) {
Random.InitState(i);
++i;
texture.SetPixel(x, y,
Random.Range(0, 2) == 0 ? white : black);
}
}
←ここ。単純な増加がダメ
シードも乱数にしてみよう
var i = 0;
var rand = new System.Random(1234);
for (var y = 0; y < texture.height; ++y) {
for (var x = 0; x < texture.width; ++x) {
Random.InitState(rand.Next());
texture.SetPixel(x, y,
Random.Range(0, 2) == 0 ? white : black);
}
}
←乱数
さあ、結果は?
良さげ!
良さげ!
しかし、これはダメ
なぜかというと…
シードを乱数にしてしまうと
周期が 2 より大きい場合、
衝突は起きる
衝突が起きる
(同じ値が出る)
32
可能性は低いが、
予測するのは困難
衝突しないバラバラの値が欲しい
そんなときは
ハッシュ関数
xxHash がおすすめ
(Yann Colletさん作)
xxHash の実装例
※入力を32bitにした
int xxhash(int data, int seed) {
uint v = (uint)seed + 374761393U + 4U;
v += (uint)data * 3266489917U;
v = ((v << 17) | (v >> 15)) * 668265263U;
v ^= v >> 15;
v *= 2246822519U;
v ^= v >> 13;
v *= 3266489917U;
v ^= v >> 16;
return (int)v;
}
参考
xxHash は衝突しないのか?
https://qiita.com/yuji_yasuhara/items/adefc967c51a6becca08
調べてみました
大丈夫。
var i = 0;
for (var y = 0; y < texture.height; ++y) {
for (var x = 0; x < texture.width; ++x) {
Random.InitState(xxhash(i));
texture.SetPixel(x, y,
Random.Range(0, 2) == 0 ? white : black);
}
}
xxHashでシードを作る
xxHashを呼ぶ
さあ、結果は?
良さげ!
ひと安心
擬似乱数の恐ろしさ
問題に気付きにくい
擬似乱数の恐ろしさ
「偶数と奇数が交互に出る」
開発中に気づくのは困難
問題に気付きにくい
ましてやステージボスの性別にパターンが生じるなど…
恐さを知っておこう!
Perlinノイズ
Perlin ノイズ
ホワイトノイズ Perlinノイズ
とても便利!ただの乱数
void Update() {
var x = Mathf.PerlinNoise(Time.time, 0f);
var pos = new Vector3(x, 0f, 0f);
transform.position = pos;
}
Mathf.PerlinNoiseの使用例
第1引数に現在時刻(秒) 第2引数にゼロ
Mathf.PerlinNoise(Time.time, 0f); の意味
ゼロ
Time.time
上の縁をなぞる
この画像は16x16
16.0
16.0
16x16
1.0
1.0
2.0
2.0
1.0x1.0 2x2 4x4
8x8 16x16 32x32
別系統のノイズ(シードのかわり)
ゼロ
Time.time
Mathf.PerlinNoise(Time.time, 0f);
8.0 Mathf.PerlinNoise(Time.time, 8f);
2以上離せばOK
大きなノイズ
細かいノイズ
足す
デモ:PerlinNoiseアニメーション
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PerlinNoiseTest : MonoBehaviour {
Quaternion rotation_;
float c0_;
float c1_;
float c2_;
void Start () {
rotation_ = transform.localRotation;
c0_ = Random.Range(0f, 1f);
c1_ = Random.Range(2f, 3f);
c2_ = Random.Range(4f, 5f);
}
void Update () {
var freq0 = Time.time*0.4f;
var freq1 = Time.time*1f;
var ratio0 = 0.8f;
var ratio1 = 0.1f;
transform.localRotation = Quaternion.Euler(new Vector3((Mathf.PerlinNoise(freq0, c0_)-0.5f)*ratio0 + (Mathf.PerlinNoise(freq1, c0_)-0.5f)*ratio1,
(Mathf.PerlinNoise(freq0, c1_)-0.5f)*ratio0 + (Mathf.PerlinNoise(freq1, c1_)-0.5f)*ratio1,
(Mathf.PerlinNoise(freq0, c2_)-0.5f)*ratio0 + (Mathf.PerlinNoise(freq1, c2_)-0.5f)*ratio1) * 10f) * rotation_;
}
}
Perlinノイズアニメーション
で使用したコード
参考
PerlinNoiseの周期は?
どう作ってあるか次第
Unityの実装では256.0で周回
void Update() {
m_Time += Time.deltaTime;
m_Time = Mathf.Repeat(m_Time, 256f);
var x = Mathf.PerlinNoise(m_Time, 0f);
…
256.0で繰り返しておく
これで永遠に動く
〜作ってみよう〜
噴出花火
一様乱数 正規乱数
正規乱数
Particle System
+正規乱数
Particle System
デモ:Particle System 実験
float getND() {
float x = Random.value;
float y = Random.value;
float v = Mathf.Sqrt(-2f * Mathf.Log(x)) * Mathf.Cos(2f * Mathf.PI * y);
return v;
}
正規乱数のつくりかた
ボックス・ミューラー法
処理速度も問題ない
public class MyParticleEmitter : MonoBehaviour {
float getBoxMuller()
{
float x = Random.value;
float y = Random.value;
float v = Mathf.Sqrt(-2f * Mathf.Log(x)) * Mathf.Cos(2f * Mathf.PI * y);
return v;
}
void emit()
{
var ps = GetComponent<ParticleSystem>();
var ep = new ParticleSystem.EmitParams();
ep.position = Vector3.zero;
var vx = getBoxMuller()*6f;
var vy = getBoxMuller()*6f;
ep.velocity = new Vector3(vx, vy, Random.Range(45f, 55f));
ps.Emit(ep, 1);
}
void Update()
{
for (var i = 0; i < 8; ++i) {
emit();
}
}
}
参考
正規乱数パーティクルの
コード
〜作ってみよう〜
カード配り
デモ:カード配り
摩擦つきの移動(外力なし)
速度がある値より低くなったら
摩擦つきの移動(外力なし)
速度がある値より低くなったら
乱数でカード上の一点を選択
摩擦つきの移動(外力なし)
速度がある値より低くなったら
乱数でカード上の一点を選択
回りながら進み、止まる
その点への摩擦力を追加
摩擦つきの移動(外力なし)
物理を使っておくと
条件の変更に強い
実際に起きていることを考察する
Rigidbody.AddForce
Rigidbody.AddTorque
Rigidbody.AddForceAtPosition
public class Card : MonoBehaviour {
bool stopping_ = false;
Vector3 position_;
static float height = 0f;
void Start() {
var rb = GetComponent<Rigidbody>();
transform.position = new Vector3(0f, height, 0f);
height += 0.1f;
rb.velocity = new Vector3(Random.Range(-2f, 2f), 0f, Random.Range(38f, 42f));
}
void FixedUpdate() {
var rb = GetComponent<Rigidbody>();
if (!stopping_ && rb.velocity.magnitude <= 4f) {
const float RANGE = 2f;
position_ = new Vector3((Random.Range(-RANGE, RANGE)+Random.Range(-RANGE, RANGE))/2,
(Random.Range(-RANGE, RANGE)+Random.Range(-RANGE, RANGE))/2,
0f);
stopping_ = true;
}
if (stopping_) {
rb.AddForceAtPosition(-rb.velocity * 2f, transform.TransformPoint(position_));
}
}
}
カード配りの
コード
参考
〜作ってみよう〜
いろいろな動き
ガタガタ道を進むクルマ
どう実現しよう?
void Update() {
if (Random.Range(0f, 1f) < 0.2f) {
処理
}
}
ときどき発生させる
0~1 の乱数が0.2未満
1/5 の確率
void Update() {
if (Random.Range(0f, 1f) < 0.2f) {
処理
}
}
ときどき発生させる
1秒に何回発生?
0~1 の乱数が0.2未満
1/5 の確率
void Update() {
if (Random.Range(0f, 1f) < 0.2f) {
処理
}
}
ときどき発生させる
1秒に何回発生?
0~1 の乱数が0.2未満
1/5 の確率
わかりにくい
60fpsなら12回
30fpsなら6回
1秒に6回発生(期待値)
void Update() {
if (Random.Range(0f, 1f) < 6f*Time.deltaTime) {
処理
}
}
ときどき発生させる(改)
Δt を掛けて
秒あたりの期待値に
回転バネと強めのダンパーを設置
ときどきトルクを加える
回転バネと強めのダンパーを設置
ガタガタ道を進むクルマ
完成
ふわふわ飛ぶUFO
どう実現しよう?
位置バネと弱めのダンパーを設置
位置バネと弱めのダンパーを設置
目標位置を通り過ぎる
おっとっと
位置バネと弱めのダンパーを設置
ときどき乱数で
支点を変える
通り過ぎてしまうままならなさ
ドローンを操縦しているとこんな感じ
ふわふわ飛ぶUFO
完成
デモ:てきとうに盛ってみた
おしまい
線形合同法
おまけ
an = an 1 + Q
Qが奇数なら
まんべんなく循環する
復習
Qが2 と互いに素なら
まんべんなく循環する
32
周期は2
32
もうちょっと工夫しよう
an = an 1 + Q
線形合同法
と呼ばれる乱数生成法
an = Pan 1 + Q
なにか掛けてみようかな
例:x = x*1103515245 + 12345;
ところで線形合同法は
偶数と奇数が
交互に出る
らしいぞ!?
調べてみよう
an = Pan 1 + Q
線形合同法の一般項
an = Pan 1 + Q
an 1 = Pan 2 + Q
Pan 1 = P2
an 2 + PQ
an = P2
an 2 + (P + 1)Q
an 2 = Pan 3 + Q
P2
an 2 = P3
an 3 + P2
Q
an = P3
an 3 + (P2
+ P + 1)Q
an = Pn
a0 + (Pn 1
+ Pn 2
+ · · · + P + 1)Q
an = Pn
a0 + Q
n 1X
k=0
Pk
an = Pn
a0 + Q
Pn
1
P 1
…(1)
…(2)
(2)の両辺にPを掛けて …(3)
(1)+(3)より
同様に次の項を調査 …(5)
(5)の両辺にP^2を掛けて …(6)
(4)+(6)より
…(4)
繰り返すことで次式を得る(検証略)
…(7)
参考
an = Pan 1 + Q
an = Pn
a0 + (Pn 1
+ Pn 2
+ · · · + P + 1)Q
P:奇数
Q:奇数
a0:偶数
an = Pn
a0 + (Pn 1
+ Pn 2
+ · · · + P + 1)Q
偶数 奇数 奇数 奇数 奇数
P:奇数
Q:奇数
a0:偶数
an = Pn
a0 + (Pn 1
+ Pn 2
+ · · · + P + 1)Q
偶数 奇数 奇数 奇数 奇数
nが奇数(奇数回目)→anも奇数
nが偶数(偶数回目)→anも偶数
an = Pn
a0 + (Pn 1
+ Pn 2
+ · · · + P + 1)Q
偶数 奇数 奇数 奇数 奇数
nが奇数(奇数回目)→anも奇数
nが偶数(偶数回目)→anも偶数
確かに
偶数と奇数が
交互に出ますね
P:奇数
Q:奇数
a0:偶数
ボツスライド集
おまけ
max は含むの?含まないの?
int Range(int min, int max)
含む・含まないを明記
int Range(int inclusive_min, int exclusive_max)
float Range(float min, float max)
float Range(float inclusive_min, float inclusive_max)
間違えやすい
参考
float getND() {
float v = 0f;
for (var i = 0; i < 12; ++i) {
v += Random.value;
}
return v-6f;
}
正規分布乱数の作り方 〜その2〜
12回足すだけ!!
中心をゼロに
0~1 の乱数
2回 4回
8回 12回
12回でなくても
正規分布っぽい
おしまい

【Unity道場スペシャル 2017札幌】乱数完全マスター