Os ganchos para gatos acrescentam mecânicas divertidas e interessantes ao jogo. Você pode usá-los para percorrer níveis, lutar em arenas e obter itens. Mas, apesar da aparente simplicidade, a física do gerenciamento de cordas e a criação de comportamentos realistas podem ser desafiadoras!
Na primeira parte deste tutorial, implementamos nosso próprio sistema gancho-gato bidimensional e aprendemos o seguinte:
- Crie um sistema de mira.
- Use o renderizador de linha e a junta de distância para criar a corda.
- Ensinaremos a corda a envolver objetos do jogo.
- Calcule o ângulo de giro na corda e adicione força nessa direção.
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 . Como DistanceJoint2D é usado neste tutorial, você também deve examinar as Articulações de Física no Unity 2D e só depois retornar a este tutorial.
Começando a trabalhar
Faça o download do
rascunho deste tutorial e abra-o no editor do Unity. O Unity 2017.1 ou superior é necessário para a operação.
Abra a cena do
jogo na pasta
Scenes e veja por onde começar:
Por enquanto, temos um personagem de jogador simples (lesma) e pedras penduradas no ar.
Até agora, os componentes importantes do GameObject
Player são o colisor de cápsulas e o corpo rígido, que permitem a interação com objetos físicos no nível. Além disso, um script de movimento simples (
PlayerMovement ) é anexado ao personagem, permitindo que ele deslize no chão e realize saltos simples.
Pressione o botão
Play para iniciar o jogo e tente controlar o personagem.
A e
D movem-na para a esquerda / direita e, quando você pressiona a
barra de espaço, ela salta. Tente não escorregar e cair do penhasco, senão você morrerá!
Já temos o básico de gerenciamento, mas o maior problema agora é a falta de ganchos para gatos.
Criando ganchos e cordas
A princípio, o sistema cat-hook parece bastante simples, mas para sua implementação de alta qualidade é necessário levar em consideração muitos aspectos. Aqui estão alguns dos requisitos para a mecânica bidimensional de gancho de gato:
- Renderizador de linha para exibir a corda. Quando a corda envolve objetos, podemos adicionar mais segmentos ao renderizador de linhas e colocar os vértices nos pontos correspondentes às quebras da corda.
- DistanceJoint2D. Ele pode ser usado para conectar o ponto de ancoragem atual do gancho de gato, para que nossa lesma possa balançar. Também nos permite ajustar a distância que pode ser usada para aumentar e reduzir o cabo.
- Filho de GameObject com RigidBody2D, que pode ser movido dependendo da localização atual do ponto de ancoragem do gancho. Em essência, será o ponto de suspensão / âncora da corda.
- Raycast para jogar um gancho e anexar a objetos.
Selecione o objeto
Player na Hierarquia e adicione um novo
GameObject filho chamado
RopeHingeAnchor . Este GameObject será usado para posicionar o ponto de suspensão / ancoragem do gancho de gato.
Adicione os componentes
SpriteRenderer e
RigidBody2D ao
RopeHingeAnchor .
Para SpriteRenderer, defina a propriedade
Sprite para usar o valor
UISprite e altere
Order in Layer para
2 . Desative o componente
desmarcando a caixa ao lado de seu nome.
Para o componente
RigidBody2D, defina a propriedade Tipo de corpo como
Cinemático . Este ponto será movido não pelo mecanismo físico, mas pelo código.
Selecione a camada
Corda e defina os valores de escala X e Y do componente Transform como
4 .
Selecione
Player novamente e conecte o novo componente
DistanceJoint2D .
Arraste o
RopeHingeAnchor da Hierarquia para a propriedade
Corpo Rígido Conectado do componente
DistanceJoint2D e desative a
Distância de Configuração Automática .
Crie um novo script C # chamado
RopeSystem na pasta do projeto
Scripts e abra-o no editor de código.
Remova o método
Update
.
Na parte superior do script dentro da
RopeSystem
classe
RopeSystem
adicione novas variáveis, o método
Awake()
e o novo método
Update
:
Vamos analisar cada parte em ordem:
- Usamos essas variáveis para rastrear os vários componentes com os quais o script RopeSystem irá interagir.
- O método
Awake
inicia no início do jogo e desativa o ropeJoint
(o componente DistanceJoint2D). Ele também define playerPosition
na posição atual do Player. - Essa é a parte mais importante do loop principal
Update()
. Primeiro, obtemos a posição do cursor do mouse no mundo usando o ScreenToWorldPoint
câmera ScreenToWorldPoint
. Em seguida, calculamos a direção do nosso olhar subtraindo a posição do jogador da posição do mouse no mundo. Em seguida, usamos para criar aimAngle
, que é uma representação do ângulo de mira do cursor. O valor armazena um valor positivo na construção if. aimDirection
é uma reviravolta que será útil mais tarde. Estamos interessados apenas no valor Z, pois estamos usando uma câmera 2D, e este é o único SO correspondente. Passamos aimAngle * Mathf.Rad2Deg
, que converte o ângulo do radiano em um ângulo em graus.- A posição do jogador é monitorada usando uma variável conveniente que permite que você não se refira constantemente a
transform.Position
. - Finalmente, temos a construção
if..else
, que usaremos em breve para determinar se a corda está presa ao ponto de ancoragem.
Salve o script e retorne ao editor.
Anexe o componente
RopeSystem ao objeto Player e pendure os vários componentes nos campos públicos que criamos no script
RopeSystem . Arraste
Player ,
Crosshair e
RopeHingeAnchor para os campos apropriados:
- Âncora de dobradiça de corda : RopeHingeAnchor
- Articulação de corda : Jogador
- Crosshair : Crosshair
- Sprite de mira: Crosshair
- Movimento do Jogador: Player
Agora, estamos apenas fazendo todos esses cálculos complexos, mas até agora não há visualização que possa mostrá-los em ação. Mas não se preocupe, faremos isso em breve.
Abra o script
RopeSystem e adicione um novo método a ele:
private void SetCrosshairPosition(float aimAngle) { if (!crosshairSprite.enabled) { crosshairSprite.enabled = true; } var x = transform.position.x + 1f * Mathf.Cos(aimAngle); var y = transform.position.y + 1f * Mathf.Sin(aimAngle); var crossHairPosition = new Vector3(x, y, 0); crosshair.transform.position = crossHairPosition; }
Este método posiciona a mira com base no
aimAngle
transmitido (o valor flutuante que calculamos em
Update()
) para que ele gire em torno do player com um raio de 1 unidade. Também incluímos um escopo de sprite, caso ainda não esteja pronto.
Em Update()
!ropeAttached
a construção !ropeAttached
para verificar !ropeAttached
para que fique assim:
if (!ropeAttached) { SetCrosshairPosition(aimAngle); } else { crosshairSprite.enabled = false; }
Salve o script e execute o jogo. Agora nossa lesma deve ser capaz de mirar com uma visão.
A próxima peça de lógica que precisa ser implementada é a de um gancho de gato. Já determinamos a direção da mira, por isso precisamos de um método que a receba como parâmetro.
Adicione as seguintes variáveis abaixo das variáveis no script
RopeSystem :
public LineRenderer ropeRenderer; public LayerMask ropeLayerMask; private float ropeMaxCastDistance = 20f; private List<Vector2> ropePositions = new List<Vector2>();
O LineRenderer conterá um link para um renderizador de linha que desenha a corda.
O LayerMask permite que você personalize as camadas físicas com as quais o gancho pode interagir. O valor
ropeMaxCastDistance
define a distância máxima que o raycast pode "disparar".
Finalmente, a lista de posições do Vector2 será usada para rastrear os pontos de enrolamento de corda, os quais discutiremos mais adiante.
Adicione os seguintes novos métodos:
Aqui está o que o código acima faz:
- HandleInput é chamado no loop
Update()
e simplesmente pesquisa a entrada dos botões esquerdo e direito do mouse. - Quando um clique esquerdo é registrado, a linha da corda é ativada e um radiodifusão 2D é disparado da posição do jogador na direção da mira. A distância máxima é definida para que o gancho não possa ser disparado a uma distância infinita, e uma máscara é aplicada para que seja possível selecionar as camadas da física com as quais o raycast pode colidir.
- Se um hit de raycast for detectado, o
ropeAttached
será true
e uma lista das posições dos vértices da corda será verificada para garantir que não haja nenhum ponto lá. - Se o teste retornar verdadeiro, um pequeno impulso de força será adicionado à lesma para que ela salte acima do solo, a
ropeJoint
(DistanceJoint2D) é ativada, que é definida para a distância igual à distância entre a lesma e o ponto de impacto do raio. Um sprite de ponto de ancoragem também está incluído. - Se o raycast não atingir nada, o renderizador de linha e o ropeJoint serão desativados e o sinalizador
ropeAttached
será falso. - Se o botão direito do mouse for pressionado, o método
ResetRope()
é ResetRope()
, que desativa e redefine todos os parâmetros relacionados à corda / gancho aos valores que deveriam ser se o gancho não for usado.
Na parte inferior do nosso método
Update
, adicione uma chamada ao novo método
HandleInput()
e passe o valor
aimDirection
para
aimDirection
:
HandleInput(aimDirection);
Salve as alterações em
RopeSystem.cs e retorne ao editor.
Adicionando uma corda
Nossa lesma não será capaz de voar pelo ar sem uma corda; portanto, é hora de fornecer algo que seja uma representação visual da corda e que tenha a capacidade de "virar" nos cantos.
O renderizador de linha é ideal para isso, pois permite transferir o número de pontos e sua posição no espaço do mundo.
A idéia aqui é que sempre armazenemos o primeiro vértice da corda (0) na posição do jogador, e todos os outros vértices são posicionados dinamicamente quando a corda deve envolver algo, incluindo a posição atual da dobradiça, que é o próximo ponto ao longo da corda do jogador.
Selecione
Player e adicione o componente
LineRenderer a ele. Defina
Largura como
0,075 . Expanda a lista
Materiais e, como
Elemento 0, selecione o material
RopeMaterial localizado na pasta
Materiais do projeto. Por fim, para Renderizador de linha, para
Modo de textura, selecione
Distribuir por segmento .
Arraste o componente Renderizador de linhas para o campo
Renderizador de
cordas do componente
Sistema de cordas .
Clique na lista suspensa de
Máscara de camada de
corda e selecione como camadas com as quais o raycast
Default, Rope e Pivot podem interagir. Devido a isso, ao “filmar” um raycast, ele colidirá apenas com essas camadas, mas não com outros objetos, como um jogador.
Se você iniciar o jogo agora, notará um comportamento estranho. Quando apontamos para uma pedra acima da cabeça da lesma e disparamos com um gancho, fazemos um pequeno salto para cima, após o qual nosso amigo começa a se comportar de maneira aleatória.
Ainda não definimos a distância para a junta de distância, além disso, os vértices da renderização da linha não estão configurados. Portanto, não vemos a corda e, como a junta de distância está diretamente acima da posição da lesma, o valor atual da distância da junta de distância a empurra para as pedras abaixo dela.
Mas não se preocupe, agora vamos resolver esse problema.
No script
RopeSystem.cs, adicione um novo operador no início da classe:
using System.Linq;
Isso nos permite usar consultas LINQ, que no nosso caso simplesmente nos permitem encontrar convenientemente o primeiro ou o último elemento da lista
ropePositions
.
Nota : LINQ (Consulta Integrada à Linguagem) é o nome de um conjunto de tecnologias baseadas na incorporação de recursos de consulta diretamente em C #. Você pode ler mais sobre isso aqui .
Adicione uma nova variável privada bool chamada
distanceSet
sob as outras variáveis:
private bool distanceSet;
Usaremos essa variável como uma bandeira para que o script reconheça que a distância da corda (para o ponto entre o jogador e o ponto de referência atual em que o gancho de gato está conectado) está definida corretamente.
Agora adicione um novo método que usaremos para definir as posições dos vértices da corda para renderizar a linha e definir a distância da junta de distância na lista armazenada de posições com a corda (
ropePositions
):
private void UpdateRopePositions() {
Explique o código mostrado acima:
- Saia do método se a corda não estiver presa.
- Atribuímos o valor dos pontos de renderização da linha da corda ao número de posições armazenadas
ropePositions
da ropePositions
, mais 1 a mais (para a posição do jogador). - Fazemos um loop em torno da lista
ropePositions
e em ropePositions
para cada posição (exceto a última), atribuímos a posição do vértice do renderizador de linha ao valor da posição Vector2 armazenada pelo índice de loop na lista ropePositions
. - Atribua ao ponto de ancoragem da corda o segundo da posição final da corda, na qual o atual ponto de dobradiça / âncora deve estar, ou se tivermos apenas uma posição da corda, faça dele o ponto de ancoragem. Então, definimos a distância da
ropeJoint
igual à distância entre o jogador e a posição atual da corda, que ropeJoint
no loop. - A construção if lida com o caso em que a posição atual da corda no loop é a segunda do final; ou seja, o ponto em que a corda se conecta ao objeto, ou seja ponto atual da dobradiça / âncora.
- Esse
else
bloco lida com a atribuição da posição do último vértice da corda ao valor da posição atual do jogador.
Lembre-se de adicionar a chamada
UpdateRopePositions()
no final de
Update()
UpdateRopePositions()
:
UpdateRopePositions();
Salve as alterações no script e execute o jogo novamente. Faça um “pequeno espaço” um pequeno salto, mirando e atirando em um gancho em uma pedra acima do personagem. Agora você pode apreciar os frutos de seu trabalho - a lesma balança calmamente sobre as pedras.
Agora você pode ir para a janela de cena, selecionar Player, usar a ferramenta Move (por padrão, a tecla
W ) para movê-la e observar como os dois vértices da linha da corda renderizam seguem a posição do gancho e a posição do jogador para puxar a corda. Depois de liberarmos o player, o DistanceJoint2D calcula corretamente a distância e a lesma continuará balançando na dobradiça conectada.
Manipulação de pontos de quebra automática
Até agora, brincar com uma lesma balançando não é mais útil do que uma toalha que repele a água; portanto, precisamos definitivamente complementá-la.
A boa notícia é que o método recém-adicionado para processar posições de corda pode ser usado no futuro. Até agora, estamos usando apenas duas posições de corda. Um é conectado à posição do jogador e o segundo à posição atual do ponto de ancoragem do gancho ao atirar nele.
O único problema é que, enquanto não rastreamos todas as posições potenciais da corda, isso precisa de um pouco de trabalho.
Para reconhecer as posições nas pedras em torno das quais a corda deve ser enrolada, adicionando uma nova posição de vértice ao renderizador de linhas, precisamos de um sistema que determine se o ponto de vértice do colisor está entre a linha reta entre a posição atual da lesma e o atual ponto de dobradiça / âncora da corda.
Parece que isso é trabalho novamente para o bom e velho raycast!
Primeiro, precisamos criar um método que possa encontrar o ponto mais próximo no colisor com base no ponto de impacto do raycast e nas bordas do colisor.
Adicione um novo método ao script
RopeSystem.cs :
Se você não estiver familiarizado com as consultas LINQ, esse código pode parecer algum tipo de mágica em C # complicada.
Se sim, então não tenha medo. O LINQ trabalha muito para nós:
- Este método usa dois parâmetros - o objeto RaycastHit2D e o objeto PolygonCollider2D . Todas as pedras no nível têm coletores PolygonCollider2D; portanto, se sempre usarmos formas PolygonCollider2D, funcionará bem.
- É aqui que a mágica da consulta LINQ começa! Aqui, transformamos a coleção de pontos do colisor poligonal em um dicionário de posições Vector2 (o valor de cada elemento do dicionário é a própria posição), e a chave de cada elemento recebe o valor da distância desse ponto no jogador de posição do jogador (valor flutuante). Às vezes, algo mais acontece aqui: a posição resultante é convertida em espaço do mundo (por padrão, as posições dos vértices do colisor são armazenadas no espaço local, isto é, local em relação ao objeto ao qual o colisor pertence e precisamos de posições no espaço do mundo).
- O dicionário é classificado por chave. Em outras palavras, pela distância mais próxima da posição atual do jogador. A distância mais próxima é retornada, ou seja, qualquer ponto retornado por esse método é o ponto de colisão entre o jogador e o ponto atual da dobradiça do cabo!
Vamos voltar ao script
RopeSystem.cs e adicionar uma nova variável de campo privado na parte superior:
private Dictionary<Vector2, int> wrapPointsLookup = new Dictionary<Vector2, int>();
Vamos usá-lo para rastrear as posições em torno das quais a corda pode enrolar.
No final do método
Update()
, localize a construção
else
que contém
crosshairSprite.enabled = false;
e adicione o seguinte:
Explique este trecho de código:
- Se algumas posições forem armazenadas na lista
ropePositions
, então ... - Filmamos a partir da posição do jogador na direção do jogador que olha a última posição da corda da lista - o ponto de referência no qual o gancho do gato se conectou à pedra - com uma distância de raio igual à distância entre o jogador e a posição do ponto de referência da corda.
- Se o raycast colidir com algo, o colisor deste objeto será convertido com segurança para o tipo PolygonCollider2D . Embora seja um PolygonCollider2D verdadeiro, a posição do vértice mais próximo desse colisor é retornada usando o método que escrevemos anteriormente como Vector2 .
- Ele é verificado pelo
wrapPointsLookup
para garantir que a mesma posição não seja verificada novamente. Se estiver marcado, descartamos a corda e a cortamos, deixando o jogador cair. - Em seguida, a lista
ropePositions
é ropePositions
: a posição é adicionada em torno da qual a corda deve ser enrolada. O dicionário wrapPointsLookup
também é atualizado. Finalmente, o sinalizador distanceSet
é redefinido para que o método UpdateRopePositions()
possa redefinir a distância da corda com o novo comprimento da corda e segmentos.
Em
ResetRope()
adicione a seguinte linha para que o dicionário
wrapPointsLookup
limpo toda vez que um jogador desconectar uma corda:
wrapPointsLookup.Clear();
Salve e inicie o jogo. Atire o gancho do gato na pedra acima da lesma e use a ferramenta Mover na janela Cena para mover a lesma sobre várias bordas da pedra.
Foi assim que ensinamos a corda a envolver objetos!
Adicionar capacidade de balanço
A lesma pendurada na corda é bem estática. Para corrigir isso, podemos adicionar a capacidade de oscilação.
Para fazer isso, precisamos obter uma posição perpendicular à posição de balançar para a frente (lateralmente), independentemente do ângulo em que ele está olhando.
Abra
PlayerMovement.cs e adicione as duas variáveis públicas a seguir na parte superior do script:
public Vector2 ropeHook; public float swingForce = 4f;
A variável
ropeHook
receberá uma posição em que o gancho da corda está localizado no momento, e
swingForce
é o valor que usamos para adicionar o movimento de giro.
Substitua o método
FixedUpdate()
novo:
void FixedUpdate() { if (horizontalInput < 0f || horizontalInput > 0f) { animator.SetFloat("Speed", Mathf.Abs(horizontalInput)); playerSprite.flipX = horizontalInput < 0f; if (isSwinging) { animator.SetBool("IsSwinging", true);
As principais mudanças aqui são que a bandeira é verificada primeiro isSwinging
para que as ações sejam executadas apenas quando a lesma está pendurada na corda, e também adicionamos uma perpendicular ao canto da lesma, indicando seu ponto de ancoragem atual na parte superior da corda, mas perpendicular à direção de seu balanço.- Obtemos o vetor de direção normalizado do player para o ponto de conexão do gancho.
- Dependendo se a lesma balança para a esquerda ou para a direita, a direção perpendicular é calculada usando
playerToHookDirection
. Uma chamada de depuração também foi adicionada para que você possa vê-la no editor, se desejar.
Abra o RopeSystem.cs e adicione o seguinte na parte superior do bloco else, dentro do if(!ropeAttached)
método Update()
: playerMovement.isSwinging = true; playerMovement.ropeHook = ropePositions.Last();
No bloco if do mesmo design, if(!ropeAttached)
adicione o seguinte: playerMovement.isSwinging = false;
Assim, informamos ao script PlayerMovement que o jogador está oscilando e também determinamos a última (exceto a posição do jogador) da corda - em outras palavras, o ponto de ancoragem da corda. Isso é necessário para calcular o ângulo perpendicular que acabamos de adicionar ao script PlayerMovement.Aqui está como é que você liga os aparelhos em um jogo de corrida e pressiona A ou D para girar para a esquerda / direita:Adicionando descida de corda
Embora não tenhamos a capacidade de subir e descer a corda. Embora na vida real a lesma não possa subir e cair facilmente ao longo da corda, mas este é um jogo no qual tudo pode acontecer, certo?Na parte superior do script RopeSystem, adicione duas novas variáveis de campo: public float climbSpeed = 3f; private bool isColliding;
climbSpeed
definirá a velocidade na qual a lesma pode subir e descer a corda e isColliding
será usado como uma bandeira para determinar se a propriedade da junta de distância da corda da junta de distância pode ser aumentada ou diminuída.Adicione este novo método: private void HandleRopeLength() {
Este bloco if..elseif
lê a entrada ao longo do eixo vertical (para cima / baixo ou W / S no teclado) e, levando em consideração as bandeiras, ropeAttached iscColliding
aumenta ou diminui a distância ropeJoint
, criando o efeito de alongar ou encurtar o cabo.Conectamos esse método, adicionando sua chamada ao final Update()
: HandleRopeLength();
Também precisamos de uma maneira de definir a bandeira isColliding
.Adicione os dois métodos a seguir na parte inferior do script: void OnTriggerStay2D(Collider2D colliderStay) { isColliding = true; } private void OnTriggerExit2D(Collider2D colliderOnExit) { isColliding = false; }
Esses dois métodos são nativos da classe base dos scripts MonoBehaviour.Se atualmente o Collider tocar em outro objeto físico no jogo, o método será acionado constantemente OnTriggerStay2D
, atribuindo um isColliding
valor à bandeira true
. Isso significa que quando a lesma toca a pedra, é atribuído um valor à bandeira isColliding true
.O método é OnTriggerExit2D
acionado quando um colisor sai da área de outro colisor, configurando o sinalizador como falso.Lembre-se: o método OnTriggerStay2D
pode ser muito computacionalmente caro, portanto, use-o com cuidado.Para onde ir a seguir?
Inicie o jogo novamente e desta vez pressione as teclas de seta ou W / S para mover para cima e para baixo na corda.O projeto final desta parte do tutorial pode ser baixado aqui .Percorremos um longo caminho - desde a lesma que não balança até o molusco gastrópode acrobático sem casca!Você aprendeu a criar um sistema de mira capaz de disparar um gancho de gato em qualquer objeto que tenha um colisor, agarrar-se a ele e balançar simultaneamente nele, girando em uma corda dinâmica em torno das bordas dos objetos! Bom trabalho.No entanto, uma função importante está faltando aqui - a corda não pode "desenrolar" quando necessário.Na segunda parte do tutorial, resolveremos esse problema.Mas se você estiver disposto a arriscar, por que não tentar fazer isso sozinho? Você pode usar um dicionário para isso wrapPointsLookup
.