GameDev

Aprendendo a programar jogos em Unity: posicionando e programando itens coletáveis

Vamos povoar a primeira fase de nosso game com itens coletáveis e concluir as melhorias de movimento do personagem principal.

Seja bem-vindo(a) ao GameDev: Aprendendo a programar jogos em Unity de hoje! Dando sequência ao processo de desenvolvimento do nosso platformer 2D Motorista da Pesada, iremos introduzir no cenário da primeira fase os elementos que serão coletados pelo personagem principal nos diversos caminhos que inserimos (e codificamos) nos tópicos anteriores.


Caso esta seja a primeira vez que acessa esta série, fique à vontade para juntar-se a nós e acompanhar, desde o primeiro tópico, o processo de desenvolvimento de jogos utilizando a ferramenta Unity, desde a instalação e configuração da ferramenta até o posicionamento dos itens e posterior codificação dos elementos.

Nossa série é elaborada de forma a permitir, por meio de exemplos e projetos, que você possa criar “do zero” os games que sempre sonhou em tirar do papel. No momento, estamos desenvolvendo nosso segundo jogo, um platformer 2D de coleta de itens, contendo múltiplas fases e caminhos a percorrer.


No tópico anterior de nossa jornada, corrigimos alguns aspectos da movimentação do nosso personagem principal, um simpático carrinho, além de aprendermos mais sobre aspectos relativos ao funcionamento da rotação de objetos 2D e 3D, por meio do uso de funções fornecidas pela ferramenta Unity. Venha conosco e vamos juntos nesta trilha rumo a novos conhecimentos!

Ajustando objetos e sprites

Agora que já demos uma boa melhorada nos aspectos de movimentação de nosso carrinho, que andava bem “desgovernado” até então, podemos voltar as nossas atenções a um aspecto fundamental de um bom platformer 2D: os itens coletáveis que o jogador pode encontrar pelo caminho.

Para começar a introduzi-los no cenário de nossa primeira fase, vamos abrir o projeto Motorista da Pesada no editor. Para isso, abra o Unity Hub e clique duas vezes sobre o item referente ao projeto. Na interface inicial do Unity, na aba Project, abra a pasta Assets, Scenes e, por fim, clique duas vezes no ícone da cena Fase01.

Na aba Hierarchy, clique com o botão direito sobre o objeto Itens e selecione a opção Create Empty. Dê o nome de Coletavel01 ao objeto; repita essa operação algumas vezes nomeando os objetos como Coletavel02, Coletavel03… até Coletavel07. Os sete objetos criados representarão as sete caixas de presente a serem coletadas em nossa fase.

Selecione todos os objetos criados e, na aba Inspector, adicione um componente do tipo Sprite Renderer, por meio do botão Add Component, conforme ilustração a seguir:

Agora, assim como fizemos com as imagens que compuseram a representação gráfica de nossas plataformas, do fundo e do personagem, iremos ajustar os parâmetros do arquivo de imagem que contém as caixas de presente. Para tal, na aba Project, clique duas vezes sobre o ícone da pasta Assets, depois sobre o da pasta Multimidia e, por fim, abra a pasta Imagens. Selecione o arquivo de nome “presentes.png” e clique sobre o botão Sprite Editor, conforme exemplo abaixo:

Com a janela do Sprite Editor aberta, clique sobre o menu Slice, destacado em amarelo na imagem a seguir, deixe os parâmetros como o indicado pelas setas do exemplo abaixo e clique no botão Slice, destacado em verde:

Novamente, a ferramenta Sprite Editor nos serve como uma “mão na roda”, ao delimitar os sprites dos nove itens presentes no arquivo de imagem que abrimos. Para confirmar que houve a correta delimitação dos itens, clique sobre uma das caixinhas para visualizar seus parâmetros no canto inferior direito da tela. Estando tudo OK, clique sobre o botão Apply e feche a janela da ferramenta.

Para finalizar os ajustes referentes aos sprites que iremos utilizar, ainda com o arquivo de imagem “presentes.png” selecionado no editor, na aba Inspector modifique o parâmetro Pixels Per Unit para o valor 30, conforme indicado a seguir. Isso garantirá que o tamanho dos objetos na tela esteja adequado, nem tão grande, nem tão pequeno. Ao final, clique sobre o botão Apply.

Caixas de presente

