Criando um gancho para gatos no Unity. Parte 2

imagem

Nota : este tutorial é destinado a usuários avançados e experientes e não abrange tópicos como adição de componentes, criação de novos scripts GameObject e sintaxe C #. Se você precisar aprimorar suas habilidades no Unity, consulte nossos tutoriais Introdução ao Unity e Introdução ao script de unidade .

Na primeira parte do tutorial, aprendemos como criar um gancho para gatos com a mecânica de enrolar uma corda em torno de obstáculos. No entanto, queremos mais: a corda pode envolver objetos ao nível, mas não se solta quando você retorna.

Começando a trabalhar


Abra o projeto finalizado da primeira parte do Unity ou faça o download do rascunho desta parte do tutorial e abra 2DGrapplingHook-Part2-Starter . Como na primeira parte, usaremos o Unity versão 2017.1 ou superior.

Abra a cena do jogo no editor na pasta do projeto Scenes .


Inicie a cena do jogo e tente prender o gancho do gato nas pedras acima do personagem e depois gire para fazer a corda envolver um par de bordas de pedra.

Ao retornar, você notará que os pontos da pedra através dos quais a corda costumava girar não se soltam novamente.


Pense no ponto em que a corda deve se desdobrar. Para simplificar a tarefa, é melhor usar o estojo quando a corda envolver as bordas.

Se a lesma, agarrada a uma pedra acima de sua cabeça, girar para a direita, a corda dobrará após o limiar no qual cruza o ponto de ângulo de 180 graus com a nervura à qual a lesma está atualmente presa. Na figura abaixo, é mostrado por um ponto verde destacado.


Quando a lesma balança de volta na outra direção, a corda deve soltar novamente no mesmo ponto (destacada em vermelho na figura acima):


A lógica de desvendar


Para calcular o momento em que você precisa desenrolar a corda nos pontos em que foi enrolada anteriormente, precisamos de conhecimento de geometria. Em particular, usaremos uma comparação de ângulos para determinar quando a corda deve se soltar da borda.

Essa tarefa pode parecer um pouco intimidadora. A matemática pode inspirar horror e desespero, mesmo nos mais corajosos.

Felizmente, o Unity tem ótimas funções auxiliares de matemática que podem facilitar nossa vida.

Abra o script RopeSystem no IDE e crie um novo método chamado HandleRopeUnwrap() .

 private void HandleRopeUnwrap() { } 

Vá para Update() e adicione no final uma chamada ao nosso novo método.

 HandleRopeUnwrap(); 

Enquanto HandleRopeUnwrap() não faz nada, mas agora podemos processar a lógica associada a todo o processo de desanexação das bordas.

Como você se lembra da primeira parte do tutorial, armazenamos as posições de quebra de corda em uma coleção chamada ropePositions , que é uma coleção List<Vector2> . Sempre que uma corda envolve uma borda, mantemos a posição desse ponto de empacotamento nesta coleção.

Para tornar o processo mais eficiente, não executaremos nenhuma lógica em HandleRopeUnwrap() se o número de posições armazenadas na coleção for igual ou menor que 1.

Em outras palavras, quando a lesma estiver conectada ao ponto inicial e sua corda ainda não estiver enrolada nas bordas, o número ropePositions de ropePositions será 1 e não seguiremos a lógica de processamento sem torção.

Adicione esta return simples na parte superior de HandleRopeUnwrap() para salvar valiosos ciclos da CPU, porque esse método é chamado de Update() muitas vezes por segundo.

 if (ropePositions.Count <= 1) { return; } 

Adicionando novas variáveis


Sob esse novo teste, adicionaremos algumas dimensões e referências aos diferentes ângulos necessários para implementar a base da lógica sem torção. Adicione o seguinte código ao HandleRopeUnwrap() :

 // Hinge =       // Anchor =     Hinge // Hinge Angle =   anchor  hinge // Player Angle =   anchor  player // 1 var anchorIndex = ropePositions.Count - 2; // 2 var hingeIndex = ropePositions.Count - 1; // 3 var anchorPosition = ropePositions[anchorIndex]; // 4 var hingePosition = ropePositions[hingeIndex]; // 5 var hingeDir = hingePosition - anchorPosition; // 6 var hingeAngle = Vector2.Angle(anchorPosition, hingeDir); // 7 var playerDir = playerPosition - anchorPosition; // 8 var playerAngle = Vector2.Angle(anchorPosition, playerDir); 

Existem muitas variáveis ​​aqui, então vou explicar cada uma delas, além de adicionar uma ilustração conveniente que ajudará a entender seu objetivo.

  1. anchorIndex é o índice na coleção ropePositions em duas posições do final da coleção. Podemos considerá-lo como um ponto em duas posições na corda a partir da posição da lesma. Na figura abaixo, este é o primeiro ponto de fixação do gancho à superfície. Ao preencher a coleção ropePositions novos pontos de empacotamento, esse ponto sempre permanecerá o ponto de empacotamento a uma distância de duas posições da lesma.
  2. hingeIndex é o índice da coleção que armazena o ponto da dobradiça atual; em outras palavras, a posição na qual a corda está enrolando no ponto mais próximo do final da corda da lesma. Está sempre a uma distância de uma posição da lesma, e é por isso que usamos ropePositions.Count - 1 .
  3. anchorPosition calculado referenciando o lugar anchorIndex na coleção ropePositions e é o valor simples Vector2 dessa posição.
  4. hingePosition calculado referenciando o local de hingeIndex na coleção ropePositions e é o valor simples Vector2 dessa posição.
  5. hingeDir é um vetor direcionado de anchorPosition para hingePosition . É usado na variável a seguir para obter o ângulo.
  6. hingeAngle - a útil função auxiliar Vector2.Angle() é usada aqui para calcular o ângulo entre anchorPosition e o ponto de dobradiça.
  7. playerDir é um vetor direcionado de anchorPosition para a posição atual da lesma (playerPosition)
  8. Em seguida, usando o ângulo entre o ponto de ancoragem e o jogador (lesma), o playerAngle calculado.


Todas essas variáveis ​​são calculadas usando posições armazenadas como valores de Vector2 na coleção ropePositions e comparando essas posições com outras posições ou a posição atual do jogador (lesma).

As duas variáveis ​​importantes usadas para comparação são hingeAngle e hingeAngle .

O valor armazenado em hingeAngle deve permanecer estático, pois é sempre um ângulo constante entre o ponto nas duas "dobras da corda" da lesma e a atual "dobra da corda" mais próxima da lesma que não se move até que a corda seja destorcida ou depois de ser dobrada um novo ponto de curvatura será adicionado.

Quando a lesma está playerAngle muda. Comparando esse ângulo com hingeAngle e também verificando se a lesma está à esquerda ou à direita deste canto, podemos determinar se o ponto de dobra atual mais próximo à lesma deve ser destacado.

Na primeira parte deste tutorial, salvamos as posições de dobra em um dicionário chamado wrapPointsLookup . Cada vez que salvamos o ponto de dobra, o adicionamos ao dicionário com a posição como chave e com 0 como valor. No entanto, esse valor de 0 era misterioso, certo?

Usaremos esse valor para armazenar a posição da lesma em relação ao seu ângulo com o ponto de articulação (o ponto de dobra atual mais próximo da lesma).

Se você atribuir um valor -1 , o ângulo da lesma ( playerAngle ) será menor que o ângulo da dobradiça (dobradiçaAngle) e, com o valor 1, o ângulo da playerAngle maior que a hingeAngle .

Devido ao fato de salvarmos os valores no dicionário, toda vez que comparamos hingeAngle com hingeAngle , podemos entender se a lesma acabou de ultrapassar o limite após o qual a corda deve ser desengatada.

Pode ser explicado de maneira diferente: se o ângulo da lesma acabou de ser verificado e é menor que o ângulo da dobradiça, mas a última vez que foi salva no dicionário de pontos de dobra, foi marcada com um valor indicando que estava do outro lado desse canto, então o ponto deve ser removido imediatamente !

Desacoplar corda


Dê uma olhada na captura de tela abaixo com notas. Nossa lesma se agarrou à rocha, balançou para cima, enrolando uma corda em volta da borda da rocha em seu caminho.


Você pode notar que, na posição de giro mais alta, onde a lesma é opaca, seu ponto de dobra mais próximo atual (marcado com um ponto branco) será armazenado no dicionário wrapPointsLookup com o valor 1 .

No caminho para baixo, quando o playerAngle se torna menor que o hingeAngle (duas linhas verdes tracejadas), como mostra a seta azul, é realizada uma verificação e, se o último valor (atual) do ponto de curvatura for 1 , o ponto de curvatura deverá ser removido.

Agora vamos implementar essa lógica no código. Mas antes de começarmos, vamos criar um espaço em branco do método que usaremos para relaxar. Devido a isso, após criar a lógica, ela não levará a um erro.

Adicione um novo método UnwrapRopePosition(anchorIndex, hingeIndex) inserindo as seguintes linhas:

 private void UnwrapRopePosition(int anchorIndex, int hingeIndex) { } 

Feito isso, volte para HandleRopeUnwrap() . Nas variáveis ​​recém-adicionadas, adicione a seguinte lógica, que tratará de dois casos: playerAngle menos que hingeAngle e hingeAngle mais que hingeAngle :

 if (playerAngle < hingeAngle) { // 1 if (wrapPointsLookup[hingePosition] == 1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 2 wrapPointsLookup[hingePosition] = -1; } else { // 3 if (wrapPointsLookup[hingePosition] == -1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 4 wrapPointsLookup[hingePosition] = 1; } 

Este código deve corresponder à explicação da lógica descrita acima para o primeiro caso (quando hingeAngle < hingeAngle ), mas também lida com o segundo caso (quando hingeAngle > hingeAngle ).

  1. Se o ponto de dobra atual mais próximo da lesma tiver um valor de 1 no ponto em que hingeAngle < hingeAngle , removeremos esse ponto e realizaremos um retorno para que o restante do método não seja executado.
  2. Caso contrário, se o ponto de dobra não tiver sido marcado pela última vez com um valor de 1 , mas playerAngle menor que hingeAngle , -1 será atribuído.
  3. Se o ponto de dobra atual mais próximo da lesma for -1 no ponto em que hingeAngle > hingeAngle , remova o ponto e retorne.
  4. Caso contrário, atribuímos as entradas no dicionário de pontos de dobra na posição da dobradiça para 1 .

Esse código garante que o dicionário wrapPointsLookup sempre atualizado, garantindo que o valor do ponto de dobra atual (mais próximo da lesma) corresponda ao ângulo da lesma atual em relação ao ponto de dobra.

Não se esqueça de que o valor é -1 quando o ângulo da barra é menor que o ângulo da dobradiça (em relação ao ponto de referência) e 1 quando o ângulo da barra é maior que o ângulo da dobradiça.

Agora, UnwrapRopePosition() no script RopeSystem com um código que se envolverá diretamente no desacoplamento, movendo a posição de referência e atribuindo um novo valor de distância ao valor da distância da corda DistanceJoint2D. Adicione as seguintes linhas ao disco do método criado anteriormente:

  // 1 var newAnchorPosition = ropePositions[anchorIndex]; wrapPointsLookup.Remove(ropePositions[hingeIndex]); ropePositions.RemoveAt(hingeIndex); // 2 ropeHingeAnchorRb.transform.position = newAnchorPosition; distanceSet = false; // Set new rope distance joint distance for anchor position if not yet set. if (distanceSet) { return; } ropeJoint.distance = Vector2.Distance(transform.position, newAnchorPosition); distanceSet = true; 

  1. O índice do ponto de ancoragem atual (a segunda posição da corda da lesma) torna-se a nova posição da dobradiça, e a posição antiga da dobradiça é removida (a que estava anteriormente mais próxima da lesma e que agora estamos "desenrolando"). A variável newAnchorPosition recebe o valor anchorIndex na lista de posições da corda. Ele será usado para posicionar a posição atualizada do ponto de ancoragem.
  2. A junta de cabo RigidBody2D (à qual o cabo DistanceJoint2D está conectado) muda sua posição para a nova posição do ponto de ancoragem. Isso garante um movimento contínuo suave da lesma na corda quando ela está conectada ao DistanceJoint2D, e essa conexão deve permitir que ela continue a balançar em relação à nova posição, que se tornou a referência - em outras palavras, em relação ao próximo ponto da corda a partir de sua posição.
  3. Em seguida, é necessário atualizar o valor da distância distanceJoint2D para levar em consideração uma mudança acentuada na distância da lesma ao novo ponto de referência. Se isso ainda não tiver sido feito, uma verificação rápida do sinalizador distanceSet é executada e a distância recebe o valor da distância calculada entre a lesma e a nova posição do ponto de ancoragem.

Salve o script e retorne ao editor. Comece o jogo novamente e observe como a corda se desprende das bordas quando a lesma passa os valores limite de cada ponto de curvatura!


Embora a lógica esteja pronta, adicionaremos algum código auxiliar ao HandleRopeUnwrap() antes de comparar o hingeAngle com o hingeAngle ( if (playerAngle < hingeAngle) ).

 if (!wrapPointsLookup.ContainsKey(hingePosition)) { Debug.LogError("We were not tracking hingePosition (" + hingePosition + ") in the look up dictionary."); return; } 

Na verdade, isso não deve acontecer, porque redefinimos e desconectamos o gancho do gato quando ele envolve uma costela duas vezes, mas se isso ainda acontecer, podemos sair facilmente desse método com uma return simples e uma mensagem de erro no console.

Além disso, graças a isso, lidaremos mais convenientemente com esses casos limitantes; além disso, recebemos nossa própria mensagem de erro caso algo desnecessário ocorra.

Para onde ir a seguir?


Aqui está um link para o projeto finalizado desta segunda e última parte do tutorial.

Parabéns por concluir esta série de tutoriais! Quando se trata de comparar ângulos e posições, tudo se tornou bastante complicado, mas nós sobrevivemos a isso e agora temos um maravilhoso sistema de gancho e corda que pode acabar com objetos no jogo.


Você sabia que nossa equipe de desenvolvimento do Unity escreveu um livro? Caso contrário, confira o Unity Games By Tutorials . Este jogo ensinará como criar quatro jogos prontos do zero:

  • Atirador com dois bastões
  • Atirador em primeira pessoa
  • Jogo de defesa de torre (com suporte a VR!)
  • Jogo de plataformas 2D

Depois de ler este livro, você aprenderá a criar seus próprios jogos para Windows, macOS, iOS e outras plataformas!

Este livro é destinado a iniciantes e aqueles que desejam atualizar suas habilidades em Unity para um nível profissional. Para dominar o livro, você precisa ter experiência em programação (em qualquer idioma).

Source: https://habr.com/ru/post/pt415217/


All Articles