Vektormathematik

Einführung

Diese Anleitung ist eine kurze und praktische Einführung in die lineare Algebra, so wie sie in der Spielentwicklung benötigt wird. Lineare Algebra ist die Lehre von Vektoren und deren Nutzung. Vektoren haben viele Anwendungen in der 2D und 3D Entwicklung und Godot nutzt diese umfangreich. Für einen guten Spielentwickler ist es unverzichtbar ein gutes Verständnis über Vektormathematik zu entwickeln.

Bemerkung

Diese Anleitung ist kein offizielles Lehrbuch über lineare Algebra, wir werden hier nur auf deren Anwendung bei der Spiele Entwicklung eingehen. Für einen umfassenden Blick auf diesen Bereich der Mathematik siehe https://www.khanacademy.org/math/linear-algebra

Koordinatensysteme (2D)

Im 2D Raum werden die Koordinaten mittels einer horizontalen (x) und vertikalen (y) Achse definiert. Eine bestimmte Position im 2D Raum wird als Wertepaar geschrieben, z.B. (4, 3).

../../_images/vector_axis1.png

Bemerkung

Es mag ungewöhnlich erscheinen, dass die positive y Achse nach unten zeigt anstatt nach oben, wie man es im Mathematik Unterricht einmal gelernt hat. Dies ist aber normal in den meisten Computer Grafikprogrammen, der Nullpunkt befindet sich links oben.

Jede Position im 2D System kann über ein Nummern-Paar angesprochen werden. Man kann somit die Position (4, 3) als Abstand (Offset) vom Ursprung (0, 0) ansehen. Hier sehen wir einen Pfeil vom Ursprung zum Punkt:

../../_images/vector_xy1.png

Dies ist ein Vektor. Ein Vektor zeigt eine Menge nützlicher Information, sowohl den Punkt auf (4, 3), sowie auch den Winkel θ und die Länge oder Größe m. In diesem Fall wird dieser Pfeil Positionsvektor genannt, er zeigt die relative Position im Raum ausgehend vom Nullpunkt.

Ein wichtiger Punkt über Vektoren ist, dass diese nur relative Richtungen und Größen haben, es gibt keine Position eines Vektors. Die folgenden beiden Vektoren sind identisch:

../../_images/vector_xy2.png

Beide Vektoren zeigen auf einen Punkt 4 Einheiten nach rechts und 3 Einheiten runter. Es spielt keine Rolle wo dieser Vektor gezeichnet wird, er stellt immer nur die relative Richtung und Größe dar.

Vektoroperationen

Es können beide Arten genutzt werden um auf einen Vektor zuzugreifen: X und Y Koordinaten oder Winkel und Größe. Der Einfachheit halber nutzen Programmierer die Koordinaten Bezeichnung. In Godot ist zum Beispiel der Ursprung des Bildschirms links oben, um also ein 2D Node namens Node2D 400 Pixels rechts und 300 Pixels runter zu platzieren, wird folgender Code benötigt:

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

Godot bietet beide Vector2 und Vector3 für die 2D bzw. 3D Nutzung. Die hier besprochenen mathematischen Regeln gelten für beide Arten.

Zugriff auf die Koordinaten des Vektors

Der jeweilige Teil des Vektors kann direkt über den Namen genutzt werden.

# 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;

Vektoren hinzufügen

Wenn zwei Vektoren miteinander addiert oder subtrahiert werden, bezieht sich das auf die zugehörigen Teile:

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

Wir können das auch in der Grafik sehen, hier beginnt der zweite Vektor an Ende des ersten:

../../_images/vector_add1.png

Beachte dass a + b zum gleichen Ergebnis führt wie b + a (Kommutativgesetz).

Skalarmultiplikation

Bemerkung

Vektoren bestehen aus Richtung und Größe. Ist nur die Größe vorhanden, spricht man von einem Skalar.

Ein Vektor kann mit einen Skalar multipliziert werden:

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

Bemerkung

Wird ein Vektor mit einem Skalar multipliziert, ändert sich nicht dessen Richtung, sondern nur seine Größe. Auf diese Weise wird ein Vektor skaliert.

praktische Anwendungen

Sehen wir uns einmal zwei einfache Anwendungen zur Vektor Addition und Subtraktion an.

Bewegung

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

Tipp

Beschleunigung misst die Änderung der Position pro Zeiteinheit. Die neue Position erhält man durch Addition der Beschleunigung zur vorherigen Position.

Auf ein Ziel zeigen

In diesem Beispiel haben wir einen Panzer, dessen Kanone auf einen Roboter zeigen soll. Wenn die Position des Panzers von der des Roboters subtrahiert wird, erhalten wir den vom Panzer zum Roboter zeigenden Vektor.

../../_images/vector_subtract2.png

Tipp

Um einen Vektor zu finden, der von A nach B zeigt, nutze B - A.

Einheitsvektor

