diff --git a/tutorials/math/random_number_generation.rst b/tutorials/math/random_number_generation.rst index 620f08e8590..5819f5462e3 100644 --- a/tutorials/math/random_number_generation.rst +++ b/tutorials/math/random_number_generation.rst @@ -3,70 +3,75 @@ Random number generation ======================== -Many games rely on randomness to implement core game mechanics. This tutorial +Many games rely on randomness to implement core game mechanics. This page guides you through common types of randomness and how to implement them in 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. + .. note:: Computers cannot generate "true" random numbers. Instead, they rely on - `psuedorandom number generators `__ - (PRNGs). + `pseudorandom number generators + `__ (PRNGs). Global scope versus RandomNumberGenerator class ----------------------------------------------- -Godot exposes two ways to generate random numbers: via *global scope* methods -or using the :ref:`class_RandomNumberGenerator` class. +Godot exposes two ways to generate random numbers: via *global scope* methods or +using the :ref:`class_RandomNumberGenerator` class. Global scope methods are easier to set up, but they don't offer as much control. RandomNumberGenerator requires more code to use, but exposes many methods not -found in global scope such as -:ref:`randi_range() ` and -:ref:`randfn() `. On top of that, -it allows creating multiple instances each with their own seed. +found in global scope such as :ref:`randi_range() +` and :ref:`randfn() +`. On top of that, it allows creating +multiple instances each with their own seed. -This tutorial uses global scope methods, except when the method is only found in +This tutorial uses global scope methods, except when the method only exists in the RandomNumberGenerator class. The randomize() method ---------------------- -In global scope, you can find a :ref:`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. +In global scope, you can find a :ref:`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. Putting it in your main scene script's ``_ready()`` method is a good choice:: func _ready(): randomize() -You can also set a fixed random seed instead using -:ref:`seed() `. This will give you *deterministic* -results across runs:: +You can also set a fixed random seed instead using :ref:`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()) -When using the RandomNumberGenerator class, you should call ``randomize()`` -on the instance since it has its own seed:: +When using the RandomNumberGenerator class, you should call ``randomize()`` on +the instance since it has its own seed:: var rng = RandomNumberGenerator.new() rng.randomize() -Get a random number -------------------- +Getting a random number +----------------------- -Godot provides several methods to get random numbers. +Let's look at some of the most commonly used functions and methods to generate +random numbers in Godot. -:ref:`randi() ` returns a random number between 0 -and 2^32-1. Since the maximum value is really high, you most likely want to use -the modulo operator (``%``) to bound the result between 0 and the denominator:: +The function :ref:`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) @@ -74,32 +79,33 @@ the modulo operator (``%``) to bound the result between 0 and the denominator:: # Prints a random integer between 10 and 60. print(randi() % 51 + 10) -:ref:`randf() ` returns a random floating-point number -between 0 and 1. This is useful to implement a +:ref:`randf() ` returns a random floating-point +number between 0 and 1. This is useful to implement a :ref:`doc_random_number_generation_weighted_random_probability` system, among other things. -:ref:`randfn() ` returns a random floating-point -number between 0 and 1. Unlike :ref:`randf() ` which follows -an uniform distribution, the returned number follows a -`normal distribution `__. -This means the returned value is more likely to be around 0.5 compared to the -extreme bounds (0 and 1):: +:ref:`randfn() ` returns a random +floating-point number between 0 and 1. Unlike :ref:`randf() +` which follows an uniform distribution, the +returned number follows a `normal distribution +`__. This means the returned +value is more likely to be around 0.5 compared to the extreme bounds (0 and 1):: # Prints a normally distributed floating-point number between 0.0 and 1.0. var rng = RandomNumberGenerator.new() rng.randomize() print(rng.randfn()) -:ref:`rand_range() ` takes two arguments ``from`` and -``to``, and returns a random floating-point number between ``from`` and ``to``:: +:ref:`rand_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(rand_range(-4, 6.5)) -:ref:`RandomNumberGenerator.randi_range() ` -takes two arguments ``from`` and ``to``, and returns a random integer between -``from`` and ``to``:: +:ref:`RandomNumberGenerator.randi_range() +` takes two arguments ``from`` +and ``to``, and returns a random integer between ``from`` and ``to``:: # Prints a random floating-point number between -10 and 10. var rng = RandomNumberGenerator.new() @@ -125,12 +131,12 @@ We can use random integer generation to get a random element from an array:: func get_fruit(): var random_fruit = fruits[randi() % fruits.size()] - # Returns "apple", "orange", "pear", or "banana" every time the code is run. - # The same fruit may be selected multiple times in succession. + # 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 -To prevent the same fruit from being picked more than once in a row, we can add more -logic to this method:: +To prevent the same fruit from being picked more than once in a row, we can add +more logic to this method:: var fruits = ["apple", "orange", "pear", "banana"] var last_fruit = "" @@ -139,9 +145,9 @@ logic to this method:: func _ready(): randomize() + # Pick 100 fruits randomly. + # Note: ``for i in 100`` is a shorthand for ``for i in range(100)``. for i in 100: - # Pick 100 fruits randomly. - # (``for i in 100`` is a faster shorthand for ``for i in range(100)``.) print(get_fruit()) @@ -151,19 +157,19 @@ logic to this method:: # 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), + # 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 is run. - # The same fruit will never be returned more than once in a row. + # 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 This approach can be useful to make random number generation feel less -repetitive, but it doesn't prevent results from "ping-ponging" between a limited -set of values. To prevent this, use the -:ref:`shuffle bag ` pattern instead. +repetitive. Still, it doesn't prevent results from "ping-ponging" between a +limited set of values. To prevent this, use the :ref:`shuffle bag +` pattern instead. Get a random dictionary value ----------------------------- @@ -186,7 +192,7 @@ We can apply similar logic from arrays to dictionaries as well:: func get_metal(): var random_metal = metals.values()[randi() % metals.size()] - # Returns a random metal value dictionary every time the code is run. + # Returns a random metal value dictionary every time the code runs. # The same metal may be selected multiple times in succession. return random_metal @@ -196,9 +202,9 @@ We can apply similar logic from arrays to dictionaries as well:: Weighted random probability --------------------------- -The :ref:`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:: +The :ref:`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() @@ -225,15 +231,14 @@ different outcomes have different likelihoods:: "Better" randomness using shuffle bags -------------------------------------- -Taking the same exemple as above, we would like to pick fruits at random. +Taking the same example as above, we would like to pick fruits at random. However, relying on random number generation every time a fruit is selected can lead to a less *uniform* distribution. If the player is lucky (or unlucky), they -could get the same fruit 3 or more times in a row. +could get the same fruit three or more times in a row. -This can be accomplished by using the *shuffle bag* pattern. It works by -removing the element from the array once it has been chosen. If this is done -multiple times, the array might end up being empty. In this case, its value is -reinitialized to its default state where it's full:: +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`. @@ -255,33 +260,31 @@ reinitialized to its default state where it's full:: fruits = fruits_full.duplicate() fruits.shuffle() - # Get a random fruit (since the array has been suffled) + # 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 is run. + # Prints "apple", "orange", "pear", or "banana" every time the code runs. return random_fruit -When running the above code, the same fruit will *never* be picked more than -twice in a row. This is because once a fruit has been picked, it will no longer -be a possible return value unless the array is now empty. When the array is -empty, we reset it back to its full state, which makes it possible to have the -same fruit again (but only once). +When running the above code, there is a chance to get the same fruit twice in a +row. Once we picked a fruit, it will no longer be a possible return value unless +the array is now empty. When the array is empty, we reset it back to its default +value, making it possible to have the same fruit again, but only once. Random noise ------------ The random number generation shown above can show its limits when you need a -value that *slowly* changes depending on the input. (The input can be a -position, time, or anything else.) +value that *slowly* changes depending on the input. The input can be a position, +time, or anything else. To achieve this, you can use random *noise* functions. Noise functions are -especially poopular in producedural generation to generate realistic-looking +especially popular in procedural generation to generate realistic-looking terrain. Godot provides :ref:`class_opensimplexnoise` for this, which supports 1D, 2D, 3D, and 4D noise. Here's an example with 1D noise:: var noise = OpenSimplexNoise.new() - func _ready(): randomize() # Configure the OpenSimplexNoise instance.