Wektory

Wprowadzenie

Ten samouczek jest krótkim i praktycznym wprowadzeniem do algebry liniowej w zakresie tworzenia gier. Algebra liniowa jest badaniem wektorów i ich zastosowań. Wektory mają wiele zastosowań zarówno w 2D, jak i 3D, a Godot używa ich w szerokim zakresie. Dobre zrozumienie matematyki wektorowej jest niezbędne, aby stać się potężnym twórcą gier.

Informacja

Ten samouczek nie jest formalnym podręcznikiem na temat algebry liniowej. Będziemy się przyglądać tylko temu, w jaki sposób jest ona stosowany do tworzenia gier. Szersze spojrzenie ten poddział matematyki można znaleźć na stronie https://www.khanacademy.org/math/linear-algebra

Układy współrzędnych (2D)

W przestrzeni 2D współrzędne są definiowane za pomocą osi poziomej ( x) i osi pionowej ( y). Położenie w przestrzeni 2D jest zapisywane jako para wartości takich jak (4, 3).

../../_images/vector_axis1.png

Informacja

Jeśli jesteś nowicjuszem w grafice komputerowej, może wydawać się dziwne, że dodatnie wartości osi y są skierowane w dół zamiast w górę, jak prawdopodobnie uczyliście się na lekcji matematyki. Jest to jednak powszechne w większości aplikacji graficznych.

W ten sposób można zidentyfikować dowolne położenie w płaszczyźnie 2D za pomocą pary liczb. Jednakże możemy również myśleć o pozycji (4, 3) jako przesunięcie od punktu (0, 0) lub źródło. Narysuj strzałkę wskazującą od początku do końca:

../../_images/vector_xy1.png

Jest to wektor. Wektor reprezentuje wiele przydatnych informacji. A także mówiąc nam, że punkt jest w (4, 3) ``, możemy również myśleć o nim jako o kącie ``θ i długości (lub wielkości) m. W tym przypadku strzałka jest wektorem pozycji - oznacza pozycję w przestrzeni względem początku.

Bardzo ważną kwestią, którą należy wziąć pod uwagę w przypadku wektorów, jest to, że reprezentują one jedynie względny kierunek i wielkość. Nie istnieje pojęcie pozycji wektora. Następujące dwa wektory są identyczne:

../../_images/vector_xy2.png

Oba wektory reprezentują punkt to samo, 4 jednostki w prawą stronę i 3 jednostki w dół od pewnego punktu początkowego. Nie ma znaczenia, gdzie na płaszczyźnie rysujesz wektor, zawsze reprezentuje on względny kierunek i wielkość.

Vector operations

You can use either method (x and y coordinates or angle and magnitude) to refer to a vector, but for convenience, programmers typically use the coordinate notation. For example, in Godot, the origin is the top-left corner of the screen, so to place a 2D node named Node2D 400 pixels to the right and 300 pixels down, use the following code:

$Node2D.position = Vector2(400, 300)
var node2D = (Node2D) GetNode("Node2D");
node2D.Position = new Vector2(400, 300);

Godot supports both Vector2 and Vector3 for 2D and 3D usage, respectively. The same mathematical rules discussed in this article apply to both types.

  • Member access

Poszczególne komponenty wektora są dostępne bezpośrednio po nazwie.

# create a vector with coordinates (2, 5)
var a = Vector2(2, 5)
# create a vector and assign x and y manually
var b = Vector2()
b.x = 3
b.y = 1
// create a vector with coordinates (2, 5)
var a = new Vector2(2, 5);
// create a vector and assign x and y manually
var b = new Vector2();
b.x = 3;
b.y = 1;
  • Dodawanie wektorów

Podczas dodawania lub odejmowania dwóch wektorów dodawane są odpowiednie składniki:

var c = a + b  # (2, 5) + (3, 1) = (5, 6)
var c = a + b;  // (2, 5) + (3, 1) = (5, 6)

Widzimy to również wizualnie, dodając drugi wektor na końcu pierwszego:

../../_images/vector_add1.png

