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.

Después de brindarle una breve descripción general de las funciones útiles que generan números aleatorios, aprenderá cómo obtener elementos aleatorios de matrices, diccionarios y cómo usar un generador de ruido en GDScript.

Nota

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

Alcance global versus clase RandomNumberGenerator

Godot expone dos formas de generar números aleatorios: a través de métodos de global scope o usando la clase: ref: class_RandomNumberGenerator.

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

RandomNumberGenerator requiere más código para usar, pero expone muchos métodos que no se encuentran en el alcance global, como randi_range() y randfn(). Además de eso, permite crear múltiples instancias, cada una con su propia semilla.

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

El método randomize()

En el ámbito global, puede encontrar un método randomize(). Este método debe llamarse sólo una vez cuando su proyecto comienza a inicializar la semilla aleatoria. Llamarlo varias veces es innecesario y puede afectar el rendimiento de manera negativa.

Putting it in your main scene script's _ready() method is a good choice:

func _ready():
    randomize()
public override void _Ready()
{
    GD.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())
public override void _Ready()
{
    GD.Seed(12345);
    GD.Seed("Hello world".Hash());
}

When using the RandomNumberGenerator class, you should call randomize() on the instance since it has its own seed:

var random = RandomNumberGenerator.new()
random.randomize()
var random = new RandomNumberGenerator();
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)
// 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() devuelve un número de punto flotante aleatorio entre 0 y 1. Esto es útil para implementar un sistema : ref: Probabilidad aleatoria ponderada, entre otras cosas.

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.
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() 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(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() takes two arguments from and to, and returns a random integer between from and to:

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

Obtener un elemento aleatorio de un array

We can use random integer generation to get a random element from an array:

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;
}

To prevent the same fruit from being picked more than once in a row, we can add more logic to this method:

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;
}

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, use el patrón shuffle bag en su lugar.

Obtener un valor de diccionario aleatorio

We can apply similar logic from arrays to dictionaries as well:

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

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():
    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";
    }
}

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.

Puede lograr esto usando el patrón shuffle bag. Funciona eliminando un elemento del array después de elegirlo. Después de varias selecciones, la matriz termina vacía. Cuando eso sucede, lo reinicializa a su valor predeterminado:

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

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 OpenSimplexNoise for this, which supports 1D, 2D, 3D, and 4D noise. Here's an example with 1D noise:

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