Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Geração de números aleatórios
Muitos jogos dependem da aleatoriedade para implementar mecânicas principais de jogo. Esta página orienta você através dos tipos comuns de aleatoriedade e como implementá-los no Godot.
Depois de fornecer uma breve visão geral de funções úteis que geram números aleatórios, você aprenderá como obter elementos aleatórios de arrays, dicionários e como usar um gerador de ruído no GDScript. Por fim, daremos uma olhada na geração de números aleatórios criptograficamente segura e como ela difere da geração típica de números aleatórios.
Nota
Os computadores não conseguem gerar números aleatórios "verdadeiros". Em vez disso, eles dependem de geradores de números pseudoaleatórios (PRNGs).
O Godot usa internamente a Família PCG de geradores de números pseudoaleatórios.
Escopo global versus classe RandomNumberGenerator
O Godot expõe duas maneiras de gerar números aleatórios: por meio de métodos de escopo global ou usando la classe RandomNumberGenerator.
Os métodos de escopo global são mais fáceis de configurar, mas não oferecem tanto controle.
RandomNumberGenerator requires more code to use, but allows creating multiple instances, each with their own seed and state. This is useful in certain scenarios like networked multiplayer, replay systems, games that feature rewind mechanics, and more.
Este tutorial usa métodos de escopo global, exceto quando o método existe apenas na classe RandomNumberGenerator.
Random seed and internal state
By default, Godot uses a random seed set according to the device's local time. This means that results will be different on every run. To get deterministic results, you can set a fixed seed using the seed() method. The seed is an integer that initializes the random number generator's state. If you use the same seed, you'll get the same sequence of random numbers every run.
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);
// To use a string as a seed, you can hash it to a number.
GD.Seed("Hello world".Hash());
}
When using the RandomNumberGenerator class, you can set the RandomNumberGenerator.seed property on individual instances:
var random = RandomNumberGenerator.new()
random.seed = 12345
var random = new RandomNumberGenerator();
random.Seed = 12345;
You can set the seed back to a randomly generated value using the randomize() global scope method. This is also available as the RandomNumberGenerator.randomize() method on RandomNumberGenerator instances.
Random number generators also feature an internal state that changes every time a random number is generated. This state is used to generate the next random number in the sequence.
Unlike global scope random number generation, RandomNumberGenerator features a state property. This is useful if you are performing multiple random number operations, and you want to return to a previous state of the random number generator without changing the seed. To do so, store the current state in a variable, and then set the state property back to that variable when needed:
var random = RandomNumberGenerator.new()
# Changing the seed will reset the state, so make sure to set the seed first.
random.seed = 12345
var previous_random_state = random.state
# Each call to a random function on this instance alters its state.
print(random.randi())
random.state = previous_random_state
# This will return the same value as the previous call,
# even though we didn't change the seed.
print(random.randi())
var random = new RandomNumberGenerator();
// Changing the seed will reset the state, so make sure to set the seed first.
random.Seed = 12345;
int previousRandomState = random.State;
// Each call to a random function on this instance alters its state.
GD.Print(random.Randi());
random.State = previousRandomState;
// This will return the same value as the previous call,
// even though we didn't change the seed.
GD.Print(random.Randi());
Obtendo um número aleatório
Vamos dar uma olhada em algumas das funções e métodos mais comumente usados para gerar números aleatórios no Godot.
A função randi() retorna um número aleatório entre 0 e 2^32 - 1. Como o valor máximo é enorme, você provavelmente desejará usar o operador de módulo (%) para limitar o resultado entre 0 e o denominador:
# 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);
O randf() retorna um número de ponto flutuante aleatório entre 0 e 1. Isso é útil para implementar um sistema de Probabilidade aleatória ponderada, entre outras coisas.
O randfn() retorna um número de ponto flutuante aleatório seguindo uma distribuição normal. Isso significa que o valor retornado tem maior probabilidade de estar em torno da média (0.0 por padrão), variando pelo desvio padrão (1.0 por padrão):
# Prints a random floating-point number from a normal distribution with a mean 0.0 and deviation 1.0.
print(randfn(0.0, 1.0))
// Prints a random floating-point number from a normal distribution with a mean 0.0 and deviation 1.0.
GD.Print(GD.Randfn(0.0, 1.0));
O randf_range() recebe dois argumentos from e to e retorna um número de ponto flutuante aleatório entre from e to:
# Prints a random floating-point number between -4 and 6.5.
print(randf_range(-4, 6.5))
// Prints a random floating-point number between -4 and 6.5.
GD.Print(GD.RandRange(-4.0, 6.5));
O randi_range() recebe dois argumentos from e to e retorna um número inteiro aleatório entre from e to:
# Prints a random integer between -10 and 10.
print(randi_range(-10, 10))
// Prints a random integer number between -10 and 10.
GD.Print(GD.RandRange(-10, 10));
Obter um elemento aleatório de um array
Podemos usar a geração de números inteiros aleatórios para obter um elemento aleatório de um array, ou usar o método Array.pick_random para fazer isso por nós:
var _fruits = ["apple", "orange", "pear", "banana"]
func _ready():
for i in range(100):
# Pick 100 fruits randomly.
print(get_fruit())
for i in range(100):
# Pick 100 fruits randomly, this time using the `Array.pick_random()`
# helper method. This has the same behavior as `get_fruit()`.
print(_fruits.pick_random())
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
// Use Godot's Array type instead of a BCL type so we can use `PickRandom()` on it.
private Godot.Collections.Array<string> _fruits = ["apple", "orange", "pear", "banana"];
public override void _Ready()
{
for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly.
GD.Print(GetFruit());
}
for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly, this time using the `Array.PickRandom()`
// helper method. This has the same behavior as `GetFruit()`.
GD.Print(_fruits.PickRandom());
}
}
public string GetFruit()
{
string randomFruit = _fruits[GD.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 randomFruit;
}
Para evitar que a mesma fruta seja escolhida mais de uma vez seguida, podemos adicionar mais lógica ao método acima. Neste caso, não podemos usar Array.pick_random, pois ele carece de uma maneira de evitar a repetição:
var _fruits = ["apple", "orange", "pear", "banana"]
var _last_fruit = ""
func _ready():
# 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()
{
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;
}
Essa abordagem pode ser útil para fazer com que a geração de números aleatórios pareça menos repetitiva. Ainda assim, ela não impede que os resultados fiquem alternando ("ping-ponging") entre um conjunto limitado de valores. Para evitar isso, use o padrão de sacola de sorteio (shuffle bag) em seu lugar.
Obtenha um valor aleatório de um dicionário
Podemos aplicar uma lógica semelhante de arrays também para dicionários:
var _metals = {
"copper": {"quantity": 50, "price": 50},
"silver": {"quantity": 20, "price": 150},
"gold": {"quantity": 3, "price": 500},
}
func _ready():
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
private Godot.Collections.Dictionary<string, Godot.Collections.Dictionary<string, int>> _metals = new()
{
{"copper", new Godot.Collections.Dictionary<string, int>{{"quantity", 50}, {"price", 50}}},
{"silver", new Godot.Collections.Dictionary<string, int>{{"quantity", 20}, {"price", 150}}},
{"gold", new Godot.Collections.Dictionary<string, int>{{"quantity", 3}, {"price", 500}}},
};
public override void _Ready()
{
for (int i = 0; i < 20; i++)
{
GD.Print(GetMetal());
}
}
public Godot.Collections.Dictionary<string, int> GetMetal()
{
var (_, randomMetal) = _metals.ElementAt((int)(GD.Randi() % _metals.Count));
// Returns a random metal value dictionary every time the code runs.
// The same metal may be selected multiple times in succession.
return randomMetal;
}
Probabilidade aleatória ponderada
O método randf() retorna um número de ponto flutuante entre 0.0 e 1.0. Podemos usar isso para criar uma probabilidade "ponderada" (weighted), onde resultados diferentes têm probabilidades diferentes:
func _ready():
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()
{
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";
}
}
You can also get a weighted random index using the
RandomNumberGenerator.rand_weighted() method
on a RandomNumberGenerator instance. This returns a random integer
between 0 and the size of the array that is passed as a parameter. Each value in the
array is a floating-point number that represents the relative likelihood that it
will be returned as an index. A higher value means the value is more likely to be
returned as an index, while a value of 0 means it will never be returned as an index.
Por exemplo, se [0.5, 1, 1, 2] for passado como parâmetro, então o método tem o dobro de probabilidade de retornar 3 (o índice do valor 2) e metade da probabilidade de retornar 0 (O índice do valor 0.5) em comparação com os índices 1 e 2.
Como o valor retornado corresponde ao tamanho do array, ele pode ser usado como um índice para obter um valor de outro array da seguinte forma:
# Prints a random element using the weighted index that is returned by `rand_weighted()`.
# Here, "apple" will be returned twice as rarely as "orange" and "pear".
# "banana" is twice as common as "orange" and "pear", and four times as common as "apple".
var fruits = ["apple", "orange", "pear", "banana"]
var probabilities = [0.5, 1, 1, 2];
var random = RandomNumberGenerator.new()
print(fruits[random.rand_weighted(probabilities)])
// Prints a random element using the weighted index that is returned by `RandWeighted()`.
// Here, "apple" will be returned twice as rarely as "orange" and "pear".
// "banana" is twice as common as "orange" and "pear", and four times as common as "apple".
string[] fruits = ["apple", "orange", "pear", "banana"];
float[] probabilities = [0.5f, 1, 1, 2];
var random = new RandomNumberGenerator();
GD.Print(fruits[random.RandWeighted(probabilities)]);
Aleatoriedade "melhorada" usando sacolas de sorteio (shuffle bags)
Pegando o mesmo exemplo acima, gostaríamos de escolher frutas aleatoriamente. No entanto, confiar na geração de números aleatórios toda vez que uma fruta é selecionada pode levar a uma distribuição menos uniforme. Se o jogador tiver sorte (ou azar), ele poderá obter a mesma fruta três ou mais vezes seguidas.
Você pode fazer isso usando o padrão shuffle bag (sacola de sorteio). Ele funciona removendo um elemento do array após escolhê-lo. Após várias seleções, o array acaba ficando vazio. Quando isso acontece, você o reinicializa para o seu valor padrão:
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():
_fruits_full = _fruits.duplicate()
_fruits.shuffle()
for i in 100:
print(get_fruit())
func get_fruit():
if _fruits.is_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()
# Returns "apple", "orange", "pear", or "banana" every time the code runs, removing it from the array.
# When all fruit are removed, it refills the array.
return random_fruit
private Godot.Collections.Array<string> _fruits = ["apple", "orange", "pear", "banana"];
// A copy of the fruits array so we can restore the original value into `fruits`.
private Godot.Collections.Array<string> _fruitsFull;
public override void _Ready()
{
_fruitsFull = _fruits.Duplicate();
_fruits.Shuffle();
for (int i = 0; i < 100; i++)
{
GD.Print(GetFruit());
}
}
public string GetFruit()
{
if(_fruits.Count == 0)
{
// Fill the fruits array again and shuffle it.
_fruits = _fruitsFull.Duplicate();
_fruits.Shuffle();
}
// Get a random fruit, since we shuffled the array,
string randomFruit = _fruits[0];
// and remove it from the `_fruits` array.
_fruits.RemoveAt(0);
// Returns "apple", "orange", "pear", or "banana" every time the code runs, removing it from the array.
// When all fruit are removed, it refills the array.
return randomFruit;
}
Ao executar o código acima, existe a chance de obter a mesma fruta duas vezes seguidas. Uma vez escolhida uma fruta, ela não será mais um valor de retorno possível, a menos que o array esteja vazio. Quando o array está vazio, nós o redefinimos de volta ao seu valor padrão, tornando possível ter a mesma fruta novamente, mas apenas uma vez.
Ruído aleatório
A geração de números aleatórios mostrada acima pode apresentar limites quando você precisa de um valor que muda lentamente dependendo da entrada. A entrada pode ser uma posição, tempo ou qualquer outra coisa.
Para conseguir isso, você pode usar funções de ruído (noise) aleatório. As funções de ruído são especialmente populares na geração procedural para criar terrenos com aparência realista. O Godot fornece a classe FastNoiseLite para isso, que suporta ruído em 1D, 2D e 3D. Aqui está um exemplo com ruído 1D:
var _noise = FastNoiseLite.new()
func _ready():
# Configure the FastNoiseLite instance.
_noise.noise_type = FastNoiseLite.NoiseType.TYPE_SIMPLEX_SMOOTH
_noise.seed = randi()
_noise.fractal_octaves = 4
_noise.frequency = 1.0 / 20.0
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 FastNoiseLite _noise = new FastNoiseLite();
public override void _Ready()
{
// Configure the FastNoiseLite instance.
_noise.NoiseType = FastNoiseLite.NoiseTypeEnum.SimplexSmooth;
_noise.Seed = (int)GD.Randi();
_noise.FractalOctaves = 4;
_noise.Frequency = 1.0f / 20.0f;
for (int i = 0; i < 100; i++)
{
GD.Print(_noise.GetNoise1D(i));
}
}
Geração de números pseudoaleatórios criptograficamente segura
Até agora, as abordagens mencionadas acima não são adequadas para a geração de números pseudoaleatórios criptograficamente segura (CSPRNG). Isso é bom para jogos, mas não é suficiente para cenários onde envolvam criptografia, autenticação ou assinatura.
O Godot oferece uma classe Crypto para isso. Esta classe pode realizar criptografia/descriptografia de chave assimétrica, assinatura/verificação, além de gerar bytes aleatórios criptograficamente seguros, chaves RSA, digests HMAC e certificados autoassinados X509Certificate.
A desvantagem da CSPRNG é que ela é muito mais lenta do que a geração de números pseudoaleatórios padrão. Sua API também é menos conveniente de usar. Como resultado, a CSPRNG deve ser evitada para elementos de jogabilidade.
Exemplo de uso da classe Crypto para gerar 2 números inteiros aleatórios entre 0 e 2^32 - 1 (inclusive):
var crypto := Crypto.new()
# Request as many bytes as you need, but try to minimize the amount
# of separate requests to improve performance.
# Each 32-bit integer requires 4 bytes, so we request 8 bytes.
var byte_array := crypto.generate_random_bytes(8)
# Use the ``decode_u32()`` method from PackedByteArray to decode a 32-bit unsigned integer
# from the beginning of `byte_array`. This method doesn't modify `byte_array`.
var random_int_1 := byte_array.decode_u32(0)
# Do the same as above, but with an offset of 4 bytes since we've already decoded
# the first 4 bytes previously.
var random_int_2 := byte_array.decode_u32(4)
prints("Random integers:", random_int_1, random_int_2)
Ver também
Consulte a documentação da classe PackedByteArray para outros métodos que você pode usar para decodificar os bytes gerados em vários tipos de dados, como inteiros ou pontos flutuantes.