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...
Generazione di numeri casuali
Molti giochi si affidano alla casualità per implementare certe meccaniche di gioco principali. Questa pagina ti guiderà attraverso i tipi più comuni di casualità e come implementarli in Godot.
Dopo una breve panoramica delle funzioni utili che generano numeri casuali, imparerai come ottenere elementi casuali da array e dizionari e come utilizzare un generatore di noise in GDScript. Infine, esamineremo la generazione di numeri casuali crittograficamente sicura e le sue differenze rispetto alla tipica generazione di numeri casuali.
Nota
I computer non possono generare numeri casuali "veri". Si affidano invece ai generatori di numeri pseudocasuali (PRNG).
Godot utilizza internamente la famiglia PCG di generatori di numeri pseudocasuali.
Ambito globale rispetto alla classe RandomNumberGenerator
Godot espone due modi per generare numeri casuali: tramite metodi di ambito globale o attraverso la classe RandomNumberGenerator.
I metodi di ambito globale sono più facili da configurare, ma non offrono lo stesso controllo.
RandomNumberGenerator richiede più codice per essere utilizzato, ma consente di creare più istanze, ciascuna con il proprio seed e stato.
In questo tutorial vengono utilizzati metodi di ambito globale, tranne quando il metodo esiste solo nella classe RandomNumberGenerator.
Il metodo randomize()
Nota
A partire da Godot 4.0, il seed casuale viene impostato automaticamente su un valore casuale all'avvio del progetto. Questo significa che non è più necessario chiamare randomize() in _ready() per garantire che i risultati siano casuali tra diverse esecuzioni del progetto. Tuttavia, è comunque possibile utilizzare randomize() se si desidera utilizzare un numero specifico di seed, oppure generarlo tramite un metodo diverso.
Nell'ambito globale, puoi trovare il metodo randomize(). Questo metodo si dovrebbe chiamare una sola volta all'inizio del progetto, per inizializzare il seed casuale. Chiamarlo più volte è superfluo e potrebbe influire negativamente sulle prestazioni.
Una buona scelta è inserirlo nel metodo _ready() dello script della scena principale:
func _ready():
randomize()
public override void _Ready()
{
GD.Randomize();
}
È possibile anche impostare un seed casuale fisso utilizzando seed(). Così facendo i risultati saranno deterministici da un esecuzione all'altra:
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());
}
Quando si utilizza la classe RandomNumberGenerator, è necessario chiamare randomize() sull'istanza poiché ha il proprio seed:
var random = RandomNumberGenerator.new()
random.randomize()
var random = new RandomNumberGenerator();
random.Randomize();
Ottenere un numero casuale
Esaminiamo alcune delle funzioni e dei metodi più comunemente utilizzati per generare numeri casuali in Godot.
La funzione randi() restituisce un numero casuale compreso tra 0 e 2^32 - 1. Dato che il valore massimo è enorme, molto probabilmente si desidera utilizzare l'operatore modulo (%) per limitare il risultato tra 0 e il denominatore:
# 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() restituisce un numero casuale in virgola mobile compreso tra 0 e 1. Questo è utile, tra altre cose, per implementare un sistema di Probabilità casuale ponderata.
randfn() restituisce un numero casuale in virgola mobile seguendo una distribuzione normale. Ciò significa che è più probabile che il valore restituito si avvicini alla media (0,0 per valore predefinito), variando in base alla deviazione (1,0 per valore predefinito):
# 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));
randf_range() accetta due argomenti from e to e restituisce un numero casuale in virgola mobile compreso tra 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));
randi_range() accetta due argomenti from e to e restituisce un numero intero casuale compreso tra 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));
Ottenere un elemento casuale di un array
Possiamo utilizzare la generazione di numeri interi casuali per ottenere un elemento casuale da un array, oppure utilizzare il metodo Array.pick_random affinché lo faccia per noi:
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;
}
Per impedire che lo stesso frutto sia scelto più volte di seguito, possiamo aggiungere ulteriore logica al metodo precedente. In questo caso, non possiamo utilizzare Array.pick_random perché non ha un modo per impedire le ripetizioni:
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;
}
Questo approccio può essere utile per far sembrare la generazione di numeri casuali meno ripetitiva. Eppure, non impedisce che i risultati "facciano ping-pong" tra un insieme limitato di valori. Per evitare ciò, utilizzare il pattern shuffle bag.
Ottenere un value casuale da un dizionario
Possiamo applicare anche ai dizionari una logica simile a quella degli array:
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;
}
Probabilità casuale ponderata
Il metodo randf() restituisce un numero in virgola mobile compreso tra 0,0 e 1,0. Possiamo usarlo per creare una probabilità "ponderata" in cui risultati diversi hanno probabilità diverse:
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";
}
}
È anche possibile ottenere un indice casuale ponderato utilizzando il metodo rand_weighted() su un'istanza di RandomNumberGenerator. Questo restituisce un numero intero casuale compreso tra 0 e la dimensione dell'array passato come parametro. Ogni valore nell'array è un numero in virgola mobile che rappresenta la probabilità relativa che sia restituito come indice. Un valore più alto indica che è più probabile che il valore sia restituito come indice, mentre un valore pari a 0 indica che non sarà mai restituito come indice.
Ad esempio, se [0.5, 1, 1, 2] è passato come parametro, è due volte più probabile che il metodo restituisca 3 (l'indice del valore 2) e due volte più improbabile che restituisca 0 (l'indice del valore 0.5) rispetto agli indici 1 e 2.
Poiché il valore restituito corrisponde alla dimensione dell'array, può essere utilizzato come indice per ottenere un valore da un altro array, come segue:
# 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)]);
Una casualità "migliore" tramite il pattern shuffle bags
Riprendendo lo stesso esempio precedente, vorremmo scegliere dei frutti a caso. Tuttavia, affidarsi alla generazione di numeri casuali ogni volta che si seleziona un frutto può portare a una distribuzione meno uniforme. Se il giocatore è fortunato (o sfortunato), potrebbe ottenere lo stesso frutto tre o più volte di seguito.
Ciò si può fare utilizzando il pattern shuffle bag. Esso consiste nel rimuovere un elemento dall'array dopo averlo scelto. Dopo varie scelte, l'array risulta vuoto. Quando ciò accade, lo si ripristina al suo valore predefinito:
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;
}
Eseguendo il codice precedente, c'è la possibilità di ottenere lo stesso frutto due volte di seguito. Una volta scelto un frutto, non sarà più un possibile valore restituito, a meno che l'array non sia vuoto. Quando l'array è vuoto, lo ripristiniamo al suo valore predefinito, rendendo possibile ottenere nuovamente lo stesso frutto, ma solo una volta.
Noise casuale
La generazione di numeri casuali presentata in precedenza può mostrare i suoi limiti quando è necessario un valore che cambia lentamente a seconda dell'input. L'input può essere una posizione, un orario o qualsiasi altra cosa.
Per ottenerlo, è possibile utilizzare le funzioni di noise casuale. Le funzioni di noise sono particolarmente note nella generazione procedurale per generare terreni dall'aspetto realistico. A questo scopo Godot fornisce FastNoiseLite, il quale supporta noise 1D, 2D e 3D. Ecco un esempio con noise 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));
}
}
Generazione di numeri pseudo-casuali crittograficamente sicuri
Finora, gli approcci summenzionati non sono adatti alla generazione di numeri pseudo-casuali crittograficamente sicuri (CSPRNG). Questo va bene per i giochi, ma non è sufficiente per scenari che richiedono crittografia, autenticazione o firma.
Godot offre una classe Crypto per questo scopo. Questa classe può eseguire crittografia/decrittografia a chiave asimmetrica, firma/verifica, generando anche byte casuali crittograficamente sicuri, chiavi RSA, digest HMAC e certificati X509Certificate autofirmati.
Lo svantaggio di CSPRNG è che è molto più lenta della generazione di numeri pseudo-casuali standard. Anche la sua API è meno pratica da usare. Pertanto, CSPRNG si dovrebbe evitare per gli elementi di gioco.
Esempio di utilizzo della classe Crypto per generare 2 numeri interi casuali compresi tra 0 e 2^32 - 1 (inclusi):
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)
Vedi anche
Consultare la documentazione di PackedByteArray per altri metodi utilizzabili per decodificare i byte generati in vari tipi di dati, come numeri interi o float.