この記事では、Godot Engine における乱数の使い方を説明します。
目次
ビルドイン関数での乱数制御
GDScriptのビルドイン関数での乱数は以下のものが用意されています
float rand_range(float from, float to)
: from〜toの乱数を浮動小数値で返すvoid seed(int seed)
: 乱数のシード値を設定float randf()
: 浮動小数値 0.0〜1.0の乱数を返すint randi()
: 整数値 0〜4294967295(2^32-1)の乱数を返すvoid randomize()
: 現在の時間の値を使用して乱数のシード値を設定する
from〜toの乱数を浮動小数値で返します。
ソースコードを読んだ印象では、toの値はたぶん含みますが、検証用コードでは引けず……
extends Node2D
func _ready() -> void:
for i in range(10000000):
var rnd = rand_range(0, 100)
if rnd > 99.99999:
print(rnd)
99.999996
99.999993
floatなので、引くのはかなり難しいかなと……
ちなみに整数値版の RandomNumberGenerator::randi_range()
の場合は終端の値を簡単に引けます。
(ビルドイン関数には整数値版はなさそう……? →randi()%n
で代用)
乱数のシード値を設定します。
カードゲームのリプレイなどで乱数を再現したい場合など。
rand_range()
がfloatなので必要ないけれど、0.0〜1.0 が欲しい場合に使うかもしれない……
rand_range()
の整数値バージョンがないので、整数値の乱数が欲しい場合に使います。
乱数の初期化として使います。ゲーム起動時にこの関数を呼び出すようにすれば、おおよそ毎回違う乱数になります。
再現性のある乱数にしたい場合には、あえて呼び出さないようにしてもいいかもしれません。
RandomNumberGenerator
乱数を複数用意する場合には RandomNumberGenerator
を使用します。
例えば、カードゲームなどのリプレイ保存などで、カードには RandomNumberGenerator
で乱数を固定化して、演出などではビルドイン関数の乱数を使用する、という方法です。
また、ビルドインにはない以下の関数が使用できます。
int randi_range(int from, int to)
:fromからtoの値を整数値で返すfloat randfn(float median, float deviation)
:ガウス分布の乱数を浮動小数値で返す
fromからtoの値を整数値で返します (from/toの値を含む)
使用例は以下のとおりです
extends Node2D
func _ready() -> void:
# 乱数生成
var rng = RandomNumberGenerator.new()
# 乱数初期化
rng.randomize()
# 1〜3の乱数を引く
for i in range(10):
print(rng.randi_range(1, 3))
この処理は 1〜3 の値をランダムで出力します。randi()%3 + 1
で代用できるのですが、この関数の方がわかりやすさは高いです。
medianが平均値で、deviationが偏差となります。
数学は得意ではないので正確な定義は間違っているかもしれませんが、偏差とは偏りのある振れ幅のことで、例えば randfn(10, 1)
と指定すると 平均がおおよそ “10” になるようにして、おおよそ “10±1 (= 9.0〜11.0)” の値を返します。(おおよそなので実際にはその範囲を飛び越えます)
そして、中央値 “10” に結果が偏ります。
extends Node2D
func _ready() -> void:
# 乱数生成
var rng = RandomNumberGenerator.new()
# 乱数初期化
rng.randomize()
# ガウス分布の乱数を生成
var sum = 0
for i in range(100):
var n = rng.randfn(10, 1)
sum += n
print("平均値: %f"%(sum / 100))
平均値: 9.923579
Array.shuffle()
カードゲームの山札を作る場合には、Array.shuffle()
が便利です。
extends Node2D
func _ready() -> void:
# 乱数初期化
randomize()
# 山札作成
var deck = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 山札をシャッフル
deck.shuffle()
print(deck)
ただ、ドキュメントによると、ビルドイン関数の randi()
が内部で使われてしまうようなので、乱数を複数使いたい場合には、独自にシャッフル関数を作るなど、工夫が必要となるかもしれません。
重み付けの抽選を行う
最後に重み付けの抽選を行うサンプルコードの紹介です。
このクジには、
- 大当たり(SSR): 1本
- 当たり(SR): 10本
- はずれ(R): 100本
が入っているとして、10連ガチャで引きます。
引いた後は、はずれでもクジの箱に入れ直すという鬼畜仕様ですが、それゆえ10連すべてが SSR になる可能性もあります……!
extends Node2D
# 結果格納
var result = []
# 重み付けで抽選
# @return 抽選結果のインデックス番号
func weighted_pick(arr) -> int:
var total_weight = 0
var pick = 0
# トータルの重みを計算する
for v in arr:
total_weight += v
# 抽選する
var rnd = randi()%total_weight
for i in arr.size():
if rnd < arr[i]:
# 抽選対象決定
pick = i
break
# 次の対象を調べる
rnd -= arr[i]
return pick
func _ready() -> void:
# 乱数初期化
randomize()
# 重み付け配列のクジを作成
# 0: 1本
# 1: 10本
# 2: 100本
var gacha = [1, 10, 100]
# 10連ガチャ
for i in range(10):
var ret = weighted_pick(gacha)
result.append(ret)
func _get_rarity(idx):
match idx:
0:
return "SSR"
1:
return "SR"
_:
return "R"
func _draw():
# デフォルトフォントを取得
var font = Control.new().get_font("font")
# 結果を描画
var i = 0
for idx in result:
draw_string(font, Vector2(12, 16 * (i + 1)), "%d: %s"%[i, _get_rarity(idx)])
i += 1
結果は以下のように……