Chegou a hora de espalharmos presentes por todo esse enorme cenário. Para isso, são indicados a seguir os parâmetros relativos ao componente Transform a serem alterados para cada objeto criado:
  • Coletavel01: Transform X = -6, Y = 0.85, Z = 0;
  • Coletavel02: Transform X = 11.5, Y = 0.85, Z = 0;
  • Coletavel03: Transform X = 2, Y = 3.85, Z = 0;
  • Coletavel04: Transform X = -10, Y = 3.85, Z = 0;
  • Coletavel05: Transform X = -18, Y = 0.85, Z = 0;
  • Coletavel06: Transform X = –18, Y =-1.75, Z = 0; e
  • Coletavel07: Transform X = 9, Y = -1.75, Z = 0;
Em relação ao componente Sprite Renderer, abaixo são descritos quais são os sprites que deverão ser atrelados a cada objeto:
  • Coletavel01, 03, 05 e 07: Sprite = presentes_0; e
  • Coletavel02, 04 e 06: Sprite = presentes_1.
Veja, na imagem a seguir, como ficarão os elementos espalhados pelo cenário:

Além disso, para que as caixas de presente se movimentem pelo cenário no mesmo ritmo das plataformas, na aba Hierarchy, selecione todos os objetos coletáveis e adicione o componente Mov Fundo a elas, com os parâmetros iguais aos fornecidos para as plataformas (Velocidade = 0.2, Limite Min = -23 e Limite Max = 18).

Colisores e triggers

Um dos comportamentos que, normalmente, esperamos que ocorra em platformers 2D no momento em que um personagem encosta em um objeto coletável é o “desaparecimento” do objeto. É o que acontece, por exemplo, quando Mario, em suas inúmeras aventuras 2D, encosta em uma moeda:


Para programarmos esse comportamento, primeiro devemos inserir um componente nos objetos que representam os presentes, a fim de que eles possam “perceber” que foram tocados. Para isso, inicialmente devemos selecionar todos os objetos referentes aos objetos coletáveis da aba Hierarchy e, na aba Inspector, inserir componentes do tipo Box Collider 2D.

Observe se o Unity já forneceu automaticamente os tamanhos corretos dos colisores inseridos nos objetos, por meio do atributo Size X = 0.5333 e Y = 0.5; caso não tenha sido fornecido, altere para esses valores.

Visualmente, no editor, conseguimos perceber se o colisor está com um tamanho adequado pela linha de cor verde que é exibida em volta do objeto:

Vamos testar a execução para ver se os colisores estão bem posicionados? Para isso, vá até a aba Game e pressione o botão Play:

Bem... os colisores em si estão muito bem, obrigado, mas os presentes estão agindo muito mais como “lombadas” no caminho do que presentes em si, não concorda?

Isso se deve ao fato de que colisores podem se comportar de duas formas distintas: agindo como “bloqueios físicos”, como o que utilizamos até o momento para a criação do chão, do teto e das plataformas, ou como “gatilhos”, também conhecidos pelo termo em inglês triggers.

Um Box Collider 2D, por exemplo, se configurado como um trigger, não irá bloquear o caminho de nosso personagem, mas irá mandar um “sinal” para a engine do Unity informando que houve uma colisão entre dois objetos ali. Esse “sinal” pode ser interceptado pela codificação de nosso game, o que posteriormente iremos fazer por meio de um script que criaremos em breve.

Por hora, interrompa a simulação da execução do jogo, volte à aba Scene e, com todos os objetos coletáveis da aba Hierarchy selecionados, na aba Inspector, iremos modificar o atributo Is Trigger, presente em Box Collider 2D, para verdadeiro, clicando na caixinha indicada na imagem ilustrativa a seguir:

Feito isso, vamos desenvolver o script que irá detectar essa colisão. Na aba Project, abra a pasta Assets e, em seguida, Scripts. Clique com o botão direito em uma área vazia desta pasta e, no menu suspenso, selecione a opção Create e, em seguida, C# Script. Dê o nome de “Itens” (sem as aspas) ao script criado.

Clique duas vezes sobre o ícone do script, para que ele seja aberto para edição no Visual Studio. Iremos remover as funções padrão (void Start() e void Update()) para o conteúdo descrito a seguir:

public bool presente, bombinha;

private void OnTriggerEnter2D(Collider2D other)
{
if (presente)
    {
    if (other.gameObject.name.StartsWith("Personagem"))
        Destroy(gameObject);
    }
}

Nesse script, criamos duas variáveis públicas, acessíveis pelo editor, chamadas presente e bombinha, para indicar a natureza do item. Por enquanto iremos trabalhar apenas com presentes.

Também criamos uma função privada void OnTriggerEnter2D(Collider2D other), cujo código será executado apenas se for detectada a colisão entre algum objeto com o GameObject que está com esse script atrelado e configurado como trigger.

O código da função criada irá verificar, primeiramente, se o objeto com o script atrelado está com a variável presente com valor verdadeiro (true). Caso positivo, será verificado se o objeto other (o que causou a colisão) tem o nome começando por “Personagem”. Se isso for verdadeiro, o objeto com o script atrelado será destruído, sumindo do cenário.

Feito tudo isso, salve o script, minimize o Visual Studio e vamos retornar ao Unity.

Ajustes finos

Agora, iremos realizar algumas ações para permitir que as caixinhas de presente possam ser coletadas corretamente e, também, finalizar os ajustes necessários para uma boa movimentação do personagem pelo cenário.

Selecione todos os GameObjects que representam os coletáveis e adicione o componente Itens, já deixando o atributo Presente marcado como verdadeiro para todos, conforme imagem a seguir:

Vamos testar a execução do jogo para ver os objetos coletáveis “sumindo” de cena. Preste especial atenção ao que ocorre na aba Hierarchy: os GameObjects em execução, ao serem tocados, somem até da listagem (apenas enquanto em execução), pois a função Destroy(gameObject) de fato remove o GameObject da memória.

Interrompa a execução e volte à aba Scene, pois vamos corrigir algo que, talvez, você já tenha notado, por meio das simulações de execução.

Quando implementamos o script MovFundo, indicamos que os objetos atrelados a este script se movimentariam seguindo o sentido dos botões do teclado que foram pressionados, no caso, a seta para a direita (ou tecla D) e para a esquerda (ou tecla A). Porém, no caso do game Motorista da Pesada, o fundo deverá se deslocar para o lado oposto ao pressionado, pois quem tem seu movimento controlado, ao menos aos olhos do jogador, é o carrinho.

Portanto, vamos realizar uma pequena alteração na codificação para corrigir isso. Na aba Project, abra a pasta Assets, Scripts e, por fim, clique duas vezes sobre o ícone do script MovFundo.

Com o script aberto para edição no Visual Studio, iremos “inverter os sinais” de duas linhas em que há a declaração de valores para a variável direcao, conforme exemplo indicado pela imagem a seguir:

Salve o script e volte ao Unity para realizarmos uma última alteração referente à movimentação de nosso personagem.

Você se lembra, durante o desenvolvimento do projeto Forest Ping Pong, que elaboramos um script para, dada a tecla pressionada pelo jogador, corrigir o posicionamento do corpo das cobrinhas, deixando o rosto das peçonhentas em frente à direção do movimento? Se não se recorda, veja em ação o que fizemos anteriormente:

Iremos criar um script com o mesmo intuito, agora, para o personagem de nosso projeto atual; desta vez, adaptado às modificações que aplicamos no jogo, como por exemplo pelo uso de Sprite Renderers em vez de Images.

Na aba Project do editor do Unity, vá até a pasta Scripts e crie um novo, cujo nome será LadoCarrinho.

Clique duas vezes sobre o ícone do script criado e, no Visual Studio, apague a declaração da função void Start(). Dentro dos colchetes de void Update(), insira o seguinte trecho de código:

if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
    gameObject.GetComponent<SpriteRenderer>().flipX = true;
else if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
    gameObject.GetComponent<SpriteRenderer>().flipX = false;

Nosso código irá, dada a tecla pressionada pelo jogador em seu teclado, modificar para verdadeiro (true) ou falso (false) a variável flipX, que é responsável por espelhar a imagem de um sprite no eixo horizontal, para um componente Sprite Renderer.

Salve o script e volte ao Unity. Na aba Hierarchy, selecione o objeto Personagem e, na aba Inspector, insira um componente do tipo Lado Carrinho, conforme exemplo a seguir:

Por fim, experimente a execução do jogo, indo à aba Game e pressionando o botão Play. Veja em ação as diversas melhorias que desenvolvemos nos últimos tópicos:

Finalize a simulação da execução. Não se esqueça de, ao final, salvar a cena (menu File, Save) e o projeto (menu File, Save Project) antes de fechar o Unity.

Próximos passos

Hoje aprendemos juntos alguns aspectos importantes no desenvolvimento de jogos 2D do Unity, como por exemplo a detecção de colisões via código, por meio do uso de scripts e triggers. Além disso, povoamos nossa primeira fase com diversos elementos coletáveis, que reagem ao serem tocados pelo personagem principal desaparecendo da cena.

Estamos cada vez mais perto de termos a primeira fase do game plenamente funcional, pronta para ser jogada e passar o bastão para a próxima fase, mas, antes, teremos que criar alguns controladores em nosso jogo, a fim de armazenar e repassar entre fases informações importantes, como o placar e o tempo da partida.

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

Revisão: Thais Santos

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.