乱数あれこれ
(初稿:2013/10/13)
VRMスクリプト遊びにおける「乱数」の話です。
なにか「ランダム」な仕掛けを行いたい場合に使えるのが「乱数」です。例えば、ランダムに生成された正体不明の整数nを3で割ったあまりに応じて、3通りの"仕掛け"をランダムに発生させる、といった使い方をします。(nの正体が不明なので、当然その余りも正体不明なものになりますね。)
そこでVRMスクリプトでそんなことをしようぜ!となると、rndやirndといった命令があるのですが、コレが案外クセモノで、コイツラが取ってくる乱数というのは「擬似乱数」というシロモノなのです。
実は、rndやirnd命令が行っているのは、予め決まった計算に基づいて生成される乱数列から順番に数を取り出しているだけなのです。その計算が十分複雑なため、乱数列そのものは十分ランダムに見えますが、ビュワーを実行するごとに同じ乱数列をイチから拾っていくことになるので、「ランダムになるはずなのに、"仕掛け"がいつも同じ順番で発生するぞ?」といった自体が生じます。
・・・というのが、かなり前から知られていて、先行研究が存在します。いくつか残っていますが2つだけご紹介します。
- 45-50sさん such a cool 擬似乱数 (2006年12月)
- ghostさん VRM入道 乱数初期化話 (2007年9月)
いつの話やねーんというくらいに昔の話ですが、月日が流れてVRMスクリプトにも多数の命令が追加されたので、当然「できること」が増えているわけです。
ghostさん「乱数初期化話」(が言及しているtettaさん SKY&ICE Vectorって何じゃい最終回にも)言及がありますが、外の世界の実時間を乱数列の計算に割り込ませると、ビュワー起動毎に異なる乱数列が生成されます。
そして、現行のVRM5/Onlineではそれができます。真にランダム(に見える)"仕掛けパターン"が実現できるわけです。
ということで前置きはここまで(笑)
執筆時点でVRM5/Onlineに用意されている乱数関連の命令をリストアップしておきます。
irnd,irndex(頭にiが付いている)は整数=integerの乱数、rnd,rndexは少数の乱数を拾ってきます。GetUnixTimeは乱数と直接関係はありませんがあとで使います。
ついでに、関連するI.MAGIC Blogの記事です。
- VRM ONLINE アップデート (Ver.5.0.5.48)
- アップデータ (Ver.5.0.6.57)
(関係ないけど、もうちとマトモな記事タイトル付けといてくれよな…)
マニュアルにも記述がありますが、乱数列(シード値)の初期値が同じなので何回ビュワーを再起動しても同じ乱数列が生成されたのでした。
rndseedおよびrndexseed命令は、乱数列をリセットして新たな初期値を設定します。ビュワー起動直後に、GetUnixTime命令を使用して得られた国際標準時刻をもとに初期値を設定してやります。これが、「たしかにランダムな乱数」の基本原理となります。
サンプルコードは次のようになります。レイアウトスクリプトの先頭にでも書いておきましょう。
これで、rnd,irnd命令で取得する乱数列が、UnixTimeにもとづいてビュワー起動毎に異なるものになります。従来行われていた、「開始キーを設定して、それが押されるまで乱数列を無意味に進める」方法より簡単ですね。無駄なタイマーイベントも削減できます。
途中、and命令でUnixTimeを1023までに丸める(1024で割った余りに直す)処理が入っていますが、これは生のUnixTimeの値が大きすぎてシードに使えないためです。(マニュアルに書いとけよ…。)こうすると乱数列が1024通りに制限されてしまいますがこれだけあれば十分でしょう。(ちなみに適当な数では意味がなくて、2^n-1にする)
【rndex, irndexと、rndexseedはどうするのか】
ここまでexのついた連中には触れてきませんでしたが、コイツらはどう使ったらよいのでしょうか。
rndex,irndexは、0~7の8つの独立した乱数列から自由に選んで乱数を拾いに行くことが出来ます。
(今後の説明の簡単のため、rndおよびirndが使っている乱数列をR、rndex、irndexが使っている乱数列8本をR0,R1,...,R7と名前をつけることにします。)
しかし、乱数列R,R0~R7をDrawVarで順繰りに見ていくと、まったく同じ乱数列であることが分かります。
ここで一瞬疑問に感じる、rndexの「それぞれの数列は互いに独立」の意味は、拾いに行った数列は1進むが、拾いに行かなかった数列はそのまま、ということです。(8人とも全く同じ順番でグーチョキパーを出すことが分かっている8人がいて、その誰かを一人ずつ選んでじゃんけんをしに行くイメージ。)
R0~R7をそのまま使えば、「ビュワーを再起動しても同じように振る舞う個性のない8人」として使うことになります。
R0~R7に1~8のシード値(0~7でもいい)を設定してやれば、「個性があるが、それぞれはビュワーを再起動するとまた同じように振る舞う8人」(1~8が、もとの乱数列の何番目に来るかは不明であることがミソ。)
R0~R7にUnixTimeベースの「同じ」シード値を与えれば、「ビュワーを再起動すると異なる振る舞いをするが、その都度8人の個性は似ている」
R0~R7にそれぞれUnixTimeの1~8倍のシード値を設定してやれば、「個性があり、ビュワー起動毎に毎回違う振る舞いをする8人」ということになります。しかし、これをすることに全く意味が無いことが分かりますか?8人が別々の「正体不明数列」になってしまったので、1人の「正体不明数列」とじゃんけんをするのとランダムさに変わりはありませんね?
ということで乱数のお話でした。疑問があったらぜひコメント欄にどうぞ。今回は抽象的に話を進めましたが、具体的なケースに即していたほうが説明が簡単なので。
| 固定リンク
「VRM:研究」カテゴリの記事
- VRMNXの時間系イベントが使いにくいなあと思っているアナタへ(2019.12.24)
- [VRMNX] フレーム同期の話(2019.07.18)
- [Python] リスト型をベクトルに見立てて計算したい(2019.06.16)
- VRMNXのレイヤーベース地形を使ってみよう(前編)(2019.05.18)
- [VRMNX]自動踏切モジュール(2019.01.25)
この記事へのコメントは終了しました。


コメント
ビュワーを開始する度に1回だけのランダムで起こすアクションを組む場合はこれで全然行けそうなんやけど、
例えば5種のアクションをキーを押す度にとか、列車の周回毎に特定のセンサーにタッチする度にランダムで動作させる場合はタイマーイベントの方が簡単じゃない?
そう言った場合のランダムアクションをタイマーレスで起こす場合の見本を書いてくれると有難いよ(^^ゞ
どうもビュワーを開いてから特定の動作の度に、“ランダムアクションをタイマーレスで起こさせると言う内容を簡単に記述する”と言うところまで至れんかったww
乱数系はあんまり分からんもんやから、未だタイマーを使ったルーレット式でやってるしwww
投稿: RYOMA | 2013年11月 1日 (金) 20時52分