Kapitel 4. Mathematik

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Mathematik ist ein unvermeidlicher Teil der Arbeit mit einem räumlichen Entwicklungswerkzeug wie Unity. Ob du sie magst oder verabscheust, du musst sie nutzen, anerkennen und sogar regelrecht annehmen, um etwas zu erreichen. Zum Glück kannst du vieles davon überspielen oder so tun, als ob es nicht da wäre, aber es ist immer da, unterstützt dich und lauert im Verborgenen.

In diesem Kapitel geht es um einige der häufigsten mathematischen Probleme, auf die du bei der Entwicklung mit Unity stoßen wirst.

Hinweis

Das ist nicht die gesamte Mathematik, die du für die Entwicklung von Spielen oder Unity brauchst - im Gegenteil! Aber es ist genug Mathe, um dir zu helfen und dir den Einstieg zu erleichtern. Wir haben unser Bestes getan, um uns an den Problemen zu orientieren, die du mit Hilfe der Mathematik lösen möchtest, und nicht an der Mathematik selbst.

Hast du dich jemals gefragt, was ein Vektor ist? Oder wie man eine Matrix benutzt? Was ist mit einer Quaternion? Wir werden sie hier behandeln und dabei auch auf häufige mathematische Probleme in der Unity-Entwicklung eingehen, wie z. B. die Berechnung von Winkeln und Abständen zu anderen Objekten in deinem Unity-Projekt.

Tipp

Nichts in diesem Kapitel ist einzigartig oder speziell für die Entwicklung mit Unity. Wenn wir über Code sprechen, behandeln wir die Dinge aus einer Unity-zentrierten Perspektive, aber alles hier ist einfach nur Mathematik für die Raum- oder Spieleentwicklung.

Lies weiter, um gerade genug Mathe für die Unity-Entwicklung zu lernen.

Tipp

Wir können Paco Nathans Just Enough Math-Videoserie, ebenfalls von O'Reilly, nur wärmstens empfehlen. Schau sie dir an, wenn du noch mehr Mathe lernen willst.

4.1 Speichern von Koordinaten mit unterschiedlichenDimensionen mit Hilfe von Vektoren

Problem

Die Unity-Entwicklung umfasst eine Menge Koordinaten unterschiedlicher Größe:

  • Eindimensionale Koordinaten, wie 1 oder -9

  • Zweidimensionale Koordinaten, wie z. B. 8, 3 oder -1, 4

  • Dreidimensionale Koordinaten, wie z. B. 3, 1, -3 oder -7, 6, 1

Wenn du etwas in Unity baust, spielt es oft keine Rolle, wie du es machst, solange du das im Auge behältst, was du im Auge behalten musst. Eine gängige und nützliche Methode, Koordinaten zu verfolgen, sind Vektoren.

Vektoren sind eine Reihe von Koordinaten mit unterschiedlichen Dimensionen. Unser Problem hier ist also: Wie verwende ich Vektoren und Unity und wofür kann ich sie verwenden?

Lösung

Um hier eine Lösung zu finden, müssen wir das Problem ein wenig auspacken. Wir geben zu, dass wir die Problemstellung absichtlich weit gefasst haben, aber das ist so, damit wir dir alles zeigen können, was du über Vektoren wissen musst, ohne ein winziges Rezept für jede Manipulation oder Aktion zu haben, die du mit einem Vektor durchführen willst.

Tipp

Oft werden Vektoren mit ganz bestimmten Bedeutungen belegt: In Videospielen geht es meist um Koordinaten in der Geometrie, aber es gibt keinen Grund, warum du nicht alles in ihnen speichern kannst, was du willst. Sie sind einfach nur eine Datenstruktur.

Zunächst einmal kannst du in Unity eine Vector2 definieren: eine mit zwei Dimensionen, in der Regel eine x- und eine y-Dimension. Eine Vector2 wird normalerweise verwendet, um einen Punkt im 2D-Raum in Unity darzustellen.

Du kannst eine Vector2 mit zwei Dimensionen definieren:

Vector2 direction = new Vector2(0.0f, 2.0f);

Oder verwende einen der eingebauten Vektoren von Unity:

var up    = Vector2.up;    // ( 0,  1)
var down  = Vector2.down;  // ( 0, -1)
var left  = Vector2.left;  // (-1,  0)
var right = Vector2.right; // ( 1,  0)
var one   = Vector2.one;   // ( 1,  1)
var zero  = Vector2.zero;  // ( 0,  0)

Unity hat auch einen Typ namens Vector3, der ein Vektor mit drei Dimensionen ist. Es gibt mehrere vordefinierte Vektoren, die in der Klasse Vector3 verfügbar sind:

