はじめに
ゲームでは、サイコロを振って出目を決めるように、ランダムな結果が必要になる場面がたくさんあります。
RPGなら、攻撃が当たるかどうかの判定や、ダメージが一定の幅で増減する仕組みなどが代表的です。
コンピューターに「サイコロを振らせる」ために使うのが 疑似乱数 です。 今回は、この疑似乱数について簡単に説明します。
疑似乱数とは?
疑似乱数は大きく分けて次の2種類があります。
- 計算を利用したもの(プログラムで生成するタイプ)
- ノイズのように不安定な値(実際の物理現象などを利用するタイプ)
ゲーム開発でよく使うのは「計算で作られた疑似乱数」です。
計算で作られた疑似乱数
たとえば、次のようなシンプルな乱数生成があります。
rand_value = 0
def rand():
global rand_value
rand_value = (rand_value + 173) % 501
return rand_value
print([rand() for _ in range(5)]) # 173, 346, 19, ...
こうした簡素なものは、乱数としては課題があります。それを克服したものとして、メルセンヌツイスタやXorShiftが存在します。
コードのとおり、最初は 0+173 より 173 が生成されます。
実際使うときはこれを、必要な範囲に絞ります。
rand() % 10
このようにすると、10で割った時の余りが取得されます。そのため、0~9のいずれかになります。正確には「余り」を使って範囲を限定するのは良くありませんが、難しいことはここではおいておきたいと思います。
Pythonでは、メルセンヌツイスタが標準で利用できます。
import random
# 再現性が欲しいなら seed を固定
random.seed(12345)
# [0.0, 1.0) の浮動小数
print(random.random())
# 整数の範囲
print(random.randint(0, 9)) # 0〜9 を含む
print(random.randrange(10)) # 0〜9(上限は含まない)
Pythonで疑似乱数の生成にメルセンヌツイスタが利用されているのは、下記に書かれています。 https://docs.python.org/3/library/random.html?utm_source=chatgpt.com
乱数生成については、最初はそこまで深く考える必要はないかもしれません。
ただ、ゲームによっては、乱数の偏りによってクリアが困難、到達不可能なルートが存在してしまうことがあります。
そのため、プログラムのモジュールやゲームエンジンが、どんな疑似乱数を使っているのか、心に留めておくとよいでしょう。
ノイズのように不安定な値
不安定な値をうまくサイコロの出目のように使うことも可能です。
その定番は、現在時刻、マウスの動き、マイク入力などから常に入る微弱なノイズ、です。
計算で作られた疑似乱数の初期化には、現在時刻を使うのは定番となっています。
時刻は、ミリ秒単位で細かく取得できます。
たまたま取得した値が、2025年の4月25日13時15分788ミリ秒だったとすると、それをいったんミリ秒に全て換算しなおして、100の余りを求めれば88が取得できます。
次の瞬間にもう一度取得すると、その値は大きく変化します。
このような値は、計算も不要で手軽にそれっぽいランダムな値を獲得できます。
半面、その品質は保証できません。計算で作られた疑似乱数は、良くも悪くも、どういう品質か、算出することが可能であり、それゆえゲームでは好まれます。
また、乱数にも復元したい場合があります。
例えば、マインクラフトなどのマップを生成する場合です。
計算を利用した乱数は、SEED値を共有することで、同じマップ・乱数の生成を再現できます。
疑似乱数の品質
疑似乱数の品質について、ざっくり紹介すると3種類あり、細かくみると下記の7種です。
「ランダムらしさ」: 周期 / 一様性 / 独立性 / 相関
「ゲームや開発の都合」: 再現性 / 計算コスト
「特殊用途」: 暗号学的安全性
それぞれ説明すると。
周期の長さ : 乱数列は有限の長さで繰り返します。周期が短いとパターンが見えてしまい、ゲーム的にも「偏って見える」原因になる。
一様性(分布の均一さ) : 出現する値が特定の範囲に偏らず、長期的に見て全範囲に均等に出るか。
確率の独立性 : 直前の値から次の値が予測されにくいこと。乱数らしさに直結。
相関の少なさ : 多次元的に見たとき(例:2D・3Dで並べたとき)に規則的なパターンが出ないこと。グラフにプロットして格子状に並ぶと“粗悪な乱数”とされる。
再現性 : 同じシードからは必ず同じ列を再現できること。ゲーム開発では「デバッグ」「リプレイ再現」などで大事。
計算コスト : アルゴリズムの速度や軽さ。ゲームループで多用するなら処理負荷も品質の一部。
暗号学的安全性(CSPRNGかどうか) : 攻撃者に予測されにくいか。通常のゲーム乱数では不要だが、セキュリティ用途では重要。
結局どの疑似乱数を使えばいいの?
2025年の現時点では下記の3つをおさえておけばいいかもです。
PCG( Permuted Congruential Generator )
新しい(2014年ごろ)
周期 : 採用方式で変わる、2^64(PCG-XSH-RR 32)、2^128(PCG64、PCG64DXSM)
Pythonのnumpyという代表的なモジュールで利用されている
短いコードで実装できる
GPUと相性がいい
ライセンス : Apache License 2.0 (商用利用・改変・再配布すべて可能、特許権の明示もある安心なライセンス)
メルセンヌツイスタ
やや古い(1997年に発表、最新の改良アルゴリズムは2008年のGPU向けがある)
周期 : 2^19937 − 1、周期は圧倒的に長い
メモリ効率は悪い : 2KB
ライセンス : 公式ページに「自由に使用可能」と明記。長年にわたる実績があり、Pythonのモジュールにも組み込まれている。
XorShift系のxoroshiro128++
新しい(2016年ごろ)
最速クラスの高速アルゴリズム
周期 : 2^128 − 1
ライセンス : CC0 (パブリックドメイン相当)
公式ページにC++のコードがある
※XorShift自体は2003年に提案された
まとめ
2025年時点での定番は、PCGとなりつつあります。 それに対して、周期の長いメルセンヌツイスタと、速度のxoroshiro128++ と言った塩梅です。
最後に
私は、xoroshiro128++のコードをGo言語に変換して利用しています。
ひとまず、疑似乱数とは何か、見るべき項目と、現在の定番をまとめてみました。
品質や、各ゲームの場面ごとの話はまた別でまとめてみたいと思います。