Ein Vektor mit einer Größe von 1 wird Einheitsvektor oder normierter Vektor genannt. Einheitsvektoren sind hilfreich wenn die Richtung verfolgt werden muss.

Normalisierung

Normalizing a vector means reducing its length to 1 while preserving its direction. This is done by dividing each of its components by its magnitude. Because this is such a common operation, Vector2 and Vector3 provide a method for normalizing:

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

Warnung

Because normalization involves dividing by the vector's length, you cannot normalize a vector of length 0. Attempting to do so will result in an error.

Zusammenfassend

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.

Als Beispiel nehmen wir einmal an wir haben einen sich bewegenden Ball, der an einer Wand oder anderen Objekten abprallen soll:

../../_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);
}

Skalarprodukt (Punktprodukt)

Das Skalarprodukt ist eines der wichtigsten Konzepte in der Vektormathematik, wird allerdings oft falsch verstanden. Skalarprodukt ist eine Berechnung zweier Vektoren, die als Ergebnis ein Skalar liefert. Anders als ein Vektor, welcher eine Richtung und Größe besitzt, hat ein Skalar nur eine Größe.

Die Formel für das Skalarprodukt hat zwei übliche Formen:

../../_images/vector_dot1.png

und

../../_images/vector_dot2.png

In den meisten Fällen ist es jedoch am einfachsten, die integrierte Methode zu verwenden. Beachten Sie, dass die Reihenfolge der beiden Vektoren keine Rolle spielt:

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.

Das Skalarprodukt ist am nützlichsten, wenn es mit Einheitsvektoren verwendet wird, wodurch die erste Formel auf nur cosθ reduziert wird. Dies bedeutet, dass wir das Skalarprodukt verwenden können, um etwas über den Winkel zwischen zwei Vektoren zu sagen:

../../_images/vector_dot3.png

Bei Verwendung von Einheitsvektoren liegt das Ergebnis immer zwischen -1 (180°) und 1 (0°).

Blickrichtung

Wir können diese Tatsache nutzen, um festzustellen, ob ein Objekt einem anderen Objekt zugewandt ist. In der folgenden Abbildung versucht der Spieler P, den Zombies A und B auszuweichen. Angenommen, das Sichtfeld eines Zombies ist **180° **, können sie den Spieler sehen?

../../_images/vector_facing2.png

Die grünen Pfeile fA` und fB sind Einheitsvektoren, die die Blickrichtungen der Zombies darstellen, und der blaue Halbkreis repräsentiert sein Sichtfeld. Für Zombie A finden wir den Richtungsvektor AP, der mit P - A auf den Spieler zeigt, und normalisieren ihn. Godot hat jedoch eine Hilfsmethode, um dies zu tun, genannt direction_to . Wenn der Winkel zwischen diesem Vektor und dem zugewandten Vektor weniger als 90 ° beträgt, kann der Zombie den Spieler sehen.

Der entsprechende Code sieht so aus:

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

Kreuzprodukt

Wie das Skalarprodukt ist das Kreuzprodukt eine Operation mit zwei Vektoren. Das Ergebnis des Kreuzprodukts ist jedoch ein Vektor mit einer Richtung, die senkrecht zu beiden ist. Ihre Größe hängt von ihrem relativen Winkel ab. Wenn zwei Vektoren parallel sind, ist das Ergebnis ihres Kreuzprodukts ein Nullvektor.

../../_images/vector_cross1.png ../../_images/vector_cross2.png

Das Kreuzprodukt wird wie folgt berechnet:

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

Mit Godot können Sie die integrierte Methode verwenden:

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

Bemerkung

Im Kreuzprodukt ist die Reihenfolge wichtig. a.cross(b) liefert nicht das gleiche Ergebnis wie b.cross(a). Die resultierenden Vektoren zeigen in entgegengesetzte Richtungen.

Normalen ermitteln

Ein typischer Anwendungsfall des Kreuzproduktes ist es die Oberflächennormalen einer Ebenen (Plane) oder eine Oberfläche im 3D-Raum zu ermitteln. Ist das Dreieck ABC gegeben, können wir durch Vektorsubtraktion die zwei Kanten AB und AC bestimmen. Wenden wir darauf die Kreuzproduktoperation an AB x AC so ergibt sich der senkrechte Vektor zu beiden: die Oberflächennormale.

Hier ist eine Funktion zur Bestimmung der Dreiecksnormalen:

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;
}

Auf ein Ziel richten

Im obigen Skalarproduktabschnitt haben wir gesehen, wie damit der Winkel zwischen zwei Vektoren ermittelt werden kann. In 3D reichen diese Informationen jedoch nicht aus. Wir müssen auch wissen, um welche Achse wir uns drehen müssen. Wir können dies feststellen, indem wir das Kreuzprodukt der aktuellen Blickrichtung und der Zielrichtung berechnen. Der resultierende senkrechte Vektor ist die Rotationsachse.

Mehr Informationen

Für weitere Informationen über Vektormathematik in Godot siehe die folgenden Artikel: