GameDev

Aprendendo a programar jogos em Unity: delimitando espaço e implementando regras com Colliders e Triggers

Vamos introduzir em nosso projeto delimitadores e colisores que permitirão rebater a bolinha e marcar pontos.

em 09/09/2023
Seja bem-vindo(a) ao GameDev: Aprendendo a programar jogos em Unity de hoje! Dando andamento ao nosso projeto de game, começaremos a implementar alguns elementos que darão forma real às regras do jogo, como as dimensões da tela em que ocorre a partida e os elementos rebatedores da bolinha.


Caso esteja acessando pela primeira vez nossa série, não se preocupe: a partir do primeiro tópico de nossa jornada você irá aprender desde a instalação e configuração do Unity até os conceitos que iremos abordar aqui, como a criação de scripts, assunto esse que foi abordado em nosso último tópico. Sinta-se à vontade para nos acompanhar nesta caminhada rumo a novos conhecimentos!

Barreiras invisíveis

Vamos “dar um tempo” às cobras e pensar em uma questão bem interessante: como delimitar qual é o espaço em que minha bolinha pode “pingar” pela tela e, também, em quais posições da tela são atribuídos pontos ao jogador caso a bolinha passe por elas?

Para desenvolvermos essas ideias, vamos colocar a mão na massa: no Unity Hub abra o projeto Forest Ping Pong e acesse a aba Scene, conforme imagem a seguir. Nós iremos criar alguns elementos invisíveis que servirão como “barreiras”, a fim de conter a bolinha no espaço correto do jogo.

No editor, especificamente na aba Hierarchy, clique com o botão direito sobre o objeto Canvas e selecione no menu a opção Create Empty:

Dê o nome de GolEsquerda para esse objeto. Da mesma forma, crie outro objeto e lhe conceda o nome de GolDireita.


Selecione GolEsquerda para o posicionarmos adequadamente na tela. Na aba Inspector, em RectTransform clique sobre o botão de posicionamento (em destaque na imagem a seguir) e escolha o item Middle left. Seu valor de Width será 50 e de Height será 600, posições X e Y terão valor 0:

Faça o mesmo com o objeto GolDireita, mas desta vez o posicionamento selecionado será Middle right (os demais parâmetros serão iguais aos do GameObject GolEsquerda):

Agora criaremos as barreiras invisíveis que ficarão posicionadas nos cantos superior e inferior do campo de jogo. Na aba Hierarchy, clique com o botão direito sobre o objeto Canvas, selecione no menu a opção Create Empty e crie um objeto de nome LimiteSuperior. Repita os passos e crie também um objeto de nome LimiteInferior:


Selecione o GameObject LimiteSuperior para editarmos seus parâmetros de RectTransform: o posicionamento selecionado será Top center, Width com valor igual a 960, Height com valor igual a 25, posicionamento X terá valor 0 e posicionamento Y terá valor -100:

Agora vamos ajustar o GameObject LimiteInferior: o posicionamento selecionado será Bottom center, Width com valor igual a 960, Height com valor igual a 25, posicionamento X e Y terão valor 0:

Criamos até o momento os GameObjects que irão delimitar a área do jogo. A partir de agora vamos começar a desenvolver alguns scripts importantes para o desenvolvimento do jogo em si, começando pelo script responsável por armazenar o placar do jogo.

Na aba Project, dentro da pasta Scripts, clique com o botão direito em uma área vazia, selecione a opção Create e, em seguida, a opção C# Script. Para esse script dê o nome de controladorJogo e pressione Enter.

Assim como no caso do nosso primeiro script desenvolvido, o Unity já nos fornece dentro do script uma estrutura básica, conforme imagem a seguir:

Nesse caso em específico nós não iremos aproveitar a estrutura básica que nos é oferecida: iremos alterar a estrutura do script para tornar controladorJogo uma classe estática. Em programação orientada a objetos, uma classe estática não precisa ser instanciada para ser acessada pelos demais scripts e objetos; em termos práticos, não teremos que atrelar controladorJogo a nenhum objeto do Unity para que os scripts em geral possam ler e escrever os dados contidos ali.

Dadas as explicações, mãos à obra: selecione todo o código presente a partir da linha 5 (public class controladorJogo…) e substitua-o pelo conteúdo a seguir:

public static class controladorJogo()
{
  public static int PlacarJogador = 0;
  public static int PlacarAdversario = 0;
}

Temos agora duas variáveis públicas e estáticas, PlacarJogador e PlacarAdversario, responsáveis por armazenar, como os nomes já sugerem, o placar dos dois oponentes na partida em questão.

Salve o script, minimize o Visual Studio e volte ao Unity, pois criaremos mais alguns scripts bem interessantes. Na aba Project, dentro da pasta Scripts, clique com o botão direito em uma área vazia, selecione a opção Create e, em seguida, a opção C# Script. O nome desse novo script será controladorBola.

Clique duas vezes sobre o ícone do script controladorBola, que será aberto no Visual Studio. Vamos fazer grandes alterações no corpo do código. Primeiro, na região anterior ao método void Start(), insira o seguinte bloco de código:

internal int DirecaoVertical = 1;
internal int DirecaoHorizontal = 1;
public float VelocidadeBola;

As variáveis DirecaoVertical e DirecaoHorizontal serão utilizadas para informar à bolinha se ela deve seguir um trajeto positivo ou negativo na vertical (para cima ou para baixo) e na horizontal (para a direita ou para a esquerda), respectivamente. Já a variável VelocidadeBola servirá para fazermos o ajuste da velocidade da bolinha; sendo ela uma variável pública, poderá ser editada diretamente na interface gráfica do Unity.

Ainda com a janela do código aberta, preencha o código a seguir dentro das chaves { } do método void Update():

gameObject.transform.position = new Vector2(gameObject.transform.position.x + (VelocidadeBola * DirecaoHorizontal * Time.deltaTime * 60), gameObject.transform.position.y + (VelocidadeBola * DirecaoVertical * Time.deltaTime * 60));

O código inserido é o responsável por, a cada frame, movimentar um pouquinho a bolinha nos eixos X e Y na velocidade indicada (VelocidadeBola) e direção correspondente (DirecaoHorizontal e DirecaoVertical). Como os códigos que são inseridos no método void Update() são executados constantemente, a bolinha vai se movimentar continuamente.

Salve o script, minimize o Visual Studio e volte ao editor do Unity. Selecione o GameObject Bolinha e, na aba Inspector, clique em Add Component, Scripts e, por fim, em Controlador Bola, para inserirmos esse comportamento em nossa bolinha virtual.

Vamos dar inicialmente o valor 4 para a velocidade do objeto, conforme a imagem a seguir ilustra:


Agora, que tal darmos uma olhada no comportamento real da bolinha? Para isso, acesse a aba Game e pressione o botão Play.

Pois é, já temos movimento em nossa bolinha, mas quando ela começa a viajar... some do cenário. Por isso agora iremos configurar nossos limitadores e “gols” para barrar essa viagem sem fim. Terminando a simulação na aba Game (pressionando novamente o botão Play), volte à aba Scene e selecione os objetos LimiteSuperior e LimiteInferior.

Na aba Inspector, clique em Add Component e na caixa de pesquisa digite a palavra "box" (sem as aspas), para aparecer, dentre as opções disponíveis, Box Collider 2D. Insira esse elemento para ambos os GameObjects.

Dica: caso tenha inserido mais de um componente do mesmo tipo em um determinado objeto e precise removê-lo (para ficar com apenas um, por exemplo), basta clicar nos três pontinhos na extremidade direita do componente e selecionar Remove Component. Em nossos exemplos do projeto iremos utilizar apenas um componente de cada tipo para cada objeto.


Agora que já inserimos esse componente, tanto para LimiteInferior quanto para LimiteSuperior, na aba Inspector mantenha selecionado o item Is Trigger para o Box Collider 2D e, nos campos Size, conceda o valor 960 para o campo X e 25 para o campo Y, conforme imagem a seguir:


Iremos inserir o mesmo tipo de componente para os objetos GolEsquerda e GolDireita. Repita os passos que utilizamos para inserir os Box Collider 2D anteriormente. O item Is Trigger deverá ser mantido selecionado e, nos campos Size, preencha X com o valor 50 e Y com o valor 600, conforme ilustra a imagem abaixo:

Colliders e Triggers

Até o momento inserimos esses componentes nos objetos, mas, de fato, do que se tratam os tais Colliders? Trata-se de uma categoria de componentes que permitem ao objeto detectar se houve uma colisão com outro objeto dentro da área determinada pelo Collider por meio de "gatilhos" (Triggers), acionados e detectados pelo código dos scripts. Um exemplo de componente da categoria é o Box Collider 2D. Mais à frente em nosso trajeto de aprendizado iremos conhecer os pormenores do funcionamento dos Colliders.

Agora, selecione o GameObject Bolinha e adicione um componente a ele; desta vez não utilizaremos um Box Collider 2D, mas sim um Circle Collider 2D. O item Is Trigger desta vez não deverá ser mantido selecionado e, para o campo Radius, insira o valor 25, conforme imagem abaixo:

Além do Collider, para esse objeto insira também um componente do tipo Rigidbody 2D, deixando seus parâmetros semelhantes aos indicados na imagem a seguir:


O componente Rigidbody 2D atua dando um "corpo físico" a um objeto, o que irá auxiliar no processo de detecção dos Colliders. Veremos mais sobre isso em textos posteriores.

Com os componentes atrelados aos objetos, é hora de codificarmos os scripts que se aproveitarão dos comportamentos observados para tomar ações e decisões bem interessantes. Na aba Project, abra a pasta Scripts, clique com o botão direito em uma área vazia, clique em Create e, em seguida, C# Script. Dê a esse script o nome de limite e pressione Enter.

Clique duas vezes sobre o ícone do script limite; ele será aberto no Visual Studio. Vamos substituir o conteúdo do script referente aos métodos padrão (void Start() e void Update()) pelo código a seguir:

private void OnTriggerEnter2D(Collider2D other)
{
  if (other.gameObject.name.StartsWith("Bolinha"))
  other.gameObject.GetComponent<controladorBola>().DirecaoVertical = other.gameObject.GetComponent<controladorBola>().DirecaoVertical * -1;
}

Nesse trecho de código estamos utilizando um método diferente do que já observamos até o momento: trata-se do OnTriggerEnter2D, que só é executado caso algum objeto colida com o objeto que está com um componente Collider atrelado.

O código em si fará o seguinte: caso o objeto colisor (other) tenha o nome iniciado pelo texto “Bolinha”, seu componente controladorBola terá o sinal do valor da variável DirecaoVertical “invertido”. Na prática, se a bolinha bateu no limite superior, irá descer; se bateu no limite inferior, irá subir.

Salve o script no Visual Studio e vamos voltar ao Unity. Adicione aos GameObjects LimiteSuperior e LimiteInferior o componente Limite (aba Inspector, botão Add Component, ScriptsLimite); assim ambos estarão habilitados a rebater a bolinha.

Na aba Project, abra a pasta Scripts e crie um script novo, cujo nome será gol, assim como o demonstrado na imagem de referência a seguir:

Clique duas vezes sobre o ícone do script gol para que ele seja aberto no Visual Studio. Assim como o feito para o script limite, iremos substituir o conteúdo interno “padrão” por um código personalizado, descrito abaixo:

public bool GolDaEsquerda, GolDaDireita;
public UnityEngine.UI.Text TextoPlacarJogador, TextoPlacarAdversario;
 
private void OnTriggerEnter2D(Collider2D other)
{
  if (other.gameObject.name.StartsWith("Bolinha")
  {
      if (GolDaEsquerda == true)
            controladorJogo.PlacarAdversario = controladorJogo.PlacarAdversario + 1;
      else if (GolDaDireita == true)
            controladorJogo.PlacarJogador = controladorJogo.PlacarJogador + 1;
 
      //Ajustar o placar:
      TextoPlacarJogador.text = "Jogador: " + controladorJogo.PlacarJogador;
      TextoPlacarAdversario.text = "Adversário: " + controladorJogo.PlacarAdversario;
 
      //Voltar bolinha para o centro da tela:
      other.gameObject.transform.localPosition = new Vector2(0,0);
        }
}

Quanto código, não é mesmo? Vamos analisar por partes o fluxo desse código para entendermos melhor o que o script fará.
  • Primeiro declaramos algumas variáveis para que o script saiba se o objeto ao qual ele está atrelado é o “gol” da esquerda ou da direita; também declaramos variáveis para indicar quais objetos são os que terão o texto alterado ao se marcar um ponto (no caso, os placares na tela).
  • Após as declarações das variáveis, dentro do método OnTriggerEnter2D, assim que um objeto colidir com esses gols, ele verificará se trata-se da bolinha. Caso positivo, ele atribuirá um ponto ao jogador ou ao adversário, dependendo se tratar-se do gol da direita ou da esquerda, respectivamente.
  • Após a atribuição do ponto, será atualizado o texto do placar na tela e, por fim, a bolinha será devolvida ao centro da tela (posições X e Y com valor igual a 0) para recomeçar a partida.
Não se esqueça de salvar o script. Minimize o Visual Studio e vamos voltar novamente ao Unity.

Selecione os GameObjects GolEsquerda e GolDireita e atribua o componente Gol a ambos, conforme imagem a seguir:

Para cada objeto, indique se trata-se do gol da direita ou da esquerda e indique quais são os GameObjects Texto Placar Jogador e Texto Placar Adversario, assim como indicado na ilustração a seguir, contendo um exemplo de preenchimento para o objeto GolDireita:


Experimente e veja como o jogo está ficando, indo na aba Game e pressionando o botão Play.

Se tudo caminhar conforme o esperado, nossa bolinha não irá mais viajar sem fim, estará confinada dentro do campo de jogo, que é o que queremos. Mas ainda faltam algumas etapas importantes que iremos detalhar a seguir.

Cobrinhas defensoras

Você pôde perceber que, apesar de já termos um comportamento bacana em relação à movimentação da bola dentro do campo de jogo, aparentemente as cobrinhas não são boas “goleiras”, pois elas não conseguem rebater a bola e acabam levando “goleadas” nesta partida de tênis de mesa virtual. Pois é, precisaremos inserir alguns Colliders nos objetos das peçonhentas para que o jogo aconteça de verdade. Vamos lá!

Selecione os objetos Personagem01 e Personagem02 e insira Box Colliders 2D neles. Ambos terão o parâmetro Is Trigger selecionado, Size X = 35 e Size Y = 250, conforme imagem abaixo:

Agora vamos abrir o script comportamentoCobrinha (aba Project, dentro da pasta Scripts). Iremos inserir abaixo do final do método void Update() (antes do último “}” do arquivo) o código a seguir:

private void OnTriggerEnter2D (Collider2D other)
{
  if (other.gameObject.name.StartsWith("Bolinha")
  other.gameObject.GetComponent<controladorBola>().DirecaoHorizontal = other.gameObject.GetComponent<controladorBola>().DirecaoHorizontal * -1;
}

Como podemos perceber, diferentes métodos podem coexistir em um mesmo script (Update, Start, OnTriggerEnter2D, etc.). Esse método que inserimos em específico será responsável por rebater a bolinha para o outro sentido horizontal (esquerda ou direita) caso ela bata na cobrinha.

Salve o script, minimize o Visual Studio e teste o game no Unity. Agora sim temos um jogo de verdade, mas ainda vamos corrigir algumas coisas.

Se você percebeu, as cobrinhas conseguem atravessar a delimitação do cenário para cima e para baixo; vamos limitar o movimento delas até o canto superior (posição Y = 365) e inferior (posição Y = 135) do campo de jogo via código. Para isso, após interromper a simulação do game no Unity, abra o script comportamentoCobrinha.

Dentro das chaves { } abaixo de “if (Input.GetKey(KeyCode.T))” e de “if (Input.GetKey(KeyCode.O))” altere o conteúdo presente para:

gameObject.transform.position = new Vector2(gameObject. transform.position.x, Mathf.Min(365, gameObject. transform.position.y + (VelocidadeDaCobrinha * Time.deltaTime * 60)));

Já dentro das chaves { } abaixo de “if (Input.GetKey(KeyCode.G))” e de “if (Input.GetKey(KeyCode.L))” altere o conteúdo presente para:

gameObject.transform.position = new Vector2(gameObject. transform.position.x, Mathf.Max(135, gameObject. transform.position.y - (VelocidadeDaCobrinha * Time.deltaTime * 60)));

A imagem a seguir ilustra o preenchimento do código para os botões T e G do teclado. Faça o mesmo para os botões O e L, utilizando as codificações indicadas a seguir, respectivas a cada botão:

As alterações que realizamos têm como objetivo utilizar as funções matemáticas Máximo (Mathf.Max) e Mínimo (Mathf.Min) para delimitar que os movimentos devem ocorrer entre as posições verticais 365 e 125 do Canvas de nossa cena.

Salve o script, feche o Visual Studio e teste o game no Unity para ver a alteração.

Ao término da simulação, não se esqueça de clicar novamente no botão Play para interrompê-la e, ao final de todo o processo, salvar sua cena (menu File > Save) e seu projeto (menu File > Save Project) antes de fechar o Unity.

Próximos passos

De certa forma, já temos um jogo quase pronto, com regras bem definidas, elementos de cenário, personagens e pontuação funcionando bem, mas faltam alguns poucos itens para que nossa experiência esteja completa.

Em nosso próximo passo iremos finalizar a construção do game Forest Ping Pong ao refinar elementos de desafio do jogo (como a velocidade da bolinha), inserção de componentes para início e término da partida e, por fim, geração do executável de seu jogo, que estará pronto para proporcionar altas partidas virtuais de tênis de mesa.

Nosso próximo texto já encontra-se disponível, continue conosco nessa jornada de conhecimento e fique ligado sempre aqui no GameBlast!

Revisão: Ives Boitano

Entendo videogames como sendo uma expressão de arte e lazer e, também, como uma impactante ferramenta de educação. No momento, doutorando em Sistemas da Informação pela EACH-USP, desenvolvendo jogos e sistemas desde 2020. Se quiser bater um papo comigo, nas redes sociais procure por @RodrigoGPontes.
Este texto não representa a opinião do GameBlast. Somos uma comunidade de gamers aberta às visões e experiências de cada autor. Escrevemos sob a licença Creative Commons BY-SA 3.0 - você pode usar e compartilhar este conteúdo desde que credite o autor e veículo original.