Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
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 Generierung von Zufallszahlen erfahren Sie, wie Sie Zufallselemente aus Arrays und Dictionarys erhalten und wie Sie einen Rauschgenerator in GDScript verwenden können. Zum Schluss werfen wir einen Blick auf die kryptografisch sichere Zufallszahlengenerierung und wie sie sich von der typischen Zufallszahlengenerierung unterscheidet.
Bemerkung
Computer können keine "echten" Zufallszahlen erzeugen. Stattdessen verlassen sie sich auf Pseudozufallszahlengeneratoren (PRNGs).
Godot verwendet intern die PCG-Familie von Pseudozufallszahlengeneratoren.
Global-Scope vs. RandomNumberGenerator-Klasse
Godot bietet zwei Möglichkeiten, Zufallszahlen zu generieren: über Global Scope-Methoden oder mit der Klasse RandomNumberGenerator.
Global-Scope-Methoden sind einfacher einzurichten, bieten jedoch nicht so viel Kontrolle.
RandomNumberGenerator erfordert mehr Code, ermöglicht aber die Erstellung mehrerer Instanzen, jede mit eigenem Seed und Status.
In diesem Tutorial werden Global-Scope-Methoden verwendet, es sei denn, die Methode ist nur in der RandomNumberGenerator-Klasse vorhanden.
Die randomize()-Methode
Bemerkung
Seit Godot 4.0 wird der Zufallswert automatisch auf einen Zufallswert gesetzt, wenn das Projekt startet. Das bedeutet, dass Sie randomize() in _ready() nicht mehr aufrufen müssen, um sicherzustellen, dass die Ergebnisse über alle Projektläufe hinweg zufällig sind. Sie können jedoch immer noch randomize() verwenden, wenn Sie eine bestimmte Seed-Zahl verwenden oder diese mit einer anderen Methode erzeugen wollen.
Im globalen Scope 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 Performance negativ beeinflussen.
Es ist eine gute Wahl, sie in die _ready() Methode Ihres Hauptszenenskripts einzubauen:
func _ready():
randomize()
public override void _Ready()
{
GD.Randomize();
}
Sie können stattdessen auch einen festen willkürlichen Seed-Wert mit seed() setzen. Auf diese Weise erhalten Sie deterministische Ergebnisse über alle Läufe hinweg:
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);
// To use a string as a seed, you can hash it to a number.
GD.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()
var random = new RandomNumberGenerator();
random.Randomize();
Ermitteln einer Zufallszahl
Sehen wir uns einige der am häufigsten verwendeten Funktionen und Methoden zur Erzeugung von Zufallszahlen in Godot an.
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() liefert eine zufällige Float-Zahl zwischen 0 und 1. Dies ist unter anderem nützlich, um ein Gewichtete zufällige Wahrscheinlichkeit-System zu implementieren.
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.
print(randfn(0.0, 1.0))
// Prints a random floating-point number from a normal distribution with a mean 0.0 and deviation 1.0.
GD.Print(GD.Randfn(0.0, 1.0));
randf_range() nimmt zwei Argumente from und to, und gibt eine zufällige Float-Zahl zwischen from und to zurück:
# Prints a random floating-point number between -4 and 6.5.
print(randf_range(-4, 6.5))
// Prints a random floating-point number between -4 and 6.5.
GD.Print(GD.RandRange(-4.0, 6.5));
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.
print(randi_range(-10, 10))
// Prints a random integer number between -10 and 10.
GD.Print(GD.RandRange(-10, 10));
Auf ein zufälliges Array-Element zugreifen
We can use random integer generation to get a random element from an array, or use the Array.pick_random method to do it for us:
var _fruits = ["apple", "orange", "pear", "banana"]
func _ready():
for i in range(100):
# Pick 100 fruits randomly.
print(get_fruit())
for i in range(100):
# Pick 100 fruits randomly, this time using the `Array.pick_random()`
# helper method. This has the same behavior as `get_fruit()`.
print(_fruits.pick_random())
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
// Use Godot's Array type instead of a BCL type so we can use `PickRandom()` on it.
private Godot.Collections.Array<string> _fruits = ["apple", "orange", "pear", "banana"];
public override void _Ready()
{
for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly.
GD.Print(GetFruit());
}
for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly, this time using the `Array.PickRandom()`
// helper method. This has the same behavior as `GetFruit()`.
GD.Print(_fruits.PickRandom());
}
}
public string GetFruit()
{
string randomFruit = _fruits[GD.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 randomFruit;
}
To prevent the same fruit from being picked more than once in a row, we can add more logic to the above method. In this case, we can't use Array.pick_random since it lacks a way to prevent repetition:
var _fruits = ["apple", "orange", "pear", "banana"]
var _last_fruit = ""
func _ready():
# 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()
{
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;
}
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 Shuffle-Bag-Pattern.
Abrufen eines zufälligen Dictionary-Werts
Wir können eine ähnliche Logik der Arrays auch auf Dictionarys anwenden:
var _metals = {
"copper": {"quantity": 50, "price": 50},
"silver": {"quantity": 20, "price": 150},
"gold": {"quantity": 3, "price": 500},
}
func _ready():
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
private Godot.Collections.Dictionary<string, Godot.Collections.Dictionary<string, int>> _metals = new()
{
{"copper", new Godot.Collections.Dictionary<string, int>{{"quantity", 50}, {"price", 50}}},
{"silver", new Godot.Collections.Dictionary<string, int>{{"quantity", 20}, {"price", 150}}},
{"gold", new Godot.Collections.Dictionary<string, int>{{"quantity", 3}, {"price", 500}}},
};
public override void _Ready()
{
for (int i = 0; i < 20; i++)
{
GD.Print(GetMetal());
}
}
public Godot.Collections.Dictionary<string, int> GetMetal()
{
var (_, randomMetal) = _metals.ElementAt((int)(GD.Randi() % _metals.Count));
// Returns a random metal value dictionary every time the code runs.
// The same metal may be selected multiple times in succession.
return randomMetal;
}
Gewichtete zufällige Wahrscheinlichkeit
Die Methode randf() gibt eine Float-Zahl 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():
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()
{
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";
}
}
You can also get a weighted random index using the
rand_weighted() method
on a RandomNumberGenerator instance. This returns a random integer
between 0 and the size of the array that is passed as a parameter. Each value in the
array is a floating-point number that represents the relative likelihood that it
will be returned as an index. A higher value means the value is more likely to be
returned as an index, while a value of 0 means it will never be returned as an index.
For example, if [0.5, 1, 1, 2] is passed as a parameter, then the method is twice
as likely to return 3 (the index of the value 2) and twice as unlikely to return
0 (the index of the value 0.5) compared to the indices 1 and 2.
Since the returned value matches the array's size, it can be used as an index to get a value from another array as follows:
# Prints a random element using the weighted index that is returned by `rand_weighted()`.
# Here, "apple" will be returned twice as rarely as "orange" and "pear".
# "banana" is twice as common as "orange" and "pear", and four times as common as "apple".
var fruits = ["apple", "orange", "pear", "banana"]
var probabilities = [0.5, 1, 1, 2];
var random = RandomNumberGenerator.new()
print(fruits[random.rand_weighted(probabilities)])
// Prints a random element using the weighted index that is returned by `RandWeighted()`.
// Here, "apple" will be returned twice as rarely as "orange" and "pear".
// "banana" is twice as common as "orange" and "pear", and four times as common as "apple".
string[] fruits = ["apple", "orange", "pear", "banana"];
float[] probabilities = [0.5f, 1, 1, 2];
var random = new RandomNumberGenerator();
GD.Print(fruits[random.RandWeighted(probabilities)]);
"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.
You can accomplish this using the shuffle bag pattern. It works by removing an element from the array after choosing it. After multiple selections, the array ends up empty. When that happens, you reinitialize it to its default value:
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():
_fruits_full = _fruits.duplicate()
_fruits.shuffle()
for i in 100:
print(get_fruit())
func get_fruit():
if _fruits.is_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()
# Returns "apple", "orange", "pear", or "banana" every time the code runs, removing it from the array.
# When all fruit are removed, it refills the array.
return random_fruit
private Godot.Collections.Array<string> _fruits = ["apple", "orange", "pear", "banana"];
// A copy of the fruits array so we can restore the original value into `fruits`.
private Godot.Collections.Array<string> _fruitsFull;
public override void _Ready()
{
_fruitsFull = _fruits.Duplicate();
_fruits.Shuffle();
for (int i = 0; i < 100; i++)
{
GD.Print(GetFruit());
}
}
public string GetFruit()
{
if(_fruits.Count == 0)
{
// Fill the fruits array again and shuffle it.
_fruits = _fruitsFull.Duplicate();
_fruits.Shuffle();
}
// Get a random fruit, since we shuffled the array,
string randomFruit = _fruits[0];
// and remove it from the `_fruits` array.
_fruits.RemoveAt(0);
// Returns "apple", "orange", "pear", or "banana" every time the code runs, removing it from the array.
// When all fruit are removed, it refills the array.
return randomFruit;
}
Bei der Ausführung des obigen Codes besteht die Möglichkeit, zweimal hintereinander die gleiche Frucht zu erhalten. Sobald wir eine Frucht gewählt 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 Ursprungswert 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 in der prozeduralen Generierung beliebt, um realistisch aussehendes Terrain zu erzeugen. Godot bietet hierfür FastNoiseLite, die 1D, 2D und 3D Rauschen unterstützt. Hier ist ein Beispiel mit 1D-Rauschen:
var _noise = FastNoiseLite.new()
func _ready():
# 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))
private FastNoiseLite _noise = new FastNoiseLite();
public override void _Ready()
{
// Configure the FastNoiseLite instance.
_noise.NoiseType = FastNoiseLite.NoiseTypeEnum.SimplexSmooth;
_noise.Seed = (int)GD.Randi();
_noise.FractalOctaves = 4;
_noise.Frequency = 1.0f / 20.0f;
for (int i = 0; i < 100; i++)
{
GD.Print(_noise.GetNoise1D(i));
}
}
Kryptografisch sichere Pseudozufallszahlengenerierung
Bislang sind die oben genannten Ansätze nicht für die kryptografisch sichere Pseudozufallszahlengenerierung (CSPRNG) geeignet. Für Spiele ist das in Ordnung, aber für Szenarien, bei denen es um Verschlüsselung, Authentifizierung oder Signierung geht, ist das nicht ausreichend.
Godot bietet hierfür die Klasse Crypto. Diese Klasse unterstützt asymmetrische Ver- und entschlüsselung, Signieren und Verifizieren und außerdem die Erzeugung von kryptographisch sicheren Zufallsbytes, RSA-Schlüssel, HMAC-Digests und selbstsignierte X509Certificates.
Der Nachteil von CSPRNG ist, dass es viel langsamer ist als die Standard-Pseudozufallszahlengenerierung. Seine API ist auch weniger bequem zu benutzen. Aus diesem Grund sollte CSPRNG für Spielelemente vermieden werden.
Beispiel für die Verwendung der Crypto-Klasse zur Erzeugung von 2 zufälligen Integer-Zaheln zwischen 0 und 2^32 - 1 (einschließlich):
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)
Siehe auch
Siehe die Dokumentation von PackedByteArray für andere Methoden, die Sie verwenden können, um die erzeugten Bytes in verschiedene Datentypen zu dekodieren, wie z.B. Integer oder Floats.