Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

7,373 views

Published on

2017/10/8に開催されたUnity道場スペシャル 2017札幌の講演スライドです。
講師:安原 祐二(ユニティ・テクノロジーズ・ジャパン合同会社)
講演動画:https://youtu.be/VfJAgRZ338k

優れたゲームプログラム、その違いが現れるのは処理速度だけではありません。細かなテクニックを使用することで表現にも差がつきます。今回は乱数にスポットをあてて、その特徴や注意点、そして応用例についてお話しします。乱数はゲームプログラムの基本中の基本です。みなさんが作成中のゲームにも、すぐに使えるテクニックを身につけられます!

こんな人におすすめ
・ゲームプログラムの中級者を目指す方
・乱数やノイズの応用例を知りたい方

得られる知見
・乱数の数学的背景
・乱数の注意点
・乱数やノイズの応用例

Unityのイベント資料はこちらから:
https://www.slideshare.net/UnityTechnologiesJapan/clipboards

Published in: Technology
  • Be the first to comment

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

  1. 1. 乱数完全マスター ユニティ・テクノロジーズ・ジャパン合同会社 フィールド・エンジニア 安原 祐二
  2. 2. 今回のおはなし 仕組み 使いかた 前半 後半
  3. 3. 今回のおはなし 仕組み 使いかた 前半 後半
  4. 4. コンピュータ計算のきほん
  5. 5. 0か1しか入れられない箱 ビット(bit)と数える 1?0
  6. 6. 32ビットの場合 2 -1 まで数えられる (4,294,967,295) 32 32個 二進数で32桁
  7. 7. 32ビットの場合 32個 限界を越えちゃったらどうなる? 2 -1 まで数えられる (4,294,967,295) 32 二進数で32桁
  8. 8. 繰り上がったぶんは無視 2 で割った余りになっている 32 無視! 計算結果 32個
  9. 9. 9 9 90 3桁しかないデジタル表示は 1000(10 )で割った余りになっている 3 999の次は000
  10. 10. 例えば限界のところに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 無視! 計算結果
  11. 11. 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 循環する ゼロにもどる 計算結果
  12. 12. 乱数を作ってみよう
  13. 13. 乱数使用例 計算でバラバラの数を作ること! 簡単な乱数を作ってみよう var a = Random.value; そのたびに異なる値になる 擬似乱数とは
  14. 14. 0〜7の範囲で乱数を作りたい 前回の値に1を加える 新しい値 前回の値 a←a+1
  15. 15. 範囲を越えた 番目 値 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 0〜7の範囲で乱数を作りたい a←a+1 前回の値に1を加える
  16. 16. 前回の値に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の範囲で乱数を作りたい 循環している
  17. 17. 前回の値に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
  18. 18. 前回の値に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
  19. 19. 前回の値に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
  20. 20. class Random { int s = 0; int get() { s = (s+3)%8; return s; } } プログラム例
  21. 21. まんべんなく現れるためには 「前回の値に3を加えて8で割った余り」 ふたつの数が互いに素であればよい
  22. 22. 互いに素とは ふたつの数の共通の約数が存在しない ーという分数が約分できない 3 8 マメ知識
  23. 23. 同じ値が続けて出ることもないと… 大きな値で循環させて 必要な範囲を取り出す ちょっとまって! 乱数ってそういうもの? まんべんなく出ればいいって…
  24. 24. 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
  25. 25. 0~2 -1の範囲で乱数を作りたい 32 前回の値にQを加えて2 で割った余り 32
  26. 26. 0~2 -1の範囲で乱数を作りたい 32 前回の値にQを加えて2 で割った余り 32 a←(a+Q)%2 32
  27. 27. 0~2 -1の範囲で乱数を作りたい 32 前回の値にQを加えて2 で割った余り 32 Qは2 と互いに素のものを選ぶ 32 Qは奇数であればいい a←(a+Q)%2 32
  28. 28. 32ビットなら余りを計算する必要なし! 前回の値にQを加えて2 で割った余り 32 0~2 -1の範囲で乱数を作りたい 32 Qは2 と互いに素のものを選ぶ 32 Qは奇数であればいい a←(a+Q)%2 32 a←a+Q
  29. 29. 32ビットなら余りを計算する必要なし! 前回の値にQを加えて2 で割った余り 32 0~2 -1の範囲で乱数を作りたい 32 Qは2 と互いに素のものを選ぶ 32 Qは奇数であればいい a←(a+Q)%2 32 a←a+Q 32ビットで循環できた ただ奇数を足すだけで乱数ができた
  30. 30. 乱数のきほん
  31. 31. Unity の Random クラス 1. Random.InitStateで初期化 2. Random.valueなど 呼ぶ度に異なる値を返す
  32. 32. シード 151 乱数のしくみ シードで初期値を与える
  33. 33. シード 151 260 260 乱数のしくみ シードで初期値を与える 乱数を取得できる
  34. 34. シード 151 260 260 37 37 乱数のしくみ シードで初期値を与える あとは取得し続ける
  35. 35. シード 151 260 260 37 37 180 周期 乱数のしくみ シードで初期値を与える あとは取得し続ける ある周期で循環する
  36. 36. 乱数の特徴 再現する (シードが同じなら同じパターン) 周期がある (同じパターンを繰り返す)
  37. 37. 乱数を使うときは 常に意識する 再現させるかどうか ごとに ユーザ 起動 乱数を発生させたい 同じ 別の プレイ
  38. 38. 再現させたい場合 シードを固定する シードを変化させる 再現させたくない場合 現在時刻 ボタンを押すまでの時間 システムクロック
  39. 39. シードを設定せずに 乱数を使わない! ぐらいの気持ちで シードはだいじ
  40. 40. いろいろな生成法
  41. 41. LCG(線形合同法) 19??年 さすがに使われなくなってきた 高速 品質(バラバラ度)に問題あり 例 a←a×1103515245+12345
  42. 42. メルセンヌ・ツイスタ 1997年 周期が超絶の2 -1 19937 十分に高速(数百回に一度ちょっと重くなる) 品質は最上級 メモリ使用大きめ(4.8KiB)
  43. 43. Xorshift 2003年 シンプル&高速 質も高い 周期は2 -1版、2 -1版、2 -1版32 64 128 例 a = a ^ (a<<13);   a = a ^ (a>>17);   a = a ^ (a<<15);
  44. 44. PCG 2014年 高速 極めて質が高いらしい 決定版かもしれない? 今後採用事例が増えるかも
  45. 45. Unityで生成器を自作
  46. 46. 実装の理由 生成法を掌握 系統を分ける
  47. 47. エフェクト サイコロ 敵キャラ 再現不能 あちこちで使用すると…
  48. 48. 系統をわける var randA = new MyRandom(); var randB = new MyRandom(); ふたつ作る UnityEngine.Random はこれができない
  49. 49. ゲーム進行用 エフェクト用 影響を受けない 再現可能に
  50. 50. そうは言っても 自作はなあ… UnityEngine.Randomには便利関数がそろっている 作るのは面倒だしバグも怖い
  51. 51. 組み込み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 を 保存・復活する
  52. 52. 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; } } 参考
  53. 53. 間違った用法
  54. 54. 良さげな結果 乱数っぽい
  55. 55. どうしてこうなった 失敗例良さげな結果 乱数っぽい
  56. 56. 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); } } 良さげな結果 すべての座標で白か黒を 乱数で決定
  57. 57. 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); } } 失敗例 シードを毎回設定している シードを設定
  58. 58. シードを単純にセットしまくるのは 想定していない生成法が多い とはいえ。
  59. 59. シードの設定が必要なゲームの例 100面あるダンジョンの… 迷路や敵の強さを… その都度生成しつつ… パターンを固定したい。
  60. 60. シード設定が一度だけの場合 ステージ1 ステージ2 ステージ3 ステージ4 シード ステージジャンプで問題
  61. 61. ステージ1 ステージ2 ステージ3 ステージ4 シードA シードB シードC シードD 各ステージでのシード設定は必要
  62. 62. 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); } } 失敗例のコード(再掲) 自然にこうなる 最初にボスの性別を決めるとする
  63. 63. 何がいけないのか 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); } } ←ここ。単純な増加がダメ
  64. 64. シードも乱数にしてみよう 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); } } ←乱数 さあ、結果は?
  65. 65. 良さげ!
  66. 66. 良さげ! しかし、これはダメ なぜかというと…
  67. 67. シードを乱数にしてしまうと 周期が 2 より大きい場合、 衝突は起きる 衝突が起きる (同じ値が出る) 32 可能性は低いが、 予測するのは困難
  68. 68. 衝突しないバラバラの値が欲しい そんなときは ハッシュ関数 xxHash がおすすめ (Yann Colletさん作)
  69. 69. 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; } 参考
  70. 70. xxHash は衝突しないのか? https://qiita.com/yuji_yasuhara/items/adefc967c51a6becca08 調べてみました 大丈夫。
  71. 71. 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を呼ぶ さあ、結果は?
  72. 72. 良さげ! ひと安心
  73. 73. 擬似乱数の恐ろしさ 問題に気付きにくい
  74. 74. 擬似乱数の恐ろしさ 「偶数と奇数が交互に出る」 開発中に気づくのは困難 問題に気付きにくい ましてやステージボスの性別にパターンが生じるなど… 恐さを知っておこう!
  75. 75. Perlinノイズ
  76. 76. Perlin ノイズ
  77. 77. ホワイトノイズ Perlinノイズ とても便利!ただの乱数
  78. 78. void Update() { var x = Mathf.PerlinNoise(Time.time, 0f); var pos = new Vector3(x, 0f, 0f); transform.position = pos; } Mathf.PerlinNoiseの使用例 第1引数に現在時刻(秒) 第2引数にゼロ
  79. 79. Mathf.PerlinNoise(Time.time, 0f); の意味 ゼロ Time.time 上の縁をなぞる
  80. 80. この画像は16x16 16.0 16.0 16x16
  81. 81. 1.0 1.0 2.0 2.0 1.0x1.0 2x2 4x4 8x8 16x16 32x32
  82. 82. 別系統のノイズ(シードのかわり) ゼロ Time.time Mathf.PerlinNoise(Time.time, 0f); 8.0 Mathf.PerlinNoise(Time.time, 8f); 2以上離せばOK
  83. 83. 大きなノイズ
  84. 84. 細かいノイズ
  85. 85. 足す
  86. 86. デモ:PerlinNoiseアニメーション
  87. 87. 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ノイズアニメーション で使用したコード 参考
  88. 88. PerlinNoiseの周期は? どう作ってあるか次第 Unityの実装では256.0で周回
  89. 89. void Update() { m_Time += Time.deltaTime; m_Time = Mathf.Repeat(m_Time, 256f); var x = Mathf.PerlinNoise(m_Time, 0f); … 256.0で繰り返しておく これで永遠に動く
  90. 90. 〜作ってみよう〜 噴出花火
  91. 91. 一様乱数 正規乱数 正規乱数
  92. 92. Particle System +正規乱数 Particle System
  93. 93. デモ:Particle System 実験
  94. 94. 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; } 正規乱数のつくりかた ボックス・ミューラー法 処理速度も問題ない
  95. 95. 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(); } } } 参考 正規乱数パーティクルの コード
  96. 96. 〜作ってみよう〜 カード配り
  97. 97. デモ:カード配り
  98. 98. 摩擦つきの移動(外力なし)
  99. 99. 速度がある値より低くなったら 摩擦つきの移動(外力なし)
  100. 100. 速度がある値より低くなったら 乱数でカード上の一点を選択 摩擦つきの移動(外力なし)
  101. 101. 速度がある値より低くなったら 乱数でカード上の一点を選択 回りながら進み、止まる その点への摩擦力を追加 摩擦つきの移動(外力なし)
  102. 102. 物理を使っておくと 条件の変更に強い 実際に起きていることを考察する Rigidbody.AddForce Rigidbody.AddTorque Rigidbody.AddForceAtPosition
  103. 103. 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_)); } } } カード配りの コード 参考
  104. 104. 〜作ってみよう〜 いろいろな動き
  105. 105. ガタガタ道を進むクルマ どう実現しよう?
  106. 106. void Update() { if (Random.Range(0f, 1f) < 0.2f) { 処理 } } ときどき発生させる 0~1 の乱数が0.2未満 1/5 の確率
  107. 107. void Update() { if (Random.Range(0f, 1f) < 0.2f) { 処理 } } ときどき発生させる 1秒に何回発生? 0~1 の乱数が0.2未満 1/5 の確率
  108. 108. void Update() { if (Random.Range(0f, 1f) < 0.2f) { 処理 } } ときどき発生させる 1秒に何回発生? 0~1 の乱数が0.2未満 1/5 の確率 わかりにくい 60fpsなら12回 30fpsなら6回
  109. 109. 1秒に6回発生(期待値) void Update() { if (Random.Range(0f, 1f) < 6f*Time.deltaTime) { 処理 } } ときどき発生させる(改) Δt を掛けて 秒あたりの期待値に
  110. 110. 回転バネと強めのダンパーを設置
  111. 111. ときどきトルクを加える 回転バネと強めのダンパーを設置
  112. 112. ガタガタ道を進むクルマ 完成
  113. 113. ふわふわ飛ぶUFO どう実現しよう?
  114. 114. 位置バネと弱めのダンパーを設置
  115. 115. 位置バネと弱めのダンパーを設置 目標位置を通り過ぎる おっとっと
  116. 116. 位置バネと弱めのダンパーを設置 ときどき乱数で 支点を変える 通り過ぎてしまうままならなさ ドローンを操縦しているとこんな感じ
  117. 117. ふわふわ飛ぶUFO 完成
  118. 118. デモ:てきとうに盛ってみた
  119. 119. おしまい
  120. 120. 線形合同法 おまけ
  121. 121. an = an 1 + Q Qが奇数なら まんべんなく循環する 復習 Qが2 と互いに素なら まんべんなく循環する 32 周期は2 32
  122. 122. もうちょっと工夫しよう an = an 1 + Q 線形合同法 と呼ばれる乱数生成法 an = Pan 1 + Q なにか掛けてみようかな 例:x = x*1103515245 + 12345;
  123. 123. ところで線形合同法は 偶数と奇数が 交互に出る らしいぞ!? 調べてみよう an = Pan 1 + Q
  124. 124. 線形合同法の一般項 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) 参考
  125. 125. an = Pan 1 + Q an = Pn a0 + (Pn 1 + Pn 2 + · · · + P + 1)Q
  126. 126. P:奇数 Q:奇数 a0:偶数 an = Pn a0 + (Pn 1 + Pn 2 + · · · + P + 1)Q 偶数 奇数 奇数 奇数 奇数
  127. 127. P:奇数 Q:奇数 a0:偶数 an = Pn a0 + (Pn 1 + Pn 2 + · · · + P + 1)Q 偶数 奇数 奇数 奇数 奇数 nが奇数(奇数回目)→anも奇数 nが偶数(偶数回目)→anも偶数
  128. 128. an = Pn a0 + (Pn 1 + Pn 2 + · · · + P + 1)Q 偶数 奇数 奇数 奇数 奇数 nが奇数(奇数回目)→anも奇数 nが偶数(偶数回目)→anも偶数 確かに 偶数と奇数が 交互に出ますね P:奇数 Q:奇数 a0:偶数
  129. 129. ボツスライド集 おまけ
  130. 130. 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) 間違えやすい 参考
  131. 131. float getND() { float v = 0f; for (var i = 0; i < 12; ++i) { v += Random.value; } return v-6f; } 正規分布乱数の作り方 〜その2〜 12回足すだけ!! 中心をゼロに 0~1 の乱数
  132. 132. 2回 4回 8回 12回 12回でなくても 正規分布っぽい
  133. 133. おしまい

×