随机数生成
许多游戏依靠随机性来实现核心游戏机制. 本页将指导你了解常见的随机性类型, 以及如何在Godot中实现它们.
在简要概述生成随机数的实用函数之后, 你将学习如何从数组或字典中获取随机元素, 以及如何在GDScript中使用噪声生成器.
注解
计算机不能产生“真正的”随机数。相反,它们依赖伪随机数生成器(PRNG)。
全局作用域 vs 随机数生成器 (RandomNumberGenerator)类
Godot提供了两种生成随机数的方式: 通过 全局作用域 方法或使用 RandomNumberGenerator 类.
全局作用域方法更容易设置, 但不能提供太多控制.
RandomNumberGenerator则需要写更多代码, 但提供许多在全局作用域内找不到的方法, 如 randi_range() 和 randfn() . 除此之外, 它还允许创建多个实例, 每个实例都有自己的种子.
本教程使用全局作用域方法, 只存在于RandomNumberGenerator类中的方法除外.
randomize() 方法
在全局作用域内, 你可以找到一个 randomize() 方法. 这个方法只需要在你的项目开始初始化随机种子的时候调用一次 , 多次调用是多余的, 并且有可能影响性能.
把它放在你的主场景脚本的 _ready()
方法中是个不错的选择:
GDScript
C#
func _ready():
randomize()
public override void _Ready()
{
GD.Randomize();
}
您也可以使用 seed() 设置固定的随机种子。这样能在运行中获得确定性的结果:
GDScript
C#
func _ready():
seed(12345)
# To use a string as a seed, you can hash it to a number.
seed("Hello world".hash())
public override void _Ready()
{
GD.Seed(12345);
GD.Seed("Hello world".Hash());
}
当使用RandomNumberGenerator类时,应该在实例上调用 randomize()
,因为它有自己的种子:
GDScript
C#
var random = RandomNumberGenerator.new()
random.randomize()
var random = new RandomNumberGenerator();
random.Randomize();
获得一个随机数
让我们来看看Godot中最常用的一些生成随机数的函数和方法.
函数 randi() 返回 0 到 2^32-1 之间的随机数。由于最大值很大,您很可能希望使用模运算符 (%
) 将结果限制在 0 和分母之间:
GDScript
C#
# Prints a random integer between 0 and 49.
print(randi() % 50)
# Prints a random integer between 10 and 60.
print(randi() % 51 + 10)
// Prints a random integer between 0 and 49.
GD.Print(GD.Randi() % 50);
// Prints a random integer between 10 and 60.
GD.Print(GD.Randi() % 51 + 10);
randf() 返回一个0到1之间的随机浮点数. 在实现 加权随机概率 系统等时非常有用.
randfn() 返回遵循 正态分布 的随机浮点数。这意味着返回值更有可能在平均值附近(默认为 0.0),随偏差变化(默认为 1.0):
GDScript
C#
# Prints a random floating-point number from a normal distribution with a mean 0.0 and deviation 1.0.
var random = RandomNumberGenerator.new()
random.randomize()
print(random.randfn())
// Prints a normally distributed floating-point number between 0.0 and 1.0.
var random = new RandomNumberGenerator();
random.Randomize();
GD.Print(random.Randfn());
rand_range() 接受两个参数 from
和 to
,并返回一个介于 from
和 to
之间的随机浮点数:
GDScript
C#
# Prints a random floating-point number between -4 and 6.5.
print(rand_range(-4, 6.5))
// Prints a random floating-point number between -4 and 6.5.
GD.Print(GD.RandRange(-4, 6.5));
RandomNumberGenerator.randi_range() 接受两个参数 from
和 to
,并返回一个介于 from
和 to
之间的随机整数:
GDScript
C#
# Prints a random integer between -10 and 10.
var random = RandomNumberGenerator.new()
random.randomize()
print(random.randi_range(-10, 10))
// Prints a random integer number between -10 and 10.
random.Randomize();
GD.Print(random.RandiRange(-10, 10));
获取一个随机数组元素
我们可以使用随机整数生成来从数组中获得一个随机元素:
GDScript
C#
var _fruits = ["apple", "orange", "pear", "banana"]
func _ready():
randomize()
for i in range(100):
# Pick 100 fruits randomly.
print(get_fruit())
func get_fruit():
var random_fruit = _fruits[randi() % _fruits.size()]
# Returns "apple", "orange", "pear", or "banana" every time the code runs.
# We may get the same fruit multiple times in a row.
return random_fruit
private string[] _fruits = { "apple", "orange", "pear", "banana" };
public override void _Ready()
{
GD.Randomize();
for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly.
GD.Print(GetFruit());
}
}
public string GetFruit()
{
string randomFruit = _fruits[GD.Randi() % _fruits.Length];
// Returns "apple", "orange", "pear", or "banana" every time the code runs.
// We may get the same fruit multiple times in a row.
return randomFruit;
}
为了防止连续多次采摘相同的水果,我们可以给这个方法添加更多的逻辑:
GDScript
C#
var _fruits = ["apple", "orange", "pear", "banana"]
var _last_fruit = ""
func _ready():
randomize()
# Pick 100 fruits randomly.
for i in range(100):
print(get_fruit())
func get_fruit():
var random_fruit = _fruits[randi() % _fruits.size()]
while random_fruit == _last_fruit:
# The last fruit was picked, try again until we get a different fruit.
random_fruit = _fruits[randi() % _fruits.size()]
# Note: if the random element to pick is passed by reference,
# such as an array or dictionary,
# use `_last_fruit = random_fruit.duplicate()` instead.
_last_fruit = random_fruit
# Returns "apple", "orange", "pear", or "banana" every time the code runs.
# The function will never return the same fruit more than once in a row.
return random_fruit
private string[] _fruits = { "apple", "orange", "pear", "banana" };
private string _lastFruit = "";
public override void _Ready()
{
GD.Randomize();
for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly.
GD.Print(GetFruit());
}
}
public string GetFruit()
{
string randomFruit = _fruits[GD.Randi() % _fruits.Length];
while (randomFruit == _lastFruit)
{
// The last fruit was picked, try again until we get a different fruit.
randomFruit = _fruits[GD.Randi() % _fruits.Length];
}
_lastFruit = randomFruit;
// Returns "apple", "orange", "pear", or "banana" every time the code runs.
// The function will never return the same fruit more than once in a row.
return randomFruit;
}
这种方法可以让随机数生成的感觉不那么重复. 不过, 它仍然不能防止结果在有限的一组值之间 “乒乓反复”. 为了防止这种情况, 请使用 shuffle bag 模式来代替.
获取一个随机字典值
我们也可以将数组的类似逻辑应用于字典:
GDScript
var metals = {
"copper": {"quantity": 50, "price": 50},
"silver": {"quantity": 20, "price": 150},
"gold": {"quantity": 3, "price": 500},
}
func _ready():
randomize()
for i in range(20):
print(get_metal())
func get_metal():
var random_metal = metals.values()[randi() % metals.size()]
# Returns a random metal value dictionary every time the code runs.
# The same metal may be selected multiple times in succession.
return random_metal
加权随机概率
randf() 方法返回一个介于 0.0 和 1.0 之间的浮点数。我们可以使用它来创建“加权”概率,其中不同的结果具有不同的可能性:
GDScript
C#
func _ready():
randomize()
for i in range(100):
print(get_item_rarity())
func get_item_rarity():
var random_float = randf()
if random_float < 0.8:
# 80% chance of being returned.
return "Common"
elif random_float < 0.95:
# 15% chance of being returned.
return "Uncommon"
else:
# 5% chance of being returned.
return "Rare"
public override void _Ready()
{
GD.Randomize();
for (int i = 0; i < 100; i++)
{
GD.Print(GetItemRarity());
}
}
public string GetItemRarity()
{
float randomFloat = GD.Randf();
if (randomFloat < 0.8f)
{
// 80% chance of being returned.
return "Common";
}
else if (randomFloat < 0.95f)
{
// 15% chance of being returned
return "Uncommon";
}
else
{
// 5% chance of being returned.
return "Rare";
}
}
使用shuffle bag的 “更好” 随机性
以上面同样的例子为例, 我们希望随机挑选水果. 然而, 每次选择水果时依靠随机数生成会导致分布不那么 均匀 . 如果玩家足够幸运(或不幸), 他们可能会连续三次或更多次得到相同的水果.
你可以使用 shuffle bag 模式来实现。它的工作原理是在选择数组后从数组中删除一个元素。多次选择之后,数组会被清空。当这种情况发生时,就将数组重新初始化为默认值:
var _fruits = ["apple", "orange", "pear", "banana"]
# A copy of the fruits array so we can restore the original value into `fruits`.
var _fruits_full = []
func _ready():
randomize()
_fruits_full = _fruits.duplicate()
_fruits.shuffle()
for i in 100:
print(get_fruit())
func get_fruit():
if _fruits.empty():
# Fill the fruits array again and shuffle it.
_fruits = _fruits_full.duplicate()
_fruits.shuffle()
# Get a random fruit, since we shuffled the array,
# and remove it from the `_fruits` array.
var random_fruit = _fruits.pop_front()
# Prints "apple", "orange", "pear", or "banana" every time the code runs.
return random_fruit
在运行上面的代码时, 仍有可能连续两次得到同一个水果. 我们摘下一个水果时, 它将不再是一个可能的返回值, 但除非数组现在是空的. 当数组为空时, 此时我们将其重置回默认值, 这样就导致了能再次获得相同的水果, 但只有这一次.
随机噪音
当你需要一个 缓慢 根据输入而变化的值时, 上面显示的随机数生成方式就显示出了它们的局限性. 这里的输入可以是位置, 时间或其他任何东西.
为此,您可以使用随机噪声函数。噪声函数在程序式生成中特别受欢迎,可以生成逼真的地形。 Godot 为此提供了 OpenSimplexNoise,它支持 1D、2D、3D 和 4D 噪声。这是一个 1D 噪声的例子:
GDScript
C#
var _noise = OpenSimplexNoise.new()
func _ready():
randomize()
# Configure the OpenSimplexNoise instance.
_noise.seed = randi()
_noise.octaves = 4
_noise.period = 20.0
_noise.persistence = 0.8
for i in 100:
# Prints a slowly-changing series of floating-point numbers
# between -1.0 and 1.0.
print(_noise.get_noise_1d(i))
private OpenSimplexNoise _noise = new OpenSimplexNoise();
public override void _Ready()
{
GD.Randomize();
// Configure the OpenSimplexNoise instance.
_noise.Seed = (int)GD.Randi();
_noise.Octaves = 4;
_noise.Period = 20.0f;
_noise.Persistence = 0.8f;
for (int i = 0; i < 100; i++)
{
GD.Print(_noise.GetNoise1d(i));
}
}