Up to date

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

Генерація випадкових чисел

Багато ігор покладаються на випадковість для реалізації основної механіки гри. Ця сторінка допоможе вам ознайомитися з поширеними типами випадковості та тим, як їх реалізувати в 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.

Примітка

Комп'ютери не можуть генерувати "справжні" випадкові числа. Замість цього вони покладаються на `генератори псевдовипадкових чисел<https://en.wikipedia.org/wiki/Pseudorandom_number_generator>`__ (PRNG-и).

Godot internally uses the PCG Family of pseudorandom number generators.

Глобальна область та клас RandomNumberGenerator

Godot пропонує два способи генерації випадкових чисел: за допомогою застосування методів глобальної області, або за допомогою класу RandomNumberGenerator (Генератор Випадкових Чисел).

Глобальні методи легше налаштувати, але вони не пропонують багато контролю.

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

Ця стаття використовує методи глобальної області, за винятком випадків, коли метод існує лише в класі RandomNumberGenerator.

Метод randomize()

Примітка

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.

Найкраще буде розмістити його в скрипті головної сцени в методі _ready():

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

Використовуючи клас RandomNumberGenerator, ви повинні викликати randomize() на екземплярі, оскільки він має власне початкове значення:

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

Отримання випадкового числа

Давайте розглянемо деякі з найбільш часто використовуваних функцій і методів для генерування випадкових чисел у 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 Зважена випадкова ймовірність system, among other things.

randfn() повертає випадкове число з десятковою комою після `нормального розподілу<https://en.wikipedia.org/wiki/Normal_distribution>`__. Це означає, що повернуте значення, швидше за все, буде приблизно середнього значення (0,0 за замовчуванням), варійованим в межах відхилення (1,0 за замовчуванням):

# 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() приймає два аргументи from і to, і повертає випадкове ціле число між from і to:

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

Отримання випадкового елемента масиву

Ми можемо використовувати генерацію випадкових цілих чисел, щоб отримати випадковий елемент із масиву:

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

Щоб той самий фрукт не обирався більше одного разу поспіль, ми можемо додати більше логіки до цього методу:

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

Цей підхід може допомогти зробити генерацію випадкових чисел менш повторюваною. Тим не менш, це не перешкоджає результатам з "пінг-понгом" між обмеженим набором значень. Щоб запобігти цьому, використовуйте натомість шаблон перемішування.

Отримання випадкового значення словника

Ми також можемо застосувати подібну логіку від масивів до словників:

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

Зважена випадкова ймовірність

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"

"Краща" випадковість за допомогою перемішування сумки

Візьмемо наведений вище приклад, ми хочемо вибирати фрукти навмання. Однак, при генерації випадкових чисел щоразу, коли вибирається фрукт, можна отримати менш рівномірний розподіл. Якщо гравцеві пощастить (або не пощастить), він може отримати один і той же фрукт три, або більше, рази поспіль.

Ви можете зробити це за допомогою шаблону перемішування сумки. Його робота базується на видаленні елемента з масиву після його вибору. Після кількох вибірок масив виявляється порожнім. Коли це станеться, ви повторно ініціалізуєте його до значення за замовчуванням:

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

Під час виконання наведеного вище коду є шанс отримати один і той же фрукт двічі поспіль. Після того, як ми виберемо фрукти, масив стане порожнім і не зможе нічого повертати. Коли масив порожній, ми вертаємо його до значення за замовчуванням, завдяки чому можна знову отримати той самий фрукт, але лише один раз.

Випадковий шум

Генерація випадкових чисел, показана вище, може показати свої межі, коли вам потрібно значення, яке повільно змінюється залежно від введення. Введенням може бути позиція, час, або будь-що інше.

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)

Дивись також

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.