Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Matrizes e transformações
Introdução
Antes de ler este tutorial, recomendamos que você leia o tutorial e entenda tudo sobre Matemática vetorial, pois este tutorial requer conhecimento de vetores.
Este tutorial é sobre transformações e como são representados no Godot utilizando matrizes. Este não é um guia completo sobre matrizes. As transformações geralmente são aplicadas na translação, rotação e escala, portanto, vamos nos concentrar em como representá-las com matrizes.
A maior parte deste guia se concentra em 2D, usando Transform2D e Vector2, mas a maneira como as coisas funcionam em 3D é muito semelhante.
Nota
Como mencionado no tutorial anterior, é importante lembrar que no Godot, o eixo Y aponta para baixo em 2D. Isso é o oposto de como a maioria das escolas ensina álgebra linear, com o eixo Y apontando para cima.
Nota
A convenção é que o eixo X é vermelho, o eixo Y é verde e o eixo Z é azul. Este tutorial é codificado por cores para corresponder a essas convenções, mas também representaremos o vetor de origem com a cor azul.
Componentes da matriz e a matriz Identidade
A matriz identidade representa uma transformação sem translação, sem rotação e sem escala. Vamos começar olhando para a matriz identidade e como seus componentes se relacionam com sua aparência visual.
As matrizes têm linhas e colunas, e uma matriz de transformação tem convenções específicas sobre o que cada uma faz.
Na imagem acima, podemos ver que o vetor X vermelho é representado pela primeira coluna da matriz, e o vetor Y verde é igualmente representado pela segunda coluna. Uma alteração nas colunas mudará esses vetores. Veremos como eles podem ser manipulados nos próximos exemplos.
Você não deve se preocupar em manipular as linhas diretamente, pois geralmente trabalhamos com as colunas. No entanto, você pode pensar nas linhas da matriz como uma indicação de quais vetores contribuem para o movimento em uma determinada direção.
Quando nos referimos a um valor como t.x.y, esse é o componente Y do vetor coluna X. Em outras palavras, o canto inferior esquerdo da matriz. Da mesma forma, t.x.x é o canto superior esquerdo, t.y.x é o canto superior direito e t.y.y é o canto inferior direito, onde t é o Transform2D.
Dimensionando a matriz de transformação
Aplicar uma escala é uma das operações mais fáceis de entender. Vamos começar colocando o logotipo do Godot abaixo dos nossos vetores para que possamos ver visualmente os efeitos em um objeto:
Agora, para escalar la matriz, tudo o que precisamos fazer é multiplicar cada componente pela escala que desejamos. Vamos aumentá-la em 2. 1 vez 2 vira 2, e 0 vez 2 vira 0, então terminamos com isto:
Para fazer isso em código, multiplicamos cada um dos vetores:
var t = Transform2D()
# Scale
t.x *= 2
t.y *= 2
transform = t # Change the node's transform to what we calculated.
Transform2D t = Transform2D.Identity;
// Scale
t.X *= 2;
t.Y *= 2;
Transform = t; // Change the node's transform to what we calculated.
Se quiséssemos retornar à escala original, poderíamos multiplicar cada componente por 0.5. Isso é basicamente tudo o que há para saber sobre o escalonamento de uma matriz de transformação.
Para calcular a escala do objeto a partir de uma matriz de transformação existente, você pode usar length() em cada um dos vetores coluna.
Nota
Em projetos reais, você pode usar o método scaled() para realizar o escalonamento.
Girando a matriz de transformação
Começaremos da mesma forma que antes, com o logotipo do Godot sob a matriz identidade:
Como exemplo, digamos que queremos rotacionar nosso logotipo do Godot em 90 graus no sentido horário. No momento, o eixo X aponta para a direita e o eixo Y aponta para baixo. Se rotacionarmos isso em nossa mente, veríamos logicamente que o novo eixo X deve apontar para baixo e o novo eixo Y deve apontar para a esquerda.
Você pode imaginar que segura tanto o logotipo do Godot quanto seus vetores e depois os gira em torno do centro. Onde quer que você termine de girar, a orientação dos vetores determina qual é a matriz.
Precisamos representar "baixo" e "esquerda" em coordenadas normais, o que significa que definiremos X como (0, 1) e Y como (-1, 0). Esses também são os valores de Vector2.DOWN e Vector2.LEFT. Quando fazemos isso, obtemos o resultado desejado de rotacionar o objeto:
Se você tiver dificuldades para entender o que foi dito acima, tente este exercício: corte um quadrado de papel, desenhe os vetores X e Y em cima dele, coloque-o sobre um papel milimetrado, depois rotacione-o e anote os pontos finais.
Para realizar a rotação em código, precisamos ser capazes de calcular os valores programaticamente. Esta imagem mostra as fórmulas necessárias para calcular a matriz de transformação a partir de um ângulo de rotação. Não se preocupe se esta parte parecer complicada, prometo que é a coisa mais difícil que você precisa saber.
Nota
Godot represents all rotations with radians, not degrees.
A full turn is TAU or PI*2 radians, and a quarter
turn of 90 degrees is TAU/4 or PI/2 radians. Working
with TAU usually results in more readable code.
Nota
Fato curioso: além de Y ser para baixo no Godot, a rotação é representada no sentido horário. Isso significa que todas as funções matemáticas e trigonométricas se comportam da mesma forma que um sistema com Y para cima e sentido anti-horário, já que essas diferenças se "cancelam". Você pode pensar nas rotações em ambos os sistemas como sendo "de X para Y".
Para realizar uma rotação de 0,5 radianos (cerca de 28,65 graus), inserimos o valor de 0,5 na fórmula acima e calculamos para descobrir quais devem ser os valores reais:
Here's how that would be done in code (attach the script to a Node2D):
var rot = 0.5 # The rotation to apply.
var t = Transform2D()
t.x.x = cos(rot)
t.y.y = cos(rot)
t.x.y = sin(rot)
t.y.x = -sin(rot)
transform = t # Change the node's transform to what we calculated.
float rot = 0.5f; // 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 calculated.
Para calcular a rotação do objeto a partir de uma matriz de transformação existente, você pode usar atan2(t.x.y, t.x.x), onde t é o Transform2D.
Nota
Em projetos reais, você pode usar o método rotated() para realizar rotações.
Base da matriz de transformação
Até agora só trabalhamos com os vetores x e y, que são responsáveis por representar rotação, escala e/ou cisalhamento (avançado, abordado no final). Os vetores X e Y juntos são chamados de base da matriz de transformação. É importante conhecer os termos "base" e "vetores base".
You might have noticed that Transform2D actually
has three Vector2 values: x, y, and origin.
The origin value is not part of the basis, but it is part of the
transform, and we need it to represent position. From now on we'll
keep track of the origin vector in all examples. You can think of
origin as another column, but it's often better to think of it as
completely separate.
Note que em 3D, o Godot possui uma estrutura Basis separada para conter os três valores Vector3 da base, já que o código pode ficar complexo e faz sentido separá-lo do Transform3D (que é composto por uma Basis e um Vector3 extra para a origem).
Transladando a matriz de transformação
Changing the origin vector is called translating the transformation matrix. Translating is basically a technical term for "moving" the object, but it explicitly does not involve any rotation.
Vamos trabalhar com um exemplo para ajudar a entender isso. Começaremos com a transformação identidade como da última vez, exceto que desta vez acompanharemos o vetor de origem.
If we want to move the object to a position of (1, 2), we need to set its origin vector to (1, 2):
There is also a translated_local() method, which performs a different
operation to adding or changing origin directly. The translated_local()
method will translate the object relative to its own rotation.
For example, an object rotated 90 degrees clockwise will move to
the right when translated_local() is called with Vector2.UP. To translate
relative to the global/parent frame use translated() instead.
Nota
O 2D do Godot usa coordenadas baseadas em pixels, portanto, em projetos reais, você desejará transladar por centenas de unidades.
Colocando tudo junto
Vamos aplicar tudo o que mencionamos até agora em uma única transformação. Para acompanhar, crie um projeto com um nó Sprite2D e use o logotipo do Godot como recurso de textura.
Vamos definir a translação para (350, 150), rotacionar por -0.5 rad e escalar por 3. Publiquei uma captura de tela e o código para reproduzi-la, mas encorajo você a tentar reproduzir a captura de tela sem olhar o código!
var t = Transform2D()
# Translation
t.origin = Vector2(350, 150)
# Rotation
var rot = -0.5 # The rotation to apply.
t.x.x = cos(rot)
t.y.y = cos(rot)
t.x.y = sin(rot)
t.y.x = -sin(rot)
# Scale
t.x *= 3
t.y *= 3
transform = t # Change the node's transform to what we calculated.
Transform2D t = Transform2D.Identity;
// Translation
t.Origin = new Vector2(350, 150);
// Rotation
float rot = -0.5f; // 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 calculated.
Cisalhamento da matriz de transformação (avançado)
Nota
Se você está procurando apenas saber como usar matrizes de transformação, sinta-se à vontade para pular esta seção do tutorial. Esta seção explora um aspecto pouco utilizado das matrizes de transformação com o objetivo de construir uma compreensão sobre elas.
O Node2D fornece uma propriedade de cisalhamento (shearing) nativa.
Você deve ter notado que uma transformação possui mais graus de liberdade do que a combinação das ações acima. A base de uma matriz de transformação 2D possui quatro números no total em dois valores Vector2, enquanto um valor de rotação e um Vector2 para escala possuem apenas 3 números. O conceito de alto nível para o grau de liberdade que falta é chamado de cisalhamento (shearing).
Normalmente, você sempre terá os vetores base perpendiculares entre si. No entanto, o cisalhamento pode ser útil em algumas situações, e entender o cisalhamento ajuda a entender como as transformações funcionam.
Para mostrar visualmente como isso ficará, vamos sobrepor uma grade ao logotipo do Godot:
Cada ponto nesta grade é obtido somando os vetores base. O canto inferior direito é X + Y, enquanto o canto superior direito é X - Y. Se alterarmos os vetores base, a grade inteira se moverá com eles, pois a grade é composta pelos vetores base. Todas as linhas na grade que são atualmente paralelas permanecerão paralelas, independentemente das alterações que fizermos nos vetores base.
Como exemplo, vamos definir Y como (1, 1):
var t = Transform2D()
# Shear by setting Y to (1, 1)
t.y = Vector2.ONE
transform = t # Change the node's transform to what we calculated.
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 calculated.
Nota
Você não pode definir os valores brutos de um Transform2D no editor, portanto deve usar código se quiser aplicar cisalhamento ao objeto.
Devido ao fato de os vetores não serem mais perpendiculares, o objeto sofreu cisalhamento. O centro inferior da grade, que é (0, 1) em relação a si mesmo, está agora localizado em uma posição de mundo de (1, 1).
As coordenadas internas do objeto são chamadas de coordenadas UV em texturas, então vamos emprestar essa terminologia aqui. Para encontrar a posição no mundo a partir de uma posição relativa, a fórmula é U * X + V * Y, onde U e V são números e X e Y são os vetores base.
O canto inferior direito da grade, que está sempre na posição UV de (1, 1), está na posição de mundo de (2, 1), que é calculada a partir de X*1 + Y*1, o que resulta em (1, 0) + (1, 1), ou (1 + 1, 0 + 1), ou (2, 1). Isso corresponde à nossa observação de onde fica o canto inferior direito da imagem.
Da mesma forma, o canto superior direito da grade, que está sempre na posição UV de (1, -1), está na posição de mundo de (0, -1), que é calculada a partir de X*1 + Y*-1, o que resulta em (1, 0) - (1, 1), ou (1 - 1, 0 - 1), ou (0, -1). Isso corresponde à nossa observação de onde fica o canto superior direito da imagem.
Esperamos que agora você entenda totalmente como uma matriz de transformação afeta o objeto, e a relação entre os vetores base e como as coordenadas "UV" ou "internas" do objeto têm sua posição no mundo alterada.
Nota
No Godot, toda a matemática de transformação é feita em relação ao nó pai. Quando nos referimos à "posição global" (world position), ela seria relativa ao pai do nó em vez disso, caso o nó tivesse um pai.
Se você quiser uma explicação adicional, deve dar uma olhada no excelente vídeo do 3Blue1Brown sobre transformações lineares: https://www.youtube.com/watch?v=kYB8IZa5AuE
Aplicações práticas de transformações
Em projetos reais, você geralmente trabalhará com transformações dentro de transformações, tendo múltiplos nós Node2D ou Node3D como pais uns dos outros.
No entanto, é útil entender como calcular manualmente os valores que precisamos. Vamos abordar como você poderia usar Transform2D ou Transform3D para calcular manualmente as transformações dos nós.
Convertendo posições entre transformações
Há muitos casos em que você gostaria de converter uma posição para dentro e para fora de uma transformação. Por exemplo, se você tem uma posição relativa ao jogador e gostaria de encontrar a posição global (relativa ao pai), ou se você tem uma posição global e quer saber onde ela está em relação ao jogador.
Podemos encontrar como um vetor relativo ao jogador seria definido no espaço global usando o operador *:
# World space vector 100 units below the player.
print(transform * Vector2(0, 100))
// World space vector 100 units below the player.
GD.Print(Transform * new Vector2(0, 100));
E podemos usar o operador * na ordem oposta para encontrar qual seria a posição no espaço global se ela fosse definida em relação ao jogador:
# Where is (0, 100) relative to the player?
print(Vector2(0, 100) * transform)
// Where is (0, 100) relative to the player?
GD.Print(new Vector2(0, 100) * Transform);
Nota
Se você souber antecipadamente que a transformação está posicionada em (0, 0), você pode usar os métodos "basis_xform" ou "basis_xform_inv" em seu lugar, que pulam a etapa de lidar com a translação.
Movendo um objeto em relação a si mesmo
Uma operação comum, especialmente em jogos 3D, é mover um objeto em relação a si mesmo. Por exemplo, em jogos de tiro em primeira pessoa, você gostaria que o personagem se movesse para frente (eixo -Z) quando você pressiona W.
Como os vetores base são a orientação em relação ao pai e o vetor de origem é a posição em relação ao pai, podemos somar múltiplos dos vetores base para mover um objeto em relação a si mesmo.
Este código move um objeto 100 unidades para a sua própria direita:
transform.origin += transform.x * 100
Transform2D t = Transform;
t.Origin += t.X * 100;
Transform = t;
Para mover em 3D, você precisaria substituir "x" por "basis.x".
Nota
Em projetos reais, você pode usar translate_object_local em 3D ou move_local_x e move_local_y em 2D para fazer isso.
Aplicando transformações sobre transformações
Uma das coisas mais importantes a saber sobre transformações é como você pode usar várias delas juntas. A transformação de um nó pai afeta todos os seus filhos. Vamos analisar um exemplo.
Nesta imagem, o nó filho tem um "2" após os nomes dos componentes para distingui-los do nó pai. Pode parecer um pouco opressor com tantos números, mas lembre-se de que cada número é exibido duas vezes (ao lado das setas e também nas matrizes) e que quase metade dos números é zero.
As únicas transformações ocorrendo aqui são que o nó pai recebeu uma escala de (2, 1), o filho recebeu uma escala de (0.5, 0.5) e ambos os nós receberam posições.
Todas as transformações do filho são afetadas pelas transformações do pai. O filho tem uma escala de (0.5, 0.5), então você esperaria que ele fosse um quadrado na proporção de 1:1, e ele é, mas apenas em relação ao pai. O vetor X do filho acaba sendo (1, 0) no espaço global porque ele é escalonado pelos vetores base do pai. Da mesma forma, o vetor origin do nó filho está definido como (1, 1), mas isso na verdade o move (2, 1) no espaço global, devido aos vetores base do nó pai.
Para calcular manualmente a transformação no espaço global de uma transformação filha, este é o código que usaríamos:
# Set up transforms like in the image, except make positions be 100 times bigger.
var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200))
var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100))
# Calculate the child's world space transform
# origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200)
var origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin
# basis_x = (2, 0) * 0.5 + (0, 1) * 0
var basis_x = parent.x * child.x.x + parent.y * child.x.y
# basis_y = (2, 0) * 0 + (0, 1) * 0.5
var basis_y = parent.x * child.y.x + parent.y * child.y.y
# Change the node's transform to what we calculated.
transform = Transform2D(basis_x, basis_y, origin)
// Set up transforms 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.5f, 0, 0, 0.5f, 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 calculated.
Transform = new Transform2D(basisX, basisY, origin);
Em projetos reais, podemos encontrar a transformação global do filho aplicando uma transformação sobre a outra usando o operador *:
# Set up transforms like in the image, except make positions be 100 times bigger.
var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200))
var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100))
# Change the node's transform to what would be the child's world transform.
transform = parent * child
// Set up transforms 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.5f, 0, 0, 0.5f, 100, 100);
// Change the node's transform to what would be the child's world transform.
Transform = parent * child;
Nota
Ao multiplicar matrizes, a ordem importa! Não as misture.
Por fim, aplicar a transformação identidade sempre não fará nada.
Se você quiser uma explicação adicional, deve dar uma olhada no excelente vídeo do 3Blue1Brown sobre composição de matrizes: https://www.youtube.com/watch?v=XkY2DOUCWMU
Invertendo uma matriz de transformação
The affine_inverse function returns a transform that "undoes" the
previous transform. This can be useful in some situations.
Let's take a look at a few examples.
Multiplicar uma transformação inversa pela transformação normal desfaz todas as transformações:
var ti = transform.affine_inverse()
var t = ti * transform
# The transform is the identity transform.
Transform2D ti = Transform.AffineInverse();
Transform2D t = ti * Transform;
// The transform is the identity transform.
Transformar uma posição por uma transformação e sua inversa resulta na mesma posição:
var ti = transform.affine_inverse()
position = transform * position
position = ti * position
# The position is the same as before.
Transform2D ti = Transform.AffineInverse();
Position = Transform * Position;
Position = ti * Position;
// The position is the same as before.
Como tudo isso funciona em 3D?
Uma das grandes vantagens das matrizes de transformação é que elas funcionam de maneira muito semelhante entre as transformações 2D e 3D. Todo o código e fórmulas usados acima para 2D funcionam da mesma forma em 3D, com 3 exceções: a adição de um terceiro eixo, o fato de cada eixo ser do tipo Vector3 e também que o Godot armazena a Basis separadamente do Transform3D, já que a matemática pode ficar complexa e faz sentido separá-la.
Todos os conceitos de como a translação, rotação, escala e cisalhamento funcionam em 3D são os mesmos em comparação com o 2D. Para escalar, pegamos cada componente e o multiplicamos; para rotacionar, mudamos para onde cada vetor base está apontando; para transladar, manipulamos a origem; e para cisalhar, mudamos os vetores base para que não sejam perpendiculares.
Se você desejar, é uma boa ideia brincar com as transformações para entender como elas funcionam. O Godot permite que você edite matrizes de transformação 3D diretamente pelo inspetor. Você pode baixar este projeto que possui linhas coloridas e cubos para ajudar a visualizar os vetores da Basis e a origem tanto em 2D quanto em 3D: https://github.com/godotengine/godot-demo-projects/tree/master/misc/matrix_transform
Nota
Você não pode editar a matriz de transformação do Node2D diretamente no inspetor do Godot 4.0. Isso pode mudar em uma versão futura do Godot.
Se você quiser uma explicação adicional, deve dar uma olhada no excelente vídeo do 3Blue1Brown sobre transformações lineares em 3D: https://www.youtube.com/watch?v=rHLEWRxRGiM
Representando rotação em 3D (avançado)
A maior diferença entre as matrizes de transformação 2D e 3D é como você representa a rotação por si só, sem os vetores base.
Com o 2D, temos uma maneira fácil (atan2) de alternar entre uma matriz de transformação e um ângulo. Em 3D, a rotação é complexa demais para ser representada por apenas um número. Existe algo chamado ângulos de Euler, que podem representar rotações como um conjunto de 3 números, no entanto, eles são limitados e não são muito úteis, exceto para casos triviais.
Em 3D, normalmente não usamos ângulos; ou usamos uma base de transformação (usada em quase todos os lugares no Godot) ou usamos quatérnios (quaternions). O Godot pode representar quatérnios usando a estrutura Quaternion. Minha sugestão para você é ignorar completamente como eles funcionam por baixo dos panos, porque eles são muito complicados e contra-intuitivos.
No entanto, se você realmente precisa saber como funciona, aqui estão alguns ótimos recursos que você pode acompanhar em ordem:
https://www.youtube.com/watch?v=mvmuCPvRoWQ