Vector3 point = new Vector3(1.0f, 2f, 3.5f);

var up      = Vector3.up;      // ( 0,  1,  0)
var down    = Vector3.down;    // ( 0, -1,  0)
var left    = Vector3.left;    // (-1,  0,  0)
var right   = Vector3.right;   // ( 1,  0,  0)
var forward = Vector3.forward; // ( 0,  0,  1)
var back    = Vector3.back;    // ( 0,  0, -1)
var one     = Vector3.one;     // ( 1,  1,  1)
var zero    = Vector3.zero;    // ( 0,  0,  0)
Tipp

Du kannst mehr erfahren über Vector2 und Vector3 in der API-Dokumentation von Unity.

Für jede Transform Komponente in Unity sind lokale Richtungsvektoren definiert, die relativ zu ihrer aktuellen Drehung sind. Auf die lokale Vorwärtsrichtung eines Objekts kann zum Beispiel folgendermaßen zugegriffen werden:

var myForward = transform.forward;

Natürlich kannst du mit Vektoren auch einfache arithmetische Berechnungen durchführen. Vektoren können addiert werden:

var v1 = new Vector3(1f, 2f, 3f);
var v2 = new Vector3(0f, 1f, 6f);

var v3 = v1 + v2; // (1, 3, 9)

und subtrahiert von einander:

var v4 = v2 - v1; // (-1, -1, 3)

Du kannst auch den Betrag eines Vektors bestimmen. Der Betrag eines Vektors wird auch als Länge des Vektors bezeichnet und ist der geradlinige Abstand vom Ursprung (0, 0, 0) zum Vektor. Der Betrag eines Vektors ist die Quadratwurzel aus den Summen der Quadrate der Komponenten. Zum Beispiel ist der Betrag des Vektors (0, 2, 0) 2; der Betrag des Vektors (0, 1, 1) ist ungefähr 1,41 (also die Quadratwurzel aus 2):

var forwardMagnitude = Vector3.forward.magnitude; // = 1

var vectorMagnitude =
    new Vector3(2f, 5f, 3f).magnitude; // ~= 6.16
Hinweis

Ein Vektor, dessen Betrag 1 ist, wird Einheitsvektor genannt.

Der Betrag kann dann für andere Berechnungen verwendet werden. Um zum Beispiel den Abstand zwischen zwei Vektoren zu berechnen, kannst du einen Vektor vom anderen subtrahieren und den Betrag des Ergebnisses berechnen:

var point1 = new Vector3(5f, 1f, 0f);
var point2 = new Vector3(7f, 0f, 2f);

var distance = (point2 - point1).magnitude; // = 3

Die eingebaute Methode Distance führt die gleiche Berechnung für dich durch:

Vector3.Distance(point1, point2);

Um den Betrag eines Vektors zu berechnen, brauchst du die Quadratwurzel. Es gibt jedoch Fälle, in denen du den tatsächlichen Wert des Betrags eines Vektors nicht brauchst, sondern nur zwei Längen vergleichen willst. In diesen Fällen kannst du die Quadratwurzel weglassen und mit dem Quadrat des Betrags arbeiten. Das ist ein bisschen schneller, und wir legen viel Wert auf schnelle Berechnungen - vor allem bei der Spieleentwicklung.

Um diesen Wert zu erhalten, verwende die Eigenschaft sqrMagnitude:

var distanceSquared = (point2 - point1).sqrMagnitude; // = 9

Viele Operationen funktionieren am besten mit Vektoren, die den Betrag von 1 haben. Ein Vektor mit dem Betrag von 1 wird auch Einheitsvektor genannt, weil sein Betrag eine einzige Einheit (also eins) ist. Du kannst aus einem Vektor einen neuen Vektor mit der gleichen Richtung, aber mit dem Betrag 1 erzeugen, indem du ihn durch seinen eigenen Betrag teilst. Das nennt man Normalisierung eines Vektors:

var bigVector = new Vector3(4, 7, 9); // magnitude = 12.08
var unitVector =
    bigVector / bigVector.magnitude; // magnitude = 1

Dies ist eine gängige Operation. Du kannst also direkt auf eine normalisierte Version eines Vektors zugreifen, indem du die Eigenschaft normalisiert verwendest:

var unitVector2 = bigVector.normalized;

Vektoren können auch skaliert werden. Wenn du einen Vektor mit einer einzelnen Zahl (einem Skalar) multiplizierst, ist das Ergebnis ein Vektor, bei dem jede Komponente der Quelle mit dieser Zahl multipliziert wird:

var v1 = Vector3.one * 4; // = (4, 4, 4)

