Generación de números aleatorios

Muchos juegos se basan en la aleatoriedad para implementar la mecánica central del juego. Esta página lo guía a través de los tipos comunes de aleatoriedad y cómo implementarlos en Godot.

After giving you a brief overview of useful functions that generate random numbers, you will learn how to get random elements from arrays, dictionaries, and how to use a noise generator in GDScript. Lastly, we'll take a look at cryptographically secure random number generation and how it differs from typical random number generation.

Nota

Las computadoras no pueden generar números aleatorios "verdaderos". En su lugar, se basan en generadores de números pseudoaleatorios (PRNGs).

Godot internally uses the PCG Family of pseudorandom number generators.

Alcance global versus clase RandomNumberGenerator

Godot exposes two ways to generate random numbers: via global scope methods or using the RandomNumberGenerator class.

Los métodos de alcance global son más fáciles de configurar, pero no ofrecen tanto control.

RandomNumberGenerator requires more code to use, but allows creating multiple instances, each with their own seed and state.

Este tutorial usa métodos de alcance global, excepto cuando el método sólo existe en la clase RandomNumberGenerator.

El método randomize()

Nota

Since Godot 4.0, the random seed is automatically set to a random value when the project starts. This means you don't need to call randomize() in _ready() anymore to ensure that results are random across project runs. However, you can still use randomize() if you want to use a specific seed number, or generate it using a different method.

In global scope, you can find a randomize() method. This method should be called only once when your project starts to initialize the random seed. Calling it multiple times is unnecessary and may impact performance negatively.

Ponerlo en el método _ready() de la secuencia de comandos de la escena principal es una buena opción:

func _ready():
    randomize()

You can also set a fixed random seed instead using seed(). Doing so will give you deterministic results across runs:

func _ready():
    seed(12345)
    # To use a string as a seed, you can hash it to a number.
    seed("Hello world".hash())

Al usar la clase RandomNumberGenerator, debe llamar a randomize() en la instancia ya que tiene su propia semilla:

var random = RandomNumberGenerator.new()
random.randomize()

Obtener un número aleatorio

Veamos algunas de las funciones y métodos más utilizados para generar números aleatorios en Godot.

The function randi() returns a random number between 0 and 2^32 - 1. Since the maximum value is huge, you most likely want to use the modulo operator (%) to bound the result between 0 and the denominator:

# Prints a random integer between 0 and 49.
print(randi() % 50)

# Prints a random integer between 10 and 60.
print(randi() % 51 + 10)

randf() returns a random floating-point number between 0 and 1. This is useful to implement a Probabilidad aleatoria ponderada system, among other things.

randfn() returns a random floating-point number following a normal distribution. This means the returned value is more likely to be around the mean (0.0 by default), varying by the deviation (1.0 by default):

# 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))

randf_range() takes two arguments from and to, and returns a random floating-point number between from and to:

# Prints a random floating-point number between -4 and 6.5.
print(randf_range(-4, 6.5))

randi_range() takes two arguments from and to, and returns a random integer between from and to:

# Prints a random integer between -10 and 10.
print(randi_range(-10, 10))

Obtener un elemento aleatorio de un array

We can use random integer generation to get a random element from an array, or use the Array.pick_random method to do it for us:

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

To prevent the same fruit from being picked more than once in a row, we can add more logic to the above method. In this case, we can't use Array.pick_random since it lacks a way to prevent repetition:

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

Este enfoque puede ser útil para hacer que la generación de números aleatorios se sienta menos repetitiva. Aún así, no evita que los resultados "hagan ping-pong" entre un conjunto limitado de valores. Para evitar esto, usa el patrón shuffle bag en su lugar.

Obtener un valor de diccionario aleatorio

Podemos aplicar una lógica similar de arrays a diccionarios:

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

Probabilidad aleatoria ponderada

The randf() method returns a floating-point number between 0.0 and 1.0. We can use this to create a "weighted" probability where different outcomes have different likelihoods:

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"

You can also get a weighted random index using the 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.

For example, if [0.5, 1, 1, 2] is passed as a parameter, then the method is twice as likely to return 3 (the index of the value 2) and twice as unlikely to return 0 (the index of the value 0.5) compared to the indices 1 and 2.

Since the returned value matches the array's size, it can be used as an index to get a value from another array as follows:

# 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)])

Una aleatoriedad "mejor" usando shuffle bags

Tomando el mismo ejemplo anterior, nos gustaría recoger frutas al azar. Sin embargo, confiar en la generación de números aleatorios cada vez que se selecciona una fruta puede llevar a una distribución menos uniforme. Si el jugador tiene suerte (o mala suerte), podría obtener la misma fruta tres o más veces seguidas.

You can accomplish this using the shuffle bag pattern. It works by removing an element from the array after choosing it. After multiple selections, the array ends up empty. When that happens, you reinitialize it to its default value:

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

Al ejecutar el código anterior, existe la posibilidad de obtener la misma fruta dos veces seguidas. Una vez que seleccionamos una fruta, ya no será un valor de retorno posible a menos que el array esté vacío. Cuando el array esté vacío, la restablecemos a su valor predeterminado, lo que hace posible tener la misma fruta nuevamente, pero sólo una vez.

Ruido aleatorio

La generación de números aleatorios que se muestra arriba puede mostrar sus límites cuando necesita un valor que lentamente cambia según el input. El input puede ser una posición, una hora o cualquier otra cosa.

To achieve this, you can use random noise functions. Noise functions are especially popular in procedural generation to generate realistic-looking terrain. Godot provides FastNoiseLite for this, which supports 1D, 2D and 3D noise. Here's an example with 1D noise:

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))

Cryptographically secure pseudorandom number generation

So far, the approaches mentioned above are not suitable for cryptographically secure pseudorandom number generation (CSPRNG). This is fine for games, but this is not sufficient for scenarios where encryption, authentication or signing is involved.

Godot offers a Crypto class for this. This class can perform asymmetric key encryption/decryption, signing/verification, while also generating cryptographically secure random bytes, RSA keys, HMAC digests, and self-signed X509Certificates.

The downside of CSPRNG is that it's much slower than standard pseudorandom number generation. Its API is also less convenient to use. As a result, CSPRNG should be avoided for gameplay elements.

Example of using the Crypto class to generate 2 random integers between 0 and 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 también

See PackedByteArray's documentation for other methods you can use to decode the generated bytes into various types of data, such as integers or floats.