ゲーム制作技術メモ - 疑似乱数について

2025年08月18日

はじめに

ゲームでは、サイコロを振って出目を決めるように、ランダムな結果が必要になる場面がたくさんあります。

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言語に変換して利用しています。

ひとまず、疑似乱数とは何か、見るべき項目と、現在の定番をまとめてみました。

品質や、各ゲームの場面ごとの話はまた別でまとめてみたいと思います。


参考URL

Xorshift から派生した擬似乱数生成器

C++ での乱数の選択