Należy pamiętać, że dodanie a + b daje taki sam rezultat jak b + a.

  • Mnożenie skalarne

Informacja

Vectors represent both direction and magnitude. A value representing only magnitude is called a scalar.

Wektor można pomnożyć przez skalar:

var c = a * 2  # (2, 5) * 2 = (4, 10)
var d = b / 3  # (3, 6) / 3 = (1, 2)
var c = a * 2;  // (2, 5) * 2 = (4, 10)
var d = b / 3;  // (3, 6) / 3 = (1, 2)
../../_images/vector_mult1.png

Informacja

Mnożenie wektora przez skalar nie zmienia jego kierunku, a jedynie wielkość. W ten sposób można skalować wektor.

Praktyczne zastosowania

Przyjrzyjmy się dwóm powszechnym zastosowaniom dodawania i odejmowania wektorów.

  • Ruch

A vector can represent any quantity with a magnitude and direction. Typical examples are: position, velocity, acceleration, and force. In this image, the spaceship at step 1 has a position vector of (1,3) and a velocity vector of (2,1). The velocity vector represents how far the ship moves each step. We can find the position for step 2 by adding the velocity to the current position.

../../_images/vector_movement1.png

Wskazówka

Velocity measures the change in position per unit of time. The new position is found by adding velocity to the previous position.

  • Pointing toward a target

In this scenario, you have a tank that wishes to point its turret at a robot. Subtracting the tank’s position from the robot’s position gives the vector pointing from the tank to the robot.

../../_images/vector_subtract2.png

Wskazówka

Aby znaleźć wektor z punktu A do B użyj B - A.

Unit vectors

A vector with magnitude of 1 is called a unit vector. They are also sometimes referred to as direction vectors or normals. Unit vectors are helpful when you need to keep track of a direction.

Normalizacja

Normalizacja wektora oznacza zmniejszenie jego długości do 1 przy jednoczesnym zachowaniu jego kierunku. Odbywa się to poprzez podzielenie każdego z jego komponentów przez jego wielkość:

var a = Vector2(2, 4)
var m = sqrt(a.x*a.x + a.y*a.y)  # get magnitude "m" using the Pythagorean theorem
a.x /= m
a.y /= m
var a = new Vector2(2, 4);
var m = Mathf.Sqrt(a.x*a.x + a.y*a.y);  // get magnitude "m" using the Pythagorean theorem
a.x /= m;
a.y /= m;

Ponieważ jest to taka powszechna operacja, Vector2 i Vector3 zapewniają tę metodę normalizacji:

a = a.normalized()
a = a.Normalized();

Ostrzeżenie

Ponieważ normalizacja polega na dzieleniu przez długość wektora, nie można normalizować wektora o długości 0. Próba zrobienia tego spowoduje błąd.

Odbicie

A common use of unit vectors is to indicate normals. Normal vectors are unit vectors aligned perpendicularly to a surface, defining its direction. They are commonly used for lighting, collisions, and other operations involving surfaces.

Wyobraźmy sobie na przykład, że mamy ruchomą kulę, którą chcemy odbić od ściany lub innego obiektu:

../../_images/vector_reflect1.png

The surface normal has a value of (0, -1) because this is a horizontal surface. When the ball collides, we take its remaining motion (the amount left over when it hits the surface) and reflect it using the normal. In Godot, the Vector2 class has a bounce() method to handle this. Here is a GDScript example of the diagram above using a KinematicBody2D:

# object "collision" contains information about the collision
var collision = move_and_collide(velocity * delta)
if collision:
    var reflect = collision.remainder.bounce(collision.normal)
    velocity = velocity.bounce(collision.normal)
    move_and_collide(reflect)
// KinematicCollision2D contains information about the collision
KinematicCollision2D collision = MoveAndCollide(_velocity * delta);
if (collision != null)
{
    var reflect = collision.Remainder.Bounce(collision.Normal);
    _velocity = _velocity.Bounce(collision.Normal);
    MoveAndCollide(reflect);
}

Iloczyn skalarny

