Matrices and transformations in game development

  • introduce
    • Matrix components and identity matrices
    • Scaling transformation matrix
    • Rotation transformation matrix
    • The basis of transformation matrices
    • Translation transformation matrix
    • Put it all together
    • Shear transformation matrix (advanced)
  • Practical application of transformation
    • Switch positions between transitions
    • Move objects relative to themselves
    • Apply the transformation to the transformation
    • Inverted transformation matrix
  • How does all this work in 3D?
    • Represents 3D rotation (advanced)

introduce

Before reading this tutorial, I recommend that you read through and understand the vector math tutorial I posted earlier, as this tutorial requires vector knowledge.

This tutorial covers transformations and how to use matrices to represent them in Godot. It is not a complete in-depth guide to matrices. Transformations are applied in most cases in the form of translation, rotation, and scaling, so we will focus on how those transformations can be represented by matrices.

Most of this guide uses Transform2D and Vector2 for 2D research, but the way things work in 3D is very similar.

Pay attention to

As mentioned earlier in the tutorial, be sure to remember that in tuo, Y-axis points are important to pour in 2D. This is the opposite of what most schools teach linear algebra, with the Y-axis pointing upwards.

Pay attention to

The convention is that X-axis is red, Y-axis is green, z-axis is blue. This tutorial uses color coding to match these conventions, but we will also represent the original vector in blue.

Matrix components and identity matrices

The identity matrix represents transformations without translation, rotation, or scaling. Let’s start with the identity matrix and its composition in relation to visual appearance.

Matrices have rows and columns, and transformation matrices have specific conventions for each function.

In the figure above, we can see that the red X vector is represented by the first column of the matrix, and the green Y vector is also represented by the second column. Changing the columns changes these vectors. We’ll see how it works in the next few examples.

You don’t have to worry about manipulating rows directly, because we usually use columns. However, you can think of the rows of the matrix as showing which vectors help move in a given direction.

When we refer to something like txy, this is the Y component of the X column vector. In other words, the lower left corner of the matrix. Similarly, TXX is in the upper left, Tyx is in the upper right, and Tyy is in the lower right, where T is Transform2D.

Scaling transformation matrix

Applying scale is one of the easiest operations to understand. First, place the Godot logo under the vector so that we can visually see the effect on the object:

Now, to scale the matrix, all we have to do is multiply each component times the desired ratio. Let’s make it bigger by 2. 1 times 2 becomes 2, and 0 times 2 becomes 0, so we get the following conclusion:

To do this, we can simply multiply each vector:

Transform2D t = Transform2D.Identity;
// Scale
t.x *= 2;
t.y *= 2;
Transform = t; // Change the node's transform to what we just calculated.
Copy the code

If we want to restore it to its original ratio, we can multiply each component by 0.5. The scaling transformation matrix is pretty much all of that.

To calculate the scale of objects from an existing transformation matrix, use length() on each column vector.

Pay attention to

In a real project, you can use the scaled() method to perform scaling.

Rotation transformation matrix

We will start in the same way as before, adding the Godot logo below the identity matrix:

For example, suppose we want to rotate the Godot logo 90 degrees clockwise. Now, the X-axis is pointing to the right, and the Y-axis is pointing down. If we rotate these buttons in the head, it logically seems that the new X axis should point down and the new Y axis should point left.

You can imagine that you grabbed the Godot logo and its vector at the same time and rotated it around the center. No matter where you complete the rotation, the direction of the vector determines what the matrix is.

We need to represent “down” and “left” in normal coordinates, so this means we set X to (0,1) and Y to (-1, 0). These are also values for vector2.down and Vector2.left. When we do this, we get the expected result of the rotated object:

If you are having trouble understanding the above, try the following exercise: Cut a piece of paper, draw X and Y vectors on top of it, place it on the square paper, then rotate and note the endpoints.

In order to perform code rotation, we need to be able to evaluate values programmatically. This figure shows the formula needed to compute the transformation matrix from the rotation Angle. Don’t worry if this part seems complicated, I promise it’s the hardest thing you’ll ever need to know.

Pay attention to

Godot refers to all rotations in radians instead of degrees. A full circle is TAU or PI PI 2 radians, and a quarter of a circle is TAU / 4 or PI / 2 radians. Using TAU usually makes the code more readable.

Pay attention to

Interesting fact: In addition to Y falling in Godot, the rotation is also represented clockwise. This means that all the math and trigger functions behave the same as the Y-IS-UP CCW system, because these differences “cancel out.” You can think of rotation as “from X to Y” in both systems.

To perform a rotation of 0.5 radians (about 28.65 degrees), we simply plug the value of 0.5 into the formula above and evaluate it to find that the actual value should be:

This is done in code (placing the script on Node2D) :

float rot = 0.5 f; // The rotation to apply.
Transform2D t = Transform2D.Identity;
t.x.x = t.y.y = Mathf.Cos(rot);
t.x.y = t.y.x = Mathf.Sin(rot);
t.y.x *= - 1;
Transform = t; // Change the node's transform to what we just calculated.
Copy the code

To compute the rotation of an object from an existing transformation matrix, use atan2 (txy, TXX), where T is Transform2D.

Pay attention to

In a real project, you could use the rotation () method to perform the rotation.

The basis of transformation matrices

So far, we have only used x and Y vectors, which represent rotation, scaling, and/or shearing (advanced, covered at the end). The X and Y vectors together are called the basis of the transformation matrix. The terms “base” and “base vector” are important.

You may have noticed that Transform2D actually has three Vector2 values: X, Y, and Origin. The origin value is not part of the base, but it’s part of the transformation, and we need it to represent the position. From now on, we will trace the original vector in all examples. You can think of the origin as another column, but it’s usually best to keep it completely separate.

Note that in 3D, tuo has a separate base to keep the base values of the three structures of the Vector3, since the code can be very complex, it makes sense to separate it from the transform (this is the origin of a base and an extra Vector3).

Translation transformation matrix

Changing the origin vector is called the transformation transformation matrix. Pan is basically the technical term for “moving” an object, but it obviously doesn’t involve any rotation.

Let’s look at an example to help understand this. We’re going to start with the identity transformation just like we did last time, except this time we’re going to track the original vector.

If we want the object to move to position (1,2), we simply set its origin vector to (1,2) :

There is also a translation() method that performs a different operation than simply adding or changing the origin. The translation() method transforms an object relative to its own rotation. For example, when using vector2.up Translation (), an object rotated 90 degrees clockwise will move to the right.

Pay attention to

Godot 2D uses pixel-based coordinates, so in a real project you would need to translate in hundreds of units.

Put it all together

We’ll apply everything we’ve covered so far to one transformation. Next, create a simple project with a Sprite node and use the Godot logo as a texture resource.

Let’s set the translation to (350,150), rotate -0.5rad, scale 3. I have posted the screenshots and provided the copy code, but I recommend you try copying the screenshots without looking at the code!

Transform2D t = Transform2D.Identity;
// Translation
t.origin = new Vector2(350.150);
// Rotation
float rot = 0.5 f; // The rotation to apply.
t.x.x = t.y.y = Mathf.Cos(rot);
t.x.y = t.y.x = Mathf.Sin(rot);
t.y.x *= - 1;
// Scale
t.x *= 3;
t.y *= 3;
Transform = t; // Change the node's transform to what we just calculated.
Copy the code

Shear transformation matrix (advanced)

Pay attention to

If you’re just looking for how to use transformation matrices, feel free to skip this section. This section explores a less commonly used aspect of transformation matrices to build an understanding of them.

You may have noticed that transformations have more freedom than the above combination of actions. The basis of the 2D transformation matrix has four totals in two Vector2 values, whereas the rotation value and scale Vector2 have only three numbers. A higher concept lacking degrees of freedom is called shearing.

In general, you will always make the base vectors perpendicular to each other. However, clipping can be useful in some cases, and knowing about clipping can help you understand how transformations work.

To visually show the appearance, let’s overlay a grid on the Godot logo:

Each point on the grid is obtained by adding the fundamental vectors. The bottom right is X plus Y, and the top right is X minus Y. If you change the base vector, the entire grid moves with it, because the grid is made up of the base vectors. No matter what changes we make to the base vector, all parallel lines on the current grid will remain parallel.

For example, we set Y to (1,1) :

Transform2D t = Transform2D.Identity;
// Shear by setting Y to (1, 1)
t.y = Vector2.One;
Transform = t; // Change the node's transform to what we just calculated.
Copy the code

Pay attention to

You cannot set the original Transform2D value in the editor, so you must use code if you want to cut objects.

The object has been clipped because the vector is no longer vertical. The bottom center of the grid, which is (0,1) relative to itself, is now in the world position (1,1).

Coordinates within an object are called UV coordinates in a texture, so we borrow that term here. To find the world position from relative positions, the formula is U * X + V * Y, where U and V are numbers and X and Y are basis vectors.

The lower right corner of the grid is always in the UV position of (1, 1), in the world position of (2,1), which is calculated by X * 1 + Y * 1, i.e. (1, 0) + (1, 1, 0 + 1) or (2,1). This is consistent with our observation of the lower right corner of the image.

Similarly, the upper-right corner of the grid is always in the UV position of (1, -1), in the world position of (0, -1), which is based on X * 1 + Y * -1, i.e. (1, 0) – (1, 1) or (1-1, 0-1) or (0, -1). This is consistent with our observation of the position in the upper right corner of the image.

Hopefully, you now fully understand how transformation matrices affect objects, as well as the relationships between base vectors and how an object’s “UV” or “in-coordinate” changes its world position.

Pay attention to

In Godot, all the transformation math is done relative to the parent node. When we refer to “world location,” if the node has a parent, it will be relative to the node’s parent.

If you need additional instructions, you should check out 3Blue1Brown’s great video on linear transformations: www.youtube.com/watch?v=kYB…

Practical application of transformation

In a real project, you would typically handle transformations within transformations by having multiple Node2D or Spatial nodes parent each other.

However, sometimes it is useful to manually calculate the values we need. We show you how to manually compute the transformation of a node using Transform2D or Transform.

Switch positions between transitions

In many cases, you want to do a position shift in a transformation. For example, if you have a position relative to the player and want to find the world (parent relative) position, or you have a world position and want to know its position relative to the player.

We can use the “xform” method to find the definition of the vector relative to the player in the world space:

// World space vector 100 units below the player.
GD.Print(Transform.Xform(new Vector2(0.100)));
Copy the code

We can use the “xform_inv” method to find the spatial position of the world relative to the player’s definition:

// Where is (0, 100) relative to the player?
GD.Print(Transform.XformInv(new Vector2(0.100)));
Copy the code

Pay attention to

If you know in advance that the transformation is at (0,0), you can use the “basis_xform” or “basis_xform_inv” methods instead, which skip the translation.

Move objects relative to themselves

A common action, especially in 3D games, is to move objects relative to themselves. For example, in a first-person shooter, you want the character to move forward (-z-axis) W when the time is right.

Since the fundamental vector is the direction relative to the parent, and the origin vector is the position relative to the parent, we can simply add multiple fundamental vectors to move the object relative to ourselves.

This code moves an object 100 units to the right:

Transform2D t = Transform;
t.origin += t.x * 100;
Transform = t;
Copy the code

To move around in 3D, you need to replace “x” with “basis.x”.

Pay attention to

In a real project, you can use translate_object_local in 3D or move_LOCAL_X and MOVE_LOCAL_Y in 2D.

Apply the transformation to the transformation

One of the most important things about transformations is how several of them are used together. The transformation of the parent node affects all its children. Let’s examine an example.

In this image, the child node has a “2” after the component name to distinguish it from the parent node. The sheer number of numbers may seem overwhelming, but remember that each number is displayed twice (next to the arrow and in the matrix), and nearly half of the numbers are zero.

The only transformation made here is that the parent node has a ratio of (2,1) and the child node has a ratio of (0.5, 0.5), and the positions of both nodes are assigned positions.

All child transformations are affected by the parent transformation. The subitems have ratios of (0.5, 0.5), so you want it to be a 1:1 square, and it is (but only relative to the parent item). The X vector of the subterm ends up being (1, 0) in world space because it is scaled by the base vector of the parent term. Similarly, the origin vector of the child node is set to (1,1), but the parent node’s base vector actually moves it (2,1) in world space.

To compute the world space transformation of the subtransformation manually, here is the code we will use:

// Set up transforms just like in the image, except make positions be 100 times bigger.
Transform2D parent = new Transform2D(2.0.0.1.100.200);
Transform2D child = new Transform2D(0.5 f.0.0.0.5 f.100.100);

// Calculate the child's world space transform
// origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200)
Vector2 origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin;
// basisX = (2, 0) * 0.5 + (0, 1) * 0 = (0.5, 0)
Vector2 basisX = parent.x * child.x.x + parent.y * child.x.y;
// basisY = (2, 0) * 0 + (0, 1) * 0.5 = (0.5, 0)
Vector2 basisY = parent.x * child.y.x + parent.y * child.y.y;

// Change the node's transform to what we just calculated.
Transform = new Transform2D(basisX, basisY, origin);
Copy the code

In a real project, we can use the * operator to apply one transformation to another to find the child’s world transformation:

// Set up transforms just like in the image, except make positions be 100 times bigger.
Transform2D parent = new Transform2D(2.0.0.1.100.200);
Transform2D child = new Transform2D(0.5 f.0.0.0.5 f.100.100);

// Change the node's transform to what would be the child's world transform.
Transform = parent * child;
Copy the code

Pay attention to

When multiplying matrices, order matters! Don’t mix them up.

In the end, applying identity transformation will never work.

If you need additional instructions, you can check out 3Blue1Brown’s excellent video on matrix composition: www.youtube.com/watch?v=XkY…

Inverted transformation matrix

The “affine_inverse” function returns a transformation that “undoes” the previous transformation. This can be useful in some cases, but it’s easier to just provide some examples.

Multiplying the inverse with the normal undoes all transformations:

Transform2D ti = Transform.AffineInverse();
Transform2D t = ti * Transform;
// The transform is the identity transform.
Copy the code

Transpose position by transpose and its inverse will result in the same position (same as “xform_inv”) :

Transform2D ti = Transform.AffineInverse();
Position = Transform.Xform(Position);
Position = ti.Xform(Position);
// The position is the same as before.
Copy the code

How does all this work in 3D?

One of the great things about transformation matrices is that they work very similarly between 2D and 3D transformations. All of the code and formulas above for 2D work the same way in 3D, with three exceptions: a third axis is added, each of which is of type Vector3, and Godot stores the benchmark separately from the Transform, because math can get complicated and it makes sense to keep it separate.

All the concepts about how pan, rotate, scale, and shear work in 3D are the same as in 2D. To scale, we multiply each component times; To rotate, we change the position that each fundamental vector points to; Translation, we manipulate the origin; For clipping, we change the base vector to non-vertical.

If you like, try transformations to see how they work. Godot allows you to edit the 3D transformation matrix directly from the inspector. You can download projects with colored lines and cubes to help visualize base vectors and origins in 2D and 3D: github.com/godotengine…

Pay attention to

In the “matrix” part of Spatial in the inspector of Godot 3.2, the transposition of matrix is displayed as horizontal and the behavior is vertical. This can be changed in future Godot releases to reduce clutter.

Pay attention to

You cannot edit Node2D’s transformation matrix directly in Godot 3.2’s inspector. This may change in a future version of Godot.

If you need additional instructions, you can check out 3Blue1Brown’s great video on 3D linear transformations: www.youtube.com/watch?v=rHL…

Represents 3D rotation (advanced)

The biggest difference between 2D and 3D transformation matrices is how to represent the rotation itself without basis vectors.

Using 2D, we have a simple method (atan2) to switch between the transformation matrix and the Angle. In 3D, we can’t simply represent rotation as a number. There is something called Euler angles that can represent rotations as a set of three numbers, but they are finite and not very useful except in trivial cases.

In 3D, we usually don’t use angles, either use transformation basis (used almost everywhere in Godot), or use quaternions. Godot can represent quaternions using the Quat structure. I recommend that you completely ignore how they work in the background, because they are very complex and unintuitive.

However, if you really need to know how it works, here are some useful resources:

www.youtube.com/watch?v=mvm…

www.youtube.com/watch?v=d4E…

eater.net/quaternions