Du kannst auch eine komponentenweise Skalierung durchführen, indem du die Methode Scale verwendest. Bei dieser Methode wird aus zwei Vektoren ein dritter Vektor erzeugt, bei dem jede Komponente des ersten Vektors mit der entsprechenden Komponente des zweiten Vektors multipliziert wird, d.h. bei zwei Vektoren A und B ist das Ergebnis von A.Scale(B) (A.x * B.x, A.y * B.y, A.z * B.z) :

v1.Scale(new Vector3(3f, 1f, 0f)); // = (12f, 4f, 0f)

Du kannst auch das Punktprodukt zweier Vektoren berechnen, das dir den Unterschied zwischen den Richtungen angibt, in die sie zeigen.

Das Punktprodukt ist definiert als die Summe der Produkte der beiden Vektoren. Das heißt, bei zwei dreidimensionalen Vektoren A und B, A•B = sum(A.x * B.x, A.y * B.y, A.z * B.z).

Du kannst das Punktprodukt verwenden, um die Ähnlichkeit zweier Vektoren zu bestimmen. Das Punktprodukt zwischen zwei Vektoren, die in dieselbe Richtung weisen, ist 1:

var parallel = Vector3.Dot(Vector3.left, Vector3.left); // 1

Das Punktprodukt zwischen zwei Vektoren, die in entgegengesetzte Richtungen zielen, ist -1:

var opposite = Vector3.Dot(Vector3.left, Vector3.right); // -1

Und das Punktprodukt zwischen zwei rechtwinklig zueinander stehenden Vektoren ist 0:

var orthogonal = Vector3.Dot(Vector3.up, Vector3.forward); // 0

Ein schöner Nebeneffekt ist, dass das Punktprodukt zwischen zwei Vektoren auch der Kosinus des Winkels zwischen den beiden Vektoren ist. Das bedeutet, dass du bei einem Punktprodukt zwischen zwei Vektoren den Winkel zwischen den Vektoren berechnen kannst, indem du den Kosinus des Bogens nimmst:

var orthoAngle = Mathf.Acos(orthogonal);
var orthoAngleDegrees = orthoAngle * Mathf.Rad2Deg; // = 90
Hinweis

Die Methode Mathf.Acos gibt einen Wert zurück, der in Radiant gemessen wird. Um ihn in Grad umzurechnen, kannst du ihn mit der Konstante Mathf.Rad2Deg multiplizieren.

Das Punktprodukt ist eine gute Möglichkeit zu erkennen, ob ein Objekt vor oder hinter einem anderen liegt.

Um festzustellen, ob sich ein Objekt vor einem anderen befindet, müssen wir zunächst entscheiden, was "vor" bedeutet. In Unity stellt die lokale Z-Achse die Vorwärtsrichtung dar, auf die du über die Eigenschaft forward auf der Objektseite Transform zugreifen kannst.

Wir können einen Vektor erzeugen, der die Richtung vom ersten Objekt zum zweiten darstellt, indem wir die Position des zweiten von der Position des ersten subtrahieren. Dann können wir das Punktprodukt dieses Vektors mit der Vorwärtsrichtung des ersten Objekts bilden.

Wir können nun mit Hilfe des Punktprodukts herausfinden, ob das zweite Objekt vor dem ersten liegt. Erinnere dich daran, dass das Punktprodukt zweier Vektoren, die in die gleiche Richtung zeigen, 1 ist. Wenn sich das zweite Objekt direkt vor dem ersten befindet, ist die Richtung zu diesem Objekt identisch, was bedeutet, dass das Punktprodukt der beiden Vektoren 1 ist. Wenn es 0 ist, dann steht das Objekt im rechten Winkel zur Vorwärtsrichtung. Wenn er -1 ist, befindet er sich direkt hinter dem Objekt, weil er genau entgegengesetzt zur Vorwärtsrichtung verläuft:

var directionToOtherObject =
    someOtherObjectPosition - transform.position;
var differenceFromMyForwardDirection =
    Vector3.Dot(transform.forward, directionToOtherObject);

if (differenceFromMyForwardDirection > 0) {
    // The object is in front of us
} else if (differenceFromMyForwardDirection < 0) {
    // The object is behind us
} else {
    // The object neither before or behind us; it's at a perfect right
    // angle to our forward direction.
}

Das Kreuzprodukt, ein dritter Vektor orthogonal (im rechten Winkel) zu zwei Eingangsvektoren, ist ebenfalls verfügbar:

var up = Vector3.Cross(Vector3.forward, Vector3.right);
Tipp

Das Kreuzprodukt ist nur für dreidimensionale Vektoren definiert.

Du kannst auch einen neuen Vektor aus zwei Vektoren erhalten, indem du dich von einem zum anderen mit einer bestimmten Größe bewegst. Das ist besonders nützlich, um ein Überschießen zu verhindern. Hier bewegen wir uns von (0, 0, 0) nach (1, 1, 1), ohne uns weiter als 0,5 Einheiten zu bewegen:

var moved =
    Vector3.MoveTowards(Vector3.zero, Vector3.one, 0.5f);
// = (0.3, 0.3, 0.3) (a vector that has a magnitude of 0.5)

Oder von einer Ebene reflektiert werden, die durch eine Normale definiert ist:

var v =
    Vector3.Reflect(new Vector3(0.5f, -1f, 0f), Vector3.up);
// = (0.5, 1, 0)

Du kannst auch linear zwischen zwei Eingabevektoren interpolieren, wenn du eine Zahl zwischen 0 und 1 angibst. Wenn du 0 angibst, erhältst du den ersten Vektor; wenn du 1 angibst, erhältst du den zweiten; und wenn du 0,5 angibst, erhältst du irgendwo in der Mitte der beiden Vektoren:

var lerped = Vector3.Lerp(Vector3.zero, Vector3.one, 0.65f);
// = (0.65, 0.65, 0.65)

Wenn du eine Zahl außerhalb des Bereichs von 0 bis 1 angibst, klammert lerp sie auf einen Wert zwischen 0 und 1 ein. Du kannst dies verhindern, indem du LerpUnclamped verwendest:

var unclamped =
    Vector3.LerpUnclamped(Vector3.zero, Vector3.right, 2.0f);
// = (2, 0, 0)

Diskussion

Dies ist nur ein kleiner Vorgeschmack auf die Verwendung von Vektoren in Unity. Die mathematischen Operationen, die du in Unity mit einem Vektor durchführen kannst, können vieles vereinfachen. Mit dem Punktprodukt kannst du zum Beispiel feststellen, ob sich ein Punkt vor oder hinter einem Spielercharakter befindet, oder ein Radar erstellen, um herauszufinden, wo sich Feinde befinden.

Vektoren machen auch komplexe Operationen, wie das Skalieren oder Drehen von Objekten, sehr einfach. Anstatt jedes Objekt und sein Verhältnis zu den anderen manuell zu berechnen, kannst du einfach die Vektor-Mathematik verwenden.

Mit Vektoren kannst du geometriebezogene Probleme mit deutlich saubererem Code lösen, als du es sonst tun würdest. Sie sind wunderbare mathematische Werkzeuge für deine Spieleentwicklung!

4.2 Drehen im 3D-Raum

Problem

Du willst Dinge im 3D-Raum rotieren lassen.

Lösung

Um im 3D-Raum zu drehen, musst du mit Quaternionen arbeiten. Das sind mathematische Strukturen, die für die Darstellung von Drehungen im 3D-Raum sehr nützlich sind. Eine Quaternion kann eine Drehung um eine beliebige Achse um einen beliebigen Winkel darstellen.

Tipp

Quaternionen können eine knifflige Angelegenheit sein, da es sich - rein mathematisch gesehen - um vierdimensionale Zahlen handelt. Für die Spielentwicklung sind sie jedoch nichts anderes als eine Drehung, und es macht nichts, wenn du nicht genau verstehst , wie eine Quaternion funktioniert.

Du kannst zum Beispiel eine Quaternion verwenden, um eine Drehung um 90 Grad auf der x-Achse zu definieren:

var rotation = Quaternion.Euler(90, 0, 0);

Und dann benutze dies, um einen Punkt um den Ursprung zu drehen:

var input = new Vector3(0, 0, 1);

var result = rotation * input;
// = (0, -1, 0)

Es gibt ein Identitäts-Quaternion, das überhaupt keine Drehung darstellt:

var identity = Quaternion.identity;

Mit der Methode Slerp kannst du zwischenzwei Drehungen interpolieren, d.h.sie überblenden. Dabei wird zwischen Drehungen so übergegangen, dass die Winkeländerung bei jedem Schritt konstant ist. Das ist besser als eine lineare Interpolation von Winkeln, bei der sich die Winkel mit einer nicht konstanten Rate ändern:

var rotationX = Quaternion.Euler(90, 0, 0);

var halfwayRotated =
    Quaternion.Slerp(identity, rotationX, 0.5f);
Tipp

Slerp ist die Abkürzung für sphärische lineare Interpolation.

Du kannst die Quaternionen auch kombinieren. Um zum Beispiel etwas um die y-Achse und dann um die x-Achse zu drehen, multiplizierst du sie (sie werden in umgekehrter Reihenfolge angewendet):

var combinedRotation =
    Quaternion.Euler(90, 0, 0) * // rotate around x
    Quaternion.Euler(0, 90, 0);  // rotate around y
Hinweis

Diese Kombination ist nicht kommutativ: Die Reihenfolge der Multiplikation ist wichtig! Eine Drehung mit x und dann mit y ist nicht dasselbe wie eine Drehung mit y und dann mit x.

Diskussion

Eine andere Methode zur Darstellung von Drehungen im 3D-Raum sind Euler-Winkel, d.h .Drehungen um die x-, y- und z-Achse, die separat gespeichert werden. Euler-Winkel sind leicht zu verstehen und werden häufig verwendet, um Drehungen im Code auszudrücken.

Dieser Ansatz ist jedoch anfällig für das Problem der Kardanverriegelung, das auftritt, wenn ein Objekt so gedreht wird, dass zwei seiner Rotationsachsen parallel sind. Wenn das passiert, verliert das Objekt einen Freiheitsgrad. Dieses Problem gibt es bei Quaternionen nicht, denn sie können immer in jede beliebige Richtung aus jeder anderen Ausrichtung gedreht werden.

Eine alternative Methode zur Vermeidung der Kardansperre ist die Verwendung einer Matrix, die eine Drehung darstellt (siehe Rezept 4.3). Eine 4×4-Drehungsmatrix besteht jedoch aus 16 Zahlen, während eine Quaternion nur 4 Zahlen umfasst, was bedeutet, dass Quaternionen bei gleichem Ergebnis weniger Platz benötigen als Matrizen.

4.3 Durchführen von Transformationenim 3D-Raum mit Matrizen

Problem

Du willst eine ganze Reihe von Transformationen - also Bewegung, Drehung und Skalierung - in einer einzigen Struktur darstellen.

Lösung

Du kannst eine Matrix verwenden, um eine ganze Transformation darzustellen. Eine Matrix ist einfach ein Gitter aus Zahlen(Gleichung 4-1):

var matrix = new Matrix4x4();
Gleichung 4-1. Eine 4×4-Matrix
[ 1 0 0 0 0 1 0 0 0 0 1 0 5 0 0 1 ]

Du kannst an jeder Stelle des Rasters Werte setzen und abrufen:

var m00 = matrix[0, 0];

matrix[0, 1] = 2f;

Du kannst eine Matrix mit einem Vektor multiplizieren, um einen neuen Vektor zu erzeugen. Abhängig von den Werten in der Matrix hat dies zur Folge, dass der ursprüngliche Vektor verschoben, skaliert und gedreht wird. Du kannst auch komplexere Operationen durchführen, z. B. Scheren oder perspektivische Projektionen anwenden.

Du kannst zwei Matrizen miteinander multiplizieren, um eine dritte Matrix zu erhalten. Wenn du diese neue Matrix mit einem Vektor multiplizierst, ist das Ergebnis dasselbe, als hättest du jede der ursprünglichen Matrizen einzeln nacheinander mit dem Vektor multipliziert.

Tipp

In der Computergrafik und damit auch in der Spieleentwicklung werden in der Regel 4×4-Matrizen verwendet, da mit ihnen eine Vielzahl gängiger Transformationen durchgeführt werden können.

Jetzt wollen wir eine Matrix erstellen, die einen Vektor um 5 Einheiten auf der X-Achse verschiebt (translatiert). Zuerst definieren wir eine neue Matrix mit vier Vector4s (vierdimensionale Vektoren):

var translationMatrix = new Matrix4x4(
    new Vector4(1, 0, 0, 5),
    new Vector4(0, 1, 0, 0),
    new Vector4(0, 0, 1, 0),
    new Vector4(1, 0, 0, 1)
);
Hinweis

Jede der Vector4s, die wir verwenden, um eine Matrix zu erstellen, steht für eine Spalte, nicht für eine Zeile.

Die Matrix, die dieser Code erstellt, ist in Gleichung 4-1 dargestellt.

Hinweis

Wenn wir einen dreidimensionalen Vektor mit einer Matrix multiplizieren, fügen wir am Ende des Vektors eine 1 hinzu, wodurch ein vierdimensionaler Vektor entsteht. Die zusätzliche Komponente wird gemeinhin alsw Komponente bezeichnet.

Multipliziert man diese Matrix mit einem vierdimensionalen Vektor, V, erhält man folgendes Ergebnis:

1*Vx  +  0*Vy  +  0*Vz  +  5*Vw = resultX
0*Vx  +  1*Vy  +  0*Vz  +  0*Vw = resultY
0*Vx  +  0*Vy  +  1*Vz  +  0*Vw = resultZ
0*Vx  +  0*Vy  +  0*Vz  +  1*Vw = resultW

Um zum Beispiel den Punkt (0, 1, 2) (a Vector3) mit dieser Matrix zu multiplizieren:

  1. Zuerst fügen wir unsere w Komponente hinzu:

    Vx = 0, Vy = 1, Vz = 2, Vw = 1
    
    1*0  +  0*1  +  0*2  +  5*1 = 5
    0*0  +  1*1  +  0*2  +  0*1 = 1
    0*0  +  0*1  +  1*2  +  0*1 = 2
    0*0  +  0*1  +  0*2  +  1*1 = 1
  2. Dann verwerfen wir die vierte Komponente und unser Ergebnis bleibt übrig. Unser Endergebnis ist also der Vektor (5, 1, 2).

Anstatt uns diese ganze Arbeit selbst zuzumuten, bietet Unity eine MultiplyPoint Methode als Teil des Matrix4x4 Typs:

var input = new Vector3(0, 1, 2);

var result = translationMatrix.MultiplyPoint(input);
// = (5, 1, 2)
Hinweis

Du fragst dich vielleicht, warum die Matrix überhaupt eine vierte Zeile hat, denn sie bedeutet nur, dass wir eine nutzlose vierte Komponente zu unseren Vektoren hinzufügen und entfernen müssen. Sie ist da, um Operationen wie perspektivische Projektionen zu ermöglichen. Wenn du nur Transformationen wie Verschiebungen, Drehungen und Skalierungen durchführst, kannst du auch nur einen Teil der Matrix verwenden und stattdessen die Funktion MultiplyPoint4x3 von Matrix4x4nutzen. Sie ist etwas schneller, kann aber nur für Übersetzungen, Drehungen und Skalierungen verwendet werden, nicht aber für andere Aufgaben.

Unity bietet auch Hilfsmethoden, um Punkte mithilfe einer Matrix zu übersetzen:

var input = new Vector3(0, 1, 2);

var translationMatrix = Matrix4x4.Translate(new Vector3(5, 1, -2));

var result = translationMatrix.MultiplyPoint(input);
// = (5, 2, 0)

Du kannst einen Punkt auch mithilfe von Matrizen und Quaternionen um den Ursprung drehen:

var rotate90DegreesAroundX = Quaternion.Euler(90, 0, 0);

var rotationMatrix = Matrix4x4.Rotate(rotate90DegreesAroundX);

var input = new Vector3(0, 0, 1);

var result = rotationMatrix.MultiplyPoint(input);

In diesem Fall hat sich der Punkt von vor dem Ursprung nach unter den Ursprung verschoben, was zu dem Punkt (0, -1, 0) führt.

Wenn dein Vektor eine Richtung repräsentiert und du eine Matrix verwenden willst, um den Vektor zu drehen, kannst du MultiplyVector verwenden. Diese Methode verwendet nur die Teile der Matrizen, die für eine Drehung notwendig sind, und ist daher etwas schneller:

result = rotationMatrix.MultiplyVector(input);
// = (0, -1, 0) - the same result.

Du kannst auch eine Matrix verwenden, die einen Punkt vom Ursprung entfernt skaliert:

var scale2x2x2 = Matrix4x4.Scale(new Vector3(2f, 2f, 2f));

var input = new Vector3(1f, 2f, 3f);

var result = scale2x2x2.MultiplyPoint3x4(input);
// = (2, 4, 6)

Wenn du Matrizen miteinander multiplizierst, entsteht eine neue Matrix, die, wenn sie mit einem Vektor multipliziert wird, dasselbe Ergebnis liefert, als hättest du den Vektor mit jeder der ursprünglichen Matrizen in der Reihenfolge multipliziert. Mit anderen Worten: Wenn du dir eine Matrix als Anweisung zur Veränderung eines Punktes vorstellst, kannst du mehrere Matrizen in einem einzigen Schritt kombinieren.

Tipp

Wenn wir Matrizen auf diese Weise kombinieren, nennen wir das Verkettung der Matrizen.

In diesem Beispiel verketten wir Matrizen:

var translation = Matrix4x4.Translate(new Vector3(5, 0, 0));
var rotation = Matrix4x4.Rotate(Quaternion.Euler(90, 0, 0));
var scale = Matrix4x4.Scale(new Vector3(1, 5, 1));

var combined = translation * rotation * scale;

var input = new Vector3(1, 1, 1);
var result = combined.MultiplyPoint(input);
Debug.Log(result);
// = (6, 1, 5)

Wie bei den Quaternionen ist die Reihenfolge der Multiplikation wichtig! Die Matrixmultiplikation ist nicht kommutativ, die Multiplikation von regulären Zahlen hingegen schon. Zum Beispiel ist 2 * 5 die gleiche Berechnung wie 5 * 2: Beide Berechnungen ergeben die Zahl 10.

Ein Objekt zu verschieben und es dann zu drehen, führt jedoch nicht zum gleichen Ergebnis wie ein Objekt zu drehen und es dann zu verschieben. Ebenso führt die Kombination einer Matrix, die einen Punkt verschiebt, mit einer Matrix, die einen Punkt dreht, nicht zu demselben Ergebnis, wenn du sie in umgekehrter Reihenfolge kombinierst.

Wenn du Matrizen mit Multiplikation kombinierst, werden sie in umgekehrter Reihenfolge der Multiplikation angewendet. Gegeben ein Punkt P und die Matrizen A, B und C:

P * (A * B * C) == (A * (B * (C * P)))

kannst du eine kombinierte Übersetzungs-Drehungs-Matrix mit der Methode Matrix4x4.TRS erstellen:

var transformMatrix = Matrix4x4.TRS(
    new Vector3(5, 0, 0),
    Quaternion.Euler(90, 0, 0),
    new Vector3(1, 5, 1)
);

Diese neue Matrix skaliert, dreht und verschiebt jeden Punkt, auf den du sie anwendest.

Du kannst auch eine Matrix erhalten, die die Position eines Punktes in der Komponente im lokalen Raum in den Weltraum konvertiert, d.h. nimmt die lokale Position und wendet die lokale Translation, Rotation und Skalierung von diesem Objekt sowie die aller seiner Eltern an:

var localToWorld = this.transform.localToWorldMatrix;

Du kannst auch eine Matrix erhalten, die den umgekehrten Weg geht, d.h. sie konvertiert vom Weltraum in den lokalen Raum:

var worldToLocal = this.transform.worldToLocalMatrix;

Puh! Das sind eine Menge Dinge, die man mit Matrizen machen kann!

Diskussion

Intern verwendet Unity Matrizen, um Transformationen darzustellen. Der Einfachheit halber verbringst du die meiste Zeit damit, direkt auf Position, Drehung und Maßstab zuzugreifen. Dennoch ist es immer gut zu wissen, wie die Dinge unter der Haube funktionieren.

4.4 Arbeiten mit Winkeln

Problem

Du willst mit den Winkeln zwischen Vektoren arbeiten.

Lösung

In Unity werden die meisten Drehungen, die als Euler-Winkel dargestellt werden, in Grad angegeben.

Wir können Dinge mit Hilfe der Methode Rotate der Klasse Transform um Grad drehen:

// Rotate 90 degrees - one quarter circle - around the X axis
transform.Rotate(90, 0, 0);
Tipp

Ein Kreis hat 360 Grad; ein Kreis hat 2π Bogenmaß. Das sind nur unterschiedliche Maßeinheiten für Winkel.

Grad ist den meisten Menschen viel vertrauter, aber mit Bogenmaß lässt sich oft leichter rechnen. Deshalb erwarten Teile von Unity, vor allem die, die mit Mathematik zu tun haben, das Bogenmaß. Ein Kreis hat 2π Bogenmaß:

// The sine of pi radians (one half-circle) is zero
Mathf.Sin(Mathf.PI);  // = 0

Du kannst von Bogenmaß in Grad umrechnen und wieder zurück, etwa so:

// Converting 90 degrees to radians
var radians = 90 * Mathf.Deg2Rad; // ~= 1.57 (π / 2)

// Converting 2π radians to degrees
var degrees = 2 * Mathf.PI * Mathf.Rad2Deg; // = 360

Diskussion

Das Punktprodukt von zwei Einheitsvektoren ist gleich dem Kosinus des Winkels zwischen ihnen. Wenn du den Kosinus eines Grades hast, kannst du den ursprünglichen Grad erhalten, indem du den Bogenkosinus davon nimmst. Das bedeutet, dass du den Winkel zwischen zwei Vektoren wie folgt bestimmen kannst:

var angle = Mathf.Acos(Vector3.Dot(Vector3.up, Vector3.left));

Das Ergebnis ist π Bogenmaß; wenn du es dem Benutzer zeigen willst, solltest du es zuerst in Grad umrechnen. Ein Kreis hat 2π Bogenmaß, während ein Kreis 360 Grad hat. Um eine Zahl von Bogenmaß in Grad umzurechnen, multiplizierst du sie mit 180/π. Beispiel: π/2 Bogenmaß in Grad = (π/2) * (180/π) = 90. Die Umrechnung von Grad in Bogenmaß funktioniert umgekehrt: Du multiplizierst sie mit π/180. Beispiel: 45 Grad im Bogenmaß ist 45 * (π/180) = π/4.

Du kannst dies in deinem Code vereinfachen, indem du die Konstanten Mathf.Deg2Rad und Mathf.Rad2Deg verwendest. Wenn du einen in Bogenmaß ausgedrückten Winkel mit Mathf.Rad2Deg multiplizierst, erhältst du das Ergebnis in Grad; wenn du einen in Grad ausgedrückten Winkel mit Mathf.Deg2Rad multiplizierst, erhältst du das Ergebnis in Bogenmaß.

4.5 Ermittlung der Entfernung zu einem Ziel

Problem

Du willst überprüfen, ob sich ein Objekt innerhalb eines bestimmten Bereichs eines anderen befindet .

Lösung

Du musst ein Skript erstellen und zu dem Objekt hinzufügen, das wissen muss, wann sich das andere Objekt in seiner Reichweite befindet:

  1. Erstelle ein neues C#-Skript namens RangeChecker.cs und füge den folgenden Code hinzu:

    public class RangeChecker : MonoBehaviour {
    
        // The object we want to check the distance to
        [SerializeField] Transform target;
    
        // If the target is within this many units of us, it's in range
        [SerializeField] float range = 5;
    
        // Remembers if the target was in range on the previous frame.
        private bool targetWasInRange = false;
    
        void Update () {
    
            // Calculate the distance between the objects
            var distance = (target.position - transform.position).magnitude;
    
            if (distance <= range && targetWasInRange == false) {
                // If the object is now in range, and wasn't before, log it
                Debug.LogFormat("Target {0} entered range!", target.name);
    
                // Remember that it's in range for next frame
                targetWasInRange = true;
    
            } else if (distance > range && targetWasInRange == true) {
                // If the object is not in range, but was before, log it
                Debug.LogFormat("Target {0} exited range!", target.name);
    
                // Remember that it's no longer in range for next frame
                targetWasInRange = false;
            }
    
        }
    }
  2. Wenn du dieses Skript an ein beliebiges Objekt anhängst und ein anderes Objekt an das Feld Ziel des Skripts anhängst, erkennt das Skript, wenn das Ziel den angegebenen Bereich betritt und verlässt.

Diskussion

Wenn du dieses Rezept mit Rezept 4.6 kombinierst, kannst du ziemlich einfach ein Verhalten zusammenstellen, bei dem ein Objekt nur die Objekte "sehen" kann, die sich in der Nähe befinden. Eine ausgefeiltere Version dieses Skripts findest du in Rezept 10.1.

4.6 Den Winkel zu einem Ziel bestimmen

Problem

Du willst den Winkel zwischen zwei Objekten finden.

Lösung

Du musst ein Skript erstellen und zu dem Objekt hinzufügen, das den Winkel zwischen ihm und einem anderen Objekt wissen muss:

  1. Erstelle ein neues C#-Skript namens AngleChecker.cs und füge den folgenden Code hinzu:

    public class AngleChecker : MonoBehaviour {
    
        // The object we want to find the angle to
        [SerializeField] Transform target;
    
        void Update () {
    
            // Get the normalized direction to the target
            var directionToTarget =
                (target.position - transform.position).normalized;
    
            // Take the dot product between that direction and our forward
            // direction
            var dotProduct = Vector3.Dot(transform.forward,
                                         directionToTarget);
    
            // Get the angle
            var angle = Mathf.Acos(dotProduct);
    
            // Log the angle, limiting it to 1 decimal place
            Debug.LogFormat(
                "The angle between my forward direction and {0} is {1:F1}°",
                target.name, angle * Mathf.Rad2Deg
            );
    
        }
    }
  2. Wenn du dieses Skript an ein beliebiges Objekt anhängst und ein anderes Objekt mit dem Feld Ziel des Skripts verbindest, protokolliert das Skript den Winkel in Grad zwischen der Vorwärtsrichtung des Objekts und dem Zielobjekt.

Diskussion

Das Konzept des "Winkels zwischen zwei Objekten" hängt davon ab, dass du mindestens eine Richtung wählst. Du kannst den Winkel zwischen zwei Punkten im Raum nicht bestimmen, weil es eine unendliche Anzahl von möglichen Winkeln zwischen ihnen gibt. Stattdessen musst du eine Richtung relativ zum ersten Objekt wählen und diese mit der Richtung zum zweiten Objekt vergleichen.

Get Unity Development Cookbook, 2. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.