Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Génération de nombres aléatoires

De nombreux jeux s'appuient sur le hasard pour mettre en œuvre certaines de leurs mécaniques fondamentales. Cette page va vous guider à travers les types courants d'aléatoire et comment les implémenter dans 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.

Note

Les ordinateurs ne peuvent pas générer de "vrais" nombres aléatoires. Ils s'appuient plutôt sur des générateurs de nombres pseudo-aléatoires (PRNG).

Godot internally uses the PCG Family of pseudorandom number generators.

Portée globale par rapport à la classe RandomNumberGenerator

Godot possède deux façons de générer des nombres aléatoires : via des méthodes portée globale (disponible à tout endroit du code) ou en utilisant la classe RandomNumberGenerator.

Les méthodes de portée globale sont plus faciles à mettre en place, mais elles n'offrent pas autant de contrôle.

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

Ce tutoriel utilise des méthodes de portée globale, sauf lorsque la méthode n'existe que dans la classe RandomNumberGenerator.

La méthode randomize()

Note

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.

Le placer dans la méthode ready() de votre script de scène principal est un bon choix :

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

Lorsque vous utilisez la classe RandomNumberGenerator, vous devez appeler randomize() sur l'instance car elle a sa propre graine(seed) :

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

Obtenir un nombre aléatoire

Examinons certaines des fonctions et méthodes les plus couramment utilisées pour générer des nombres aléatoires dans 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 Probabilité aléatoire pondérée system, among other things.

randfn() renvoie un nombre flottant aléatoire selon une distribution normale. Cela signifie que la valeur retournée est plus susceptible d’être autour de la moyenne (0,0 par défaut), variant selon l’écart (1,0 par défaut) :

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

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

RandomNumberGenerator.randi_range() prend deux arguments from et to, et retourne un entier aléatoire entre from et to :

# Prints a random integer between -10 and 10.
var random = RandomNumberGenerator.new()
random.randomize()
print(random.randi_range(-10, 10))

Obtenir un élément aléatoire du tableau

Nous pouvons utiliser la génération d'entiers aléatoires pour obtenir un élément aléatoire à partir d’un tableau :

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

Pour éviter que le même fruit ne soit cueilli plusieurs fois de suite, nous pouvons ajouter plus de logique à cette méthode :

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

Cette approche peut être utile pour rendre la génération de nombres aléatoires moins répétitive. Néanmoins, elle n'empêche pas les résultats de "ping-pong" entre un ensemble limité de valeurs. Pour éviter cela, utilisez plutôt le design pattern (patron de conception) du sac mélangé.

Obtenir une valeur aléatoire d'un dictionnaire

Nous pouvons également appliquer aux dictionnaires une logique similaire à celle des tableaux :

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

Probabilité aléatoire pondérée

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"

Un "meilleur" aléatoire en utilisant le sac mélangé (shuffle bags)

En prenant le même exemple que ci-dessus, nous aimerions choisir des fruits au hasard. Cependant, le fait de s'appuyer sur la génération de nombres aléatoires à chaque fois qu'un fruit est sélectionné peut conduire à une distribution moins uniforme. Si le joueur est chanceux (ou malchanceux), il peut obtenir le même fruit trois fois de suite ou plus.

Vous pouvez y parvenir en utilisant le design pattern shuffle bag. Il fonctionne en retirant un élément du tableau après l'avoir choisi. Après plusieurs sélections, le tableau finit par être vide. Lorsque cela se produit, vous le réinitialisez à sa valeur par défaut : :

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

En exécutant le code ci-dessus, il y a une chance d'obtenir le même fruit deux fois de suite. Une fois que nous avons cueilli un fruit, il ne sera plus une valeur de retour possible à moins que le tableau soit maintenant vide. Lorsque le tableau est vide, nous le réinitialisons à sa valeur par défaut, ce qui permet d'obtenir à nouveau le même fruit, mais une seule fois.

Bruit aléatoire

La génération de nombres aléatoires présentée ci-dessus peut montrer ses limites lorsque vous avez besoin d'une valeur qui change lentement en fonction de l'entrée. L'entrée peut être une position, un temps, ou n'importe quoi d'autre.

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():
    randomize()
    # 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)

Voir aussi

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.