Zufallszahlengenerierung

Viele Spiele basieren auf Zufälligkeit, um die zentrale Spielmechaniken zu implementieren. Diese Seite führt Sie durch gängige Arten von Zufälligkeiten und deren Implementierung in Godot.

Nach einem kurzen Überblick über nützliche Funktionen zur Erzeugung von Zufallszahlen erfahren Sie, wie Sie Zufallselemente aus Arrays und Dictionaries abrufen und wie Sie einen Rauschgenerator in GDScript verwenden können.

Bemerkung

Computer können keine "echten" Zufallszahlen erzeugen. Stattdessen verlassen sie sich auf "Pseudozufallszahlengeneratoren" <https://en.wikipedia.org/wiki/Pseudorandom_number_generator>`__ (PRNGs).

Globaler Gültigkeitsbereich im Vergleich zur RandomNumberGenerator-Klasse

Godot bietet zwei Möglichkeiten, Zufallszahlen zu generieren: über Methoden im globalen Bereich oder mit der Klasse RandomNumberGenerator.

Globale Bereichsmethoden sind einfacher einzurichten, bieten jedoch nicht so viel Kontrolle.

RandomNumberGenerator erfordert mehr Code, bietet aber viele Methoden, die nicht im globalen Bereich zu finden sind, wie randi_range() und randfn(). Darüber hinaus können mehrere Instanzen mit jeweils eigenem Startwert erstellt werden.

In dieser Anleitung werden globale Bereichsmethoden verwendet, es sei denn, die Methode ist nur in der RandomNumberGenerator-Klasse vorhanden.

Die Methode randomize()

Im globalen Bereich finden Sie eine Methode randomize(). Diese Methode sollte nur einmal aufgerufen werden, wenn Ihr Projekt beginnt, um den Zufallsgenerator zu initialisieren. Ein mehrfacher Aufruf ist unnötig und kann die Leistung negativ beeinflussen.

Es ist eine gute Wahl, sie in die _ready() Methode Ihres Hauptszenenskripts einzubauen:

func _ready():
    randomize()

Sie können stattdessen auch einen festen zufälligen Startwert mit seed() setzen. Auf diese Weise erhalten Sie deterministische Ergebnisse über mehrere Durchläufe hinweg:

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

Wenn Sie die Klasse RandomNumberGenerator verwenden, sollten Sie randomize() für die Instanz aufrufen, da diese ihren eigenen Startwert hat:

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

Ermitteln einer Zufallszahl

Sehen wir uns einige der am häufigsten verwendeten Funktionen und Methoden zur Erzeugung von Zufallszahlen in Godot an.

Die Funktion randi() gibt eine Zufallszahl zwischen 0 und 2^32-1 zurück. Da der Maximalwert sehr groß ist, wollen Sie wahrscheinlich den Modulo-Operator (%) verwenden, um das Ergebnis zwischen 0 und dem Nenner zu begrenzen:

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

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

randf() gibt eine zufällige Fließkommazahl zwischen 0 und 1 zurück. Dies ist u.a. nützlich, um ein Gewichtete zufällige Wahrscheinlichkeit-System zu implementieren.

randfn() gibt eine zufällige Gleitkommazahl zurück, die einer Normalverteilung <https://en.wikipedia.org/wiki/Normal_distribution> folgt. Das bedeutet, dass der zurückgegebene Wert mit größerer Wahrscheinlichkeit um den Mittelwert (standardmäßig 0,0) liegt und um die Abweichung (standardmäßig 1,0) variiert:

# 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() nimmt zwei Argumente von und bis und gibt eine zufällige Gleitkommazahl zwischen von und bis zurück:

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

RandomNumberGenerator.randi_range() nimmt zwei Argumente von und bis, und gibt eine zufällige ganze Zahl zwischen von und bis zurück:

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

Ein zufälliges Array-Element holen

Wir können zufällige Ganzzahlen erzeugen, um ein zufälliges Element aus einem Array zu erhalten:

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

Um zu verhindern, dass dieselbe Frucht mehrmals hintereinander ausgewählt wird, können wir dieser Methode mehr Logik hinzufügen:

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

Dieser Ansatz kann nützlich sein, damit sich die Zufallszahlengenerierung weniger repetitiv anfühlt. Dennoch verhindert es nicht, dass Ergebnisse zwischen einem begrenzten Satz von Werten hin und her springen. Um dies zu verhindern, verwenden Sie stattdessen das Muster shuffle bag.

Abrufen eines zufälligen Dictionary-Werts

Wir können eine ähnliche Logik der Arrays auch auf Dictionaries anwenden:

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

Gewichtete zufällige Wahrscheinlichkeit

Die Methode randf() gibt eine Gleitkommazahl zwischen 0,0 und 1,0 zurück. Damit können wir eine "gewichtete" Wahrscheinlichkeit erstellen, bei der verschiedene Ergebnisse unterschiedliche Wahrscheinlichkeiten haben:

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"

"Bessere" Zufälligkeit durch Shuffle-Bags

Nehmen wir das gleiche Beispiel wie oben: Wir möchten die Früchte nach dem Zufallsprinzip auswählen. Wenn man sich jedoch jedes Mal, wenn eine Frucht ausgewählt wird, auf die Generierung von Zufallszahlen verlässt, kann dies zu einer weniger gleichmäßigen Verteilung führen. Wenn der Spieler Glück (oder Pech) hat, könnte er drei oder mehr Mal hintereinander dieselbe Frucht erhalten.

Sie können dies mit dem Muster shuffle bag erreichen. Dabei wird ein Element aus dem Array entfernt, nachdem es ausgewählt wurde. Nach mehrfacher Auswahl ist das Array schließlich leer. Wenn das passiert, setzen Sie es auf seinen Standardwert zurück:

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

Bei der Ausführung des obigen Codes besteht die Möglichkeit, zweimal hintereinander die gleiche Frucht zu erhalten. Sobald wir eine Frucht gepflückt haben, ist sie kein möglicher Rückgabewert mehr, es sei denn, das Array ist jetzt leer. Wenn das Array leer ist, setzen wir es auf seinen Standardwert zurück, so dass es möglich ist, dieselbe Frucht erneut zu erhalten, aber nur einmal.

Zufälliges Rauschen

Die oben gezeigte Zufallszahlengenerierung stößt an ihre Grenzen, wenn Sie einen Wert benötigen, der sich langsam in Abhängigkeit von der Eingabe ändert. Die Eingabe kann eine Position, eine Zeit oder etwas anderes sein.

Um dies zu erreichen, können Sie zufällige Rausch-Funktionen verwenden. Rauschfunktionen sind besonders beliebt in der prozeduralen Generierung, um realistisch aussehendes Terrain zu erzeugen. Godot bietet hierfür OpenSimplexNoise, das 1D, 2D, 3D und 4D Rauschen unterstützt. Hier ist ein Beispiel mit 1D-Rauschen:

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