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

Багато ігор покладаються на випадковість для реалізації основної механіки гри. Ця сторінка допоможе вам ознайомитися з поширеними типами випадковості та тим, як їх реалізувати в Godot.

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

Примітка

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

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

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

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

RandomNumberGenerator вимагає більше коду, але дає багато методів, яких нема у глобальній області, таких як randi_range() і randfn(). Крім того, він дозволяє створювати кілька екземплярів, кожен зі своїм власним початковим значенням.

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

Метод randomize()

У глобальній області можна знайти метод randomize(). Цей метод слід викликати тільки один раз, коли ваш проект починає ініціалізувати випадкове початкове значення. Викликати його кілька разів непотрібно, це може негативно вплинути на продуктивність.

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

func _ready():
    randomize()

Ви також можете встановити фіксоване випадкове початкове значення використовуючи seed(). Це дасть вам детерміновані результати для всіх запусків:

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.

Функція randi() повертає випадкове число від 0 до 2^32-1. Оскільки максимальне значення величезне, ви, швидше за все, захочете використовувати оператор модуля (%), щоб зв’язати результат між 0 і знаменником:

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

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

randf() повертає випадкове число з десятковою комою від 0 до 1. Це корисно для реалізації системи Зважена випадкова ймовірність.

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

rand_range() приймає два аргументи from і to, і повертає випадкове число з десятковою комою між from і to:

# Prints a random floating-point number between -4 and 6.5.
print(rand_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

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

Метод randf() повертає число з десятковою комою між 0,0 і 1,0. Ми можемо використовувати його для створення "зваженої" ймовірності, де різні результати мають різні ймовірності:

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

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

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

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

Для цього можна використовувати функції випадкового шуму. Функції шуму особливо популярні в процедурній генерації для створення реалістичного ландшафту. Godot надає для цього OpenSimplexNoise, який підтримує 1D, 2D, 3D і 4D шум. Ось приклад із шумом 1D:

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