Matemática para jogos 2 - Como Utilizar Vetores
No último post conseguimos fazer com que o zumbi chegasse até a posição da heroína. Porém, ele estava se teleportando e não andando até ela. Como vamos resolver esse problema?
Até agora conseguimos calcular quanto o zumbi deve andar e para qual direção. Podemos representar a distanciaX
e distanciaY
com uma seta ligando a posição do zumbi até a posição de destino dele. Como estamos somando na posição do zumbi, a distância total entre ele e a heroína ele acaba se teletransportando até ela.
Se queremos que ele ande mais devagar, não podemos somar essa distância inteira, temos que somar menos. Mas quanto menos, será que metade dessa distância resolve?
Ainda é muito rápido, será que 4 melhora?
Ok, 4 frames é muito pouco para a jogadora fugir. Vamos dividir por um número muito maior, 100 por exemplo.
Dividindo a distância total por 100, vemos que o zumbi começa com passos largos e vai diminuindo a velocidade de perseguição. Isso acontece porque calculamos a distância entre ele e a heroína todo frame, e como essa distância é cada vez menor, temos um número cada vez menor sendo dividido por 100.
Apesar de agora o zumbi não teleportar na direção da heroína, ele anda mais devagar quando está perto dela e isso é um problema, pois ela sempre conseguirá fugir. O zumbi precisa de uma velocidade constante, e como podemos fazer?
Estamos dividindo a distância por um número arbitrário que escolhemos. O ideal seria dividirmos dois números e sempre obtermos o mesmo resultado, e podemos ter isso se dividirmos qualquer número por ele mesmo, assim o resultado sempre será um.
Ou seja, calculamos a distância e o sentido em que queremos andar e depois dividimosessa distância pelo seu próprio valor, lembrando de ignorar o sinal na hora de dividir. Como nossa distanciaX
tem o valor de -100, -100/-100 = 1 e não queremos isso, queremos o valor -1 como resultado.
Como isso fica no código?
private void Update()
{
float distanciaX = this.alvo.position.x - this.transform.position.x;
float distanciaY = this.alvo.position.y - this.transform.position.y;
float novoX = this.transform.position.x + (distanciaX / Mathf.Abs(distanciaX));
float novoY = this.transform.position.y + (distanciaY / Mathf.Abs(distanciaY));
this.transform.position = new Vector3(novoX , novoY, this.transform.position.z);
}
Assim ,o zumbi sempre andará uma unidade na horizontal e uma unidade na vertical por frame. Mas será que isso está correto?
Dessa maneira, o zumbi não seguirá uma linha reta até a direção da heroína, como falamos para ele andar uma unidade em X e em Y ele pode dar um passo para fora do caminho.
Queremos que ele dê um passo de uma unidade na direção da heroína e isso, não necessariamente, significa andar 1 na horizontal e 1 na vertical. Vamos redesenhar esse diagrama para entendermos melhor.
Desenhando dessa maneira fica visível que temos um triângulo retângulo entre os eixos X e Y. Como falamos, o que queremos é que o tamanho do nosso passo seja 1, assim, sempre que nos movermos teremos uma velocidade constante. Acontece que, se andarmos um em X e um em Y, o tamanho do nosso passo será igual a √2 que é maior que um.
O nosso passo está maior do que 1 e a direção dele não bate com a direção da jogadora. Como fazemos para deixar tudo igual?
Da mesma maneira que desenhamos um triângulo retângulo com os valores de movimentação do zumbi, podemos usar os valores da distância entre ele e a heroína.
Desenhando dessa forma achamos que a distância entre o zumbi e a jogadora é de 134,53. Como queremos que essa distância vire 1, podemos dividir ela por ela mesma. Só que também precisamos manter as proporções do nosso triângulo, ou seja devemos dividir todos os lados do triângulo por 134,53.
Agora sim, se andarmos -0,74 unidades na horizontal e -0,66 unidades na vertical estaremos dando um passo na direção da jogadora. Mas depois de tudo isso, como implementar o código?
private void Update()
{
float deslocamentoX = this.alvo.position.x - this.transform.position.x;
float deslocamentoY = this.alvo.position.y - this.transform.position.y;
float distancia = Mathf.Sqrt(Mathf.Pow(deslocamentoX, 2) + Mathf.Pow(deslocamentoY, 2));
deslocamentoX = deslocamentoX / distancia;
deslocamentoY = deslocamentoY / distancia;
float novoX = this.transform.position.x + deslocamentoX;
float novoY = this.transform.position.y + deslocamentoY;
this.transform.position = new Vector3(novoX, novoY, this.transform.position.z);
}
O primeiro passo é achar quanto nós precisamos nos deslocar em X e Y, em seguida calcular a distância entre o zumbi e o alvo. Com isso, dividimos o deslocamento nos eixos horizontal e vertical para que o passo na direção da heroína seja igual a 1. Por último, somamos a posição atual do zumbi com o deslocamento desejado e atribuímos isso à propriedade position do componente transform
.
Esse tipo de comportamento é tão comum em jogos e simulações que existem classes que encapsulam todos esse comportamento. A Unity utiliza a classe Vector3 para representar o conjunto de números que compõem a posição de um objeto e isso possibilita fazermos operações com essa classe.
Por exemplo, ao invés de procurarmos pela direção do alvo utilizando eixos separados, podemos usar a própria classe e encontrar os valores para ambos os eixos em uma única linha.
private void Update()
{
Vector3 deslocamento = this.alvo.position - this.transform.position;
}
Ao fazermos isso, recebemos um conjunto de números que representa o quanto o zumbi deve se deslocar para chegar até a heroína . Como não queremos nos teletransportar, precisamos dividir cada componente desse grupo pela distância entre os dois objetos. Esse processo que retorna os valores de X e Y para que o "passo" seja igual a 1 é chamado de normalização ou normalized
do Vector3.
private void Update()
{
Vector3 deslocamento = this.alvo.position - this.transform.position;
deslocamento = deslocamento.normalized;
}
Agora precisamos adicionar esses valores na posição atual do zumbi e atribuir isso ao componente Transform. Como nosso deslocamento é do tipo Vector3, podemos apenas somar a posição atual e o deslocamento, da mesma forma que conseguimos subtrair dois vetores.
private void Update()
{
Vector3 deslocamento = this.alvo.position - this.transform.position;
deslocamento = deslocamento.normalized;
this.transform.position = this.transform.position + deslocamento;
}
Enfim, a classe Vector3 da Unity é baseada em vetores da matemática. Eles são uma maneira prática de fazermos operações em um conjunto de números e dessa maneira podemos expressar alguns comportamentos complexos de maneira mais simples dentro do código.
Vimos um pouco de como utilizar a soma, subtração e normalização de vetores para refletir um comportamento de perseguição dentro de um jogo. Além disso eles são muito utilizados quando queremos aplicar física aos jogos.
Caso você tenha interesse, pode procurar nos tópicos de álgebra linear para entender um pouco mais de como funcionam os vetores.
Gostou da ideia de fazer um jogo sobre uma invasão zumbi? Veja nossos cursos na Alura e de quebra aprenda muito mais sobre como trabalhar com a Unity.