Wartość skalarna jest jednym z najważniejszych pojęć w matematyce wektorowej, ale często jest źle rozumiany. Wartość skalarna to operacja na dwóch wektorach, która zwraca skalar. W przeciwieństwie do wektora, który zawiera zarówno wielkość, jak i kierunek, wartość skalarna ma tylko wielkość.

The formula for dot product takes two common forms:

A \cdot B = \left \| A \right \|\left \| B \right \|\cos \Theta

i

A \cdot B = A_{x}B_{x} + A_{y}B_{y}

Jednak w większości przypadków najłatwiej jest użyć metody wbudowanej. Kolejność dwóch wektorów nie ma znaczenia:

var c = a.dot(b)
var d = b.dot(a)  # these are equivalent
float c = a.Dot(b);
float d = b.Dot(a);  // these are equivalent

The dot product is most useful when used with unit vectors, making the first formula reduce to just cosθ. This means we can use the dot product to tell us something about the angle between two vectors:

../../_images/vector_dot3.png

Przy zastosowaniu wektorów jednostkowych wynik zawsze będzie pomiędzy -1 (180°) a 1 (0°).

Facing

We can use this fact to detect whether an object is facing toward another object. In the diagram below, the player P is trying to avoid the zombies A and B. Assuming a zombie’s field of view is 180°, can they see the player?

../../_images/vector_facing2.png

The green arrows fA and fB are unit vectors representing the zombies» facing directions and the blue semicircle represents its field of view. For zombie A, we find the direction vector AP pointing to the player using P - A and normalize it. If the angle between this vector and the facing vector is less than 90°, then the zombie can see the player.

In code it would look like this:

var AP = (P - A).normalized()
if AP.dot(fA) > 0:
    print("A sees P!")
var AP = (P - A).Normalized();
if (AP.Dot(fA) > 0)
{
    GD.Print("A sees P!");
}

Cross product

Like the dot product, the cross product is an operation on two vectors. However, the result of the cross product is a vector with a direction that is perpendicular to both. Its magnitude depends on their relative angle. If two vectors are parallel, the result of their cross product will be a null vector.

\left \|a \times b  \right \| = \left \| a \right \|\left \| b \right \|\ |\sin(a,b)|

../../_images/tutovec16.png

The cross product is calculated like this:

var c = Vector3()
c.x = (a.y * b.z) - (a.z * b.y)
c.y = (a.z * b.x) - (a.x * b.z)
c.z = (a.x * b.y) - (a.y * b.x)
var c = new Vector3();
c.x = (a.y * b.z) - (a.z * b.y);
c.y = (a.z * b.x) - (a.x * b.z);
c.z = (a.x * b.y) - (a.y * b.x);

With Godot, you can use the built-in method:

var c = a.cross(b)
var c = a.Cross(b);

Informacja

In the cross product, order matters. a.cross(b) does not give the same result as b.cross(a). The resulting vectors point in opposite directions.

Calculating normals

One common use of cross products is to find the surface normal of a plane or surface in 3D space. If we have the triangle ABC we can use vector subtraction to find two edges AB and AC. Using the cross product, AB x AC produces a vector perpendicular to both: the surface normal.

Here is a function to calculate a triangle’s normal:

func get_triangle_normal(a, b, c):
    # find the surface normal given 3 vertices
    var side1 = b - a
    var side2 = c - a
    var normal = side1.cross(side2)
    return normal
Vector3 GetTriangleNormal(Vector3 a, Vector3 b, Vector3 c)
{
    // find the surface normal given 3 vertices
    var side1 = b - a;
    var side2 = c - a;
    var normal = side1.Cross(side2);
    return normal;
}

Pointing to a target

In the dot product section above, we saw how it could be used to find the angle between two vectors. However, in 3D, this is not enough information. We also need to know what axis to rotate around. We can find that by calculating the cross product of the current facing direction and the target direction. The resulting perpendicular vector is the axis of rotation.

More information

Więcej informacji na temat używania matematyki wektorowej w Godocie można znaleźć w poniższych artykułach: