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)
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)
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
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
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
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
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 Vector4
s (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 Vector4
s, 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:
-
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
-
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 Matrix4x4
nutzen. 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!
4.4 Arbeiten mit Winkeln
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
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:
-
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
;
}
}
}
-
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
Lösung
Du musst ein Skript erstellen und zu dem Objekt hinzufügen, das den Winkel zwischen ihm und einem anderen Objekt wissen muss:
-
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
);
}
}
-
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.