Add C# code examples (salvage of VinnNo's PRs)

This commit is contained in:
Aaron Franke
2022-04-29 08:08:09 -05:00
parent c1b7505ef8
commit da14b8000d
7 changed files with 434 additions and 97 deletions
+85 -15
View File
@@ -31,8 +31,16 @@ change the value of ``t`` from 0 to 1.
.. code-tab:: gdscript GDScript
func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float):
var q0 = p0.linear_interpolate(p1, t)
var q1 = p1.linear_interpolate(p2, t)
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
.. code-tab:: csharp
private Vector2 QuadraticBezier(Vector2 p0, Vector2 p1, Vector2 p2, float t)
{
Vector2 q0 = p0.Lerp(p1, t);
Vector2 q1 = p1.Lerp(p2, t);
}
We then interpolate ``q0`` and ``q1`` to obtain a single point ``r`` that moves
along a curve.
@@ -40,9 +48,14 @@ along a curve.
.. tabs::
.. code-tab:: gdscript GDScript
var r = q0.linear_interpolate(q1, t)
var r = q0.lerp(q1, t)
return r
.. code-tab:: csharp
Vector2 r = q0.Lerp(q1, t);
return r;
This type of curve is called a *Quadratic Bezier* curve.
.. image:: img/bezier_quadratic_points2.gif
@@ -65,48 +78,86 @@ We first use a function with four parameters to take four points as an input,
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
.. code-tab:: csharp
public Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
}
We apply a linear interpolation to each couple of points to reduce them to
three:
.. tabs::
.. code-tab:: gdscript GDScript
var q0 = p0.linear_interpolate(p1, t)
var q1 = p1.linear_interpolate(p2, t)
var q2 = p2.linear_interpolate(p3, t)
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
var q2 = p2.lerp(p3, t)
.. code-tab:: csharp
Vector2 q0 = p0.Lerp(p1, t);
Vector2 q1 = p1.Lerp(p2, t);
Vector2 q2 = p2.Lerp(p3, t);
We then take our three points and reduce them to two:
.. tabs::
.. code-tab:: gdscript GDScript
var r0 = q0.linear_interpolate(q1, t)
var r1 = q1.linear_interpolate(q2, t)
var r0 = q0.lerp(q1, t)
var r1 = q1.lerp(q2, t)
.. code-tab:: csharp
Vector2 r0 = q0.Lerp(q1, t);
Vector2 r1 = q1.Lerp(q2, t);
And to one:
.. tabs::
.. code-tab:: gdscript GDScript
var s = r0.linear_interpolate(r1, t)
var s = r0.lerp(r1, t)
return s
.. code-tab:: csharp
Vector2 s = r0.Lerp(r1, t);
return s;
Here is the full function:
.. tabs::
.. code-tab:: gdscript GDScript
func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
var q0 = p0.linear_interpolate(p1, t)
var q1 = p1.linear_interpolate(p2, t)
var q2 = p2.linear_interpolate(p3, t)
var q0 = p0.lerp(p1, t)
var q1 = p1.lerp(p2, t)
var q2 = p2.lerp(p3, t)
var r0 = q0.linear_interpolate(q1, t)
var r1 = q1.linear_interpolate(q2, t)
var r0 = q0.lerp(q1, t)
var r1 = q1.lerp(q2, t)
var s = r0.linear_interpolate(r1, t)
var s = r0.lerp(r1, t)
return s
.. code-tab:: csharp
private Vector2 CubicBezier(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t)
{
Vector2 q0 = p0.Lerp(p1, t);
Vector2 q1 = p1.Lerp(p2, t);
Vector2 q2 = p2.Lerp(p3, t);
Vector2 r0 = q0.Lerp(q1, t);
Vector2 r1 = q1.Lerp(q2, t);
Vector2 s = r0.Lerp(r1, t);
return s;
}
The result will be a smooth curve interpolating between all four points:
.. image:: img/bezier_cubic_points.gif
@@ -164,6 +215,15 @@ Let's do an example with the following pseudocode:
t += delta
position = _cubic_bezier(p0, p1, p2, p3, t)
.. code-tab:: csharp
private float _t = 0.0f;
public override void _Process(float delta)
{
_t += delta;
Position = CubicBezier(p0, p1, p2, p3, _t);
}
.. image:: img/bezier_interpolation_speed.gif
@@ -204,6 +264,16 @@ Traversal at constant speed, then, can be done with the following pseudo-code:
t += delta
position = curve.interpolate_baked(t * curve.get_baked_length(), true)
.. code-tab:: csharp
private float _t = 0.0f;
public override void _Process(float delta)
{
_t += delta;
Position = curve.InterpolateBaked(_t * curve.GetBakedLength(), true);
}
And the output will, then, move at constant speed:
.. image:: img/bezier_interpolation_baked.gif
+50 -7
View File
@@ -11,15 +11,13 @@ For example, if ``t`` is 0, then the state is A. If ``t`` is 1, then the state i
Between two real (floating-point) numbers, an interpolation can be described as:
.. tabs::
.. code-tab:: gdscript GDScript
::
interpolation = A * (1 - t) + B * t
And often simplified to:
.. tabs::
.. code-tab:: gdscript GDScript
::
interpolation = A + (B - A) * t
@@ -31,7 +29,7 @@ Vector interpolation
--------------------
Vector types (:ref:`Vector2 <class_Vector2>` and :ref:`Vector3 <class_Vector3>`) can also be interpolated, they come with handy functions to do it
:ref:`Vector2.linear_interpolate() <class_Vector2_method_linear_interpolate>` and :ref:`Vector3.linear_interpolate() <class_Vector3_method_linear_interpolate>`.
:ref:`Vector2.lerp() <class_Vector2_method_lerp>` and :ref:`Vector3.lerp() <class_Vector3_method_lerp>`.
For cubic interpolation, there are also :ref:`Vector2.cubic_interpolate() <class_Vector2_method_cubic_interpolate>` and :ref:`Vector3.cubic_interpolate() <class_Vector3_method_cubic_interpolate>`, which do a :ref:`Bezier <doc_beziers_and_curves>` style interpolation.
@@ -40,10 +38,27 @@ Here is example pseudo-code for going from point A to B using interpolation:
.. tabs::
.. code-tab:: gdscript GDScript
var t = 0.0
func _physics_process(delta):
t += delta * 0.4
$Sprite2D.position = $A.position.linear_interpolate($B.position, t)
$Sprite2D.position = $A.position.lerp($B.position, t)
.. code-tab:: csharp
private float _t = 0.0f;
public override void _PhysicsProcess(float delta)
{
_t += delta * 0.4f;
Position2D a = GetNode<Position2D>("A");
Position2D b = GetNode<Position2D>("B");
Sprite2D sprite = GetNode<Sprite2D>("Sprite2D");
sprite.Position = a.Position.Lerp(b.Position, _t);
}
It will produce the following motion:
@@ -71,6 +86,21 @@ Using the following pseudocode:
$Monkey.transform = $Position1.transform.interpolate_with($Position2.transform, t)
.. code-tab:: csharp
private float _t = 0.0f;
public override void _PhysicsProcess(float delta)
{
_t += delta;
Position3D p1 = GetNode<Position3D>("Position1");
Position3D p2 = GetNode<Position3D>("Position2");
CSGMesh monkey = GetNode<CSGMesh>("Monkey");
monkey.Transform = p1.Transform.InterpolateWith(p2.Transform, _t);
}
And again, it will produce the following motion:
.. image:: img/interpolation_monkey.gif
@@ -89,7 +119,20 @@ Interpolation can be used to smooth movement, rotation, etc. Here is an example
func _physics_process(delta):
var mouse_pos = get_local_mouse_position()
$Sprite2D.position = $Sprite2D.position.linear_interpolate(mouse_pos, delta * FOLLOW_SPEED)
$Sprite2D.position = $Sprite2D.position.lerp(mouse_pos, delta * FOLLOW_SPEED)
.. code-tab:: csharp
private const float FollowSpeed = 4.0f;
public override void _PhysicsProcess(float delta)
{
Vector2 mousePos = GetLocalMousePosition();
Sprite2D sprite = GetNode<Sprite2D>("Sprite2D");
sprite.Position = sprite.Position.Lerp(mousePos, delta * FollowSpeed);
}
Here is how it looks:
+239 -53
View File
@@ -42,25 +42,54 @@ In global scope, you can find a :ref:`randomize()
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::
Putting it in your main scene script's ``_ready()`` method is a good choice:
.. tabs::
.. code-tab:: gdscript GDScript
func _ready():
randomize()
.. code-tab:: csharp
public override void _Ready()
{
GD.Randomize();
}
You can also set a fixed random seed instead using :ref:`seed()
<class_@GDScript_method_seed>`. Doing so will give you *deterministic* results
across runs::
across runs:
.. tabs::
.. code-tab:: gdscript GDScript
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::
.. code-tab:: csharp
var rng = RandomNumberGenerator.new()
rng.randomize()
public override void _Ready()
{
GD.Seed(12345);
GD.Seed("Hello world".Hash());
}
When using the RandomNumberGenerator class, you should call ``randomize()`` on
the instance since it has its own seed:
.. tabs::
.. code-tab:: gdscript GDScript
var random = RandomNumberGenerator.new()
random.randomize()
.. code-tab:: csharp
var random = new RandomNumberGenerator();
random.Randomize();
Getting a random number
-----------------------
@@ -71,7 +100,10 @@ random numbers in Godot.
The function :ref:`randi() <class_@GDScript_method_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::
denominator:
.. tabs::
.. code-tab:: gdscript GDScript
# Prints a random integer between 0 and 49.
print(randi() % 50)
@@ -79,6 +111,14 @@ denominator::
# Prints a random integer between 10 and 60.
print(randi() % 51 + 10)
.. code-tab:: csharp
// 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);
:ref:`randf() <class_@GDScript_method_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
@@ -87,83 +127,169 @@ other things.
:ref:`randfn() <class_RandomNumberGenerator_method_randfn>` returns a random
floating-point number following a `normal distribution
<https://en.wikipedia.org/wiki/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)::
value is more likely to be around the mean (0.0 by default),
varying by the deviation (1.0 by default):
.. tabs::
.. code-tab:: gdscript GDScript
# Prints a random floating-point number from a normal distribution with a mean 0.0 and deviation 1.0.
var rng = RandomNumberGenerator.new()
rng.randomize()
print(rng.randfn())
var random = RandomNumberGenerator.new()
random.randomize()
print(random.randfn())
.. code-tab:: csharp
// Prints a normally distributed floating-point number between 0.0 and 1.0.
var random = new RandomNumberGenerator();
random.Randomize();
GD.Print(random.Randfn());
:ref:`rand_range() <class_@GDScript_method_rand_range>` takes two arguments
``from`` and ``to``, and returns a random floating-point number between ``from``
and ``to``::
and ``to``:
.. tabs::
.. code-tab:: gdscript GDScript
# Prints a random floating-point number between -4 and 6.5.
print(rand_range(-4, 6.5))
.. code-tab:: csharp
// Prints a random floating-point number between -4 and 6.5.
GD.Print(GD.RandRange(-4, 6.5));
:ref:`RandomNumberGenerator.randi_range()
<class_RandomNumberGenerator_method_randi_range>` takes two arguments ``from``
and ``to``, and returns a random integer between ``from`` and ``to``::
and ``to``, and returns a random integer between ``from`` and ``to``:
.. tabs::
.. code-tab:: gdscript GDScript
# Prints a random integer between -10 and 10.
var rng = RandomNumberGenerator.new()
rng.randomize()
print(rng.randi_range(-10, 10))
var random = RandomNumberGenerator.new()
random.randomize()
print(random.randi_range(-10, 10))
.. code-tab:: csharp
# Prints a random integer number between -10 and 10.
random.Randomize();
GD.Print(random.RandiRange(-10, 10));
Get a random array element
--------------------------
We can use random integer generation to get a random element from an array::
We can use random integer generation to get a random element from an array:
var fruits = ["apple", "orange", "pear", "banana"]
.. tabs::
.. code-tab:: gdscript GDScript
var _fruits = ["apple", "orange", "pear", "banana"]
func _ready():
randomize()
for i in 100:
for i in range(100):
# Pick 100 fruits randomly.
# (``for i in 100`` is a faster shorthand for ``for i in range(100)``.)
print(get_fruit())
func get_fruit():
var random_fruit = fruits[randi() % fruits.size()]
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
To prevent the same fruit from being picked more than once in a row, we can add
more logic to this method::
.. code-tab:: csharp
var fruits = ["apple", "orange", "pear", "banana"]
var last_fruit = ""
private string[] _fruits = { "apple", "orange", "pear", "banana" };
public override void _Ready()
{
GD.Randomize();
for (int i = 0; i < 100; i++)
{
// Pick 100 fruits randomly.
GD.Print(GetFruit());
}
}
public string GetFruit()
{
string randomFruit = _fruits[GD.Randi() % _fruits.Length];
// 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 this method:
.. tabs::
.. code-tab:: gdscript GDScript
var _fruits = ["apple", "orange", "pear", "banana"]
var _last_fruit = ""
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:
for i in range(100):
print(get_fruit())
func get_fruit():
var random_fruit = fruits[randi() % fruits.size()]
while random_fruit == last_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()]
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
# 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
.. code-tab:: csharp
private string[] _fruits = { "apple", "orange", "pear", "banana" };
private string _lastFruit = "";
public override void _Ready()
{
GD.Randomize();
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;
}
This approach can be useful to make random number generation feel less
repetitive. Still, it doesn't prevent results from "ping-ponging" between a
limited set of values. To prevent this, use the :ref:`shuffle bag
@@ -172,7 +298,10 @@ limited set of values. To prevent this, use the :ref:`shuffle bag
Get a random dictionary value
-----------------------------
We can apply similar logic from arrays to dictionaries as well::
We can apply similar logic from arrays to dictionaries as well:
.. tabs::
.. code-tab:: gdscript GDScript
var metals = {
"copper": {"quantity": 50, "price": 50},
@@ -184,7 +313,7 @@ We can apply similar logic from arrays to dictionaries as well::
func _ready():
randomize()
for i in 20:
for i in range(20):
print(get_metal())
@@ -194,7 +323,6 @@ We can apply similar logic from arrays to dictionaries as well::
# The same metal may be selected multiple times in succession.
return random_metal
.. _doc_random_number_generation_weighted_random_probability:
Weighted random probability
@@ -202,12 +330,15 @@ Weighted random probability
The :ref:`randf() <class_@GDScript_method_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::
"weighted" probability where different outcomes have different likelihoods:
.. tabs::
.. code-tab:: gdscript GDScript
func _ready():
randomize()
for i in 100:
for i in range(100):
print(get_item_rarity())
@@ -224,6 +355,39 @@ floating-point number between 0.0 and 1.0. We can use this to create a
# 5% chance of being returned.
return "Rare"
.. code-tab:: csharp
public override void _Ready()
{
GD.Randomize();
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";
}
}
.. _doc_random_number_generation_shuffle_bags:
"Better" randomness using shuffle bags
@@ -238,29 +402,29 @@ 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"]
var _fruits = ["apple", "orange", "pear", "banana"]
# A copy of the fruits array so we can restore the original value into `fruits`.
var fruits_full = []
var _fruits_full = []
func _ready():
randomize()
fruits_full = fruits.duplicate()
fruits.shuffle()
_fruits_full = _fruits.duplicate()
_fruits.shuffle()
for i in 100:
print(get_fruit())
func get_fruit():
if fruits.empty():
if _fruits.empty():
# Fill the fruits array again and shuffle it.
fruits = fruits_full.duplicate()
fruits.shuffle()
_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()
# 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
@@ -279,19 +443,41 @@ time, or anything else.
To achieve this, you can use random *noise* functions. Noise functions are
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::
1D, 2D, 3D, and 4D noise. Here's an example with 1D noise:
var noise = OpenSimplexNoise.new()
.. tabs::
.. code-tab:: gdscript GDScript
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
_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))
print(_noise.get_noise_1d(i))
.. code-tab:: csharp
private OpenSimplexNoise _noise = new OpenSimplexNoise();
public override void _Ready()
{
GD.Randomize();
// Configure the OpenSimplexNoise instance.
_noise.Seed = (int)GD.Randi();
_noise.Octaves = 4;
_noise.Period = 20.0f;
_noise.Persistence = 0.8f;
for (int i = 0; i < 100; i++)
{
GD.Print(_noise.GetNoise1d(i));
}
}