GameDev

Aprendendo a programar jogos em Unity: implementando o sistema de vidas e de fim de jogo

Vamos iniciar a criação e implementação dos sistemas de vidas e de game over, suas codificações e interfaces na tela.

Seja bem-vindo(a) ao GameDev: Aprendendo a programar jogos em Unity de hoje! Prosseguindo em nossa caminhada rumo ao desenvolvimento de um platformer 2D, daremos início à elaboração do sistema de vidas do game, tanto em relação a seus aspectos visuais quanto ao código necessário para fazê-lo funcionar. Em conjunto com os aspectos voltados à gestão de vidas, iremos elaborar o sistema de fim de jogo, ou seja, o famigerado “game over” de nossa aventura.


Caso você esteja acessando nossa série pela primeira vez, sinta-se à vontade para juntar-se a nós para encararmos, juntos, uma trilha de aprendizado visando a elaboração de divertidos jogos. A partir do primeiro tópico, você poderá acompanhar todos os passos envolvidos na criação de um game, desde a instalação e configuração da ferramenta Unity em seu computador até o desenvolvimento de diversos elementos, tais como a inserção de imagens, sons e personagens, criação dos códigos de programação envolvidos e os conceitos que embasam seu funcionamento.

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 o jogo Motorista da Pesada. Trata-se de um platformer 2D em que o jogador assume o papel de um motorista que, com seu veículo, deve coletar todas as caixas de presente espalhadas pelas fases no menor tempo possível.


No tópico anterior de nossa jornada começamos a elaborar e codificar a tela que será apresentada ao jogador caso ele consiga cumprir os desafios na fase e, também, a que será exibida caso o jogador “perca”, seja por ter se esgotado o tempo restante, seja por se esgotarem suas vidas. Venha conosco e vamos juntos nesta jornada rumo a novos conhecimentos!

Regras do jogo: vidas

Em nosso último encontro conseguimos desenvolver a tela de “vitória” da fase, inclusive em relação ao código que irá exibi-la em tela, assim que forem coletados todos os presentes na fase.

Agora podemos começar a pensar a respeito das condições que levam o jogador a perder na fase. Basicamente são duas situações que podem levar o gamer à derrota:
  1. Esgotando-se o tempo restante para coletar os presentes na fase; ou
  2. Perdendo-se todas as vidas.
Apesar de serem condições até certo ponto simples de serem entendidas, até o momento não definimos como será realizada a gestão das vidas do jogo; porém, já deixamos algumas “pistas” nos códigos que desenvolvemos até o momento, sobretudo em relação aos itens, que podem ser categorizados como Presentes ou Bombinhas:


Portanto, podemos considerar para nosso game as seguintes regras, levando em conta as particularidades de nosso jogo:
  • Toda partida começa com três vidas, sendo o contador de vidas exibido em tela para o jogador;
  • Cada vez que o jogador coletar um item caracterizado como Bombinha, perderá uma vida;
  • Se perder as três vidas, a partida se encerrará.
Sendo assim, vamos iniciar a implementação desse sistema inserindo os elementos gráficos que irão compor o contador de vidas a ser exibido na tela; em seguida, iremos criar os códigos responsáveis pela gestão das vidas e a exibição da tela de game over.

Contador de vidas

Vamos abrir o projeto Motorista da Pesada no editor. 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 elemento CanvasFase e, no menu suspenso, selecione a opção Create Empty.


O nome do novo GameObject será “ContadorVidas”, sem as aspas. Selecione-o e, na aba Inspector, altere os parâmetros de seu componente Rect Transform pelos descritos a seguir:
  • Âncora (ícone destacado em verde): Bottom Left;
  • Pivot X = 0, Y = 0;
  • Pos X = 25, Pos Y = 12.5, Pos Z = 0;
  • Width = 600; e
  • Height = 100.
Na aba Hierarchy, clique com o botão direito sobre ContadorVidas e, no menu suspenso, selecione a opção UI e, em seguida, Text. O novo objeto criado terá o nome de “Titulo”, sem as aspas.

Após sua criação, clique novamente com o botão direito sobre ContadorVidas, selecionando desta vez as opções UI e, em seguida, Image. Essa operação deverá ser realizada três vezes e os GameObjects criados deverão receber os nomes “Vida01”, “Vida02” e “Vida03”, sem as aspas.


Nosso placar será composto por um texto com a inscrição “Vidas” e três imagens, representando as vidas do jogador. Caso a quantidade de vidas seja menor do que três, somente serão apresentadas as imagens correspondentes à quantidade de vidas restantes.

Selecione agora o objeto Titulo, subordinado ao GameObject ContadorVidas. Iremos conceder os seguintes parâmetros a seu componente Rect Transform:
  • Âncora (ícone destacado em verde): Middle Left;
  • Pivot X = 0, Y = 0.5;
  • Pos X = 0, Pos Y = 0, Pos Z = 0;
  • Width = 200; e
  • Height = 100.
Altere o conteúdo textual de seu componente Text para “Vidas”, sem as aspas, e realize os ajustes dos outros atributos do componente conforme apontamentos realizados no exemplo a seguir:


Para evitar problemas com contraste, vamos acrescentar uma borda branca ao redor das letras do objeto. Para isso, adicione um componente Outline, por meio do botão Add Component da aba Inspector. Altere os parâmetros de Effect Color do componente para R  = 255, G = 255, B = 255 e A (Alpha, ou transparência) = 128.

Os GameObjects Vida01, Vida02 e Vida03 também receberão alterações em alguns parâmetros de seus componentes. Inicialmente, selecione todos os objetos e altere o parâmetro Source Image de seus respectivos componentes Image para o sprite de nome “carrinho”, sem as aspas, conforme exemplo a seguir:

Agora, altere os parâmetros dos componentes Rect Transform dos três objetos para o que se segue:
  • Âncoras: Middle Left;
  • Pivot X = 0, Y = 0.5;
  • Width = 150;
  • Height = 75;
  • Pos Y = 0, Pos Z = 0; e
  • Pos X (Vida01) = 150; Pos X (Vida02) = 310; Pos X (Vida03) = 470.
Agora, na parte inferior da tela de jogo, serão apresentadas as imagens de três carrinhos, representando a quantidade de vidas do jogador na partida em questão. Como nosso personagem principal também é representado pela imagem de um carro, vamos dar um toque de personalização ao placar para diferenciá-lo do personagem em tela, recolorindo os itens gráficos.

Para cada um dos três objetos que representam as vidas, altere o parâmetro Color de seus respectivos componentes Image para o que se segue:
  • Vida01: R = 255, G = 128, B = 128 e A = 255;
  • Vida02: R = 255, G = 255, B = 0 e A = 255;
  • Vida03: R = 0, G = 255, B = 0 e A = 255.

Codificando o sistema

Agora que já temos uma boa representação gráfica do contador de vidas na tela, vamos codificar seu funcionamento. Para isso, na aba Project, abra o conteúdo da pasta Assets, Scripts e, por fim, clique duas vezes no ícone referente ao script ControladorFase para abri-lo no Visual Studio.

Iremos realizar algumas alterações no código, tanto para que o jogo saiba como organizar o contador de vidas ao se iniciar uma fase, quanto para atualizá-lo, nos casos em que o jogador perca uma vida.

Vamos iniciar as alterações realizando a declaração de duas novas variáveis, na linha imediatamente posterior às declarações de telaGanhou e telaPerdeu. O código será o descrito a seguir:

    public Image PlacarVida01, PlacarVida02, PlacarVida03;

Feito isso, logo após os códigos referentes à função AvancarFase(), iremos criar uma nova função, que receberá o nome AtualizarVidas(). O código a ser inserido é o que se segue:

    internal void AtualizarVidas()
    {
        PlacarVida01.gameObject.SetActive(Partida.Vidas >= 1);
        PlacarVida02.gameObject.SetActive(Partida.Vidas >= 2);
        PlacarVida03.gameObject.SetActive(Partida.Vidas == 3);

        if (Partida.Vidas <= 0)
            Perdeu();
    }

Basicamente, o trecho de código irá realizar duas ações: a primeira é a ativação dos GameObjects que contêm as imagens representando as vidas restantes, dependendo do placar em questão; e a segunda é a verificação da condição de fim de partida, chamando a função Perdeu() caso a quantidade de vidas restantes se esgote.

Note que, por enquanto, o código apresenta um erro na linha correspondente à chamada à função Perdeu(). Isso é normal, pois ainda não criamos a dita função, o que faremos daqui a pouco. 
Agora, iremos acrescentar ao final do código da função void Start() a seguinte linha de código:

    AtualizarVidas();

Dessa forma, ao se iniciar uma fase, serão verificadas as vidas restantes, para que seu contador mostre o valor adequado ao jogador.

Por fim, vamos criar agora a função Perdeu(), a ser inserida logo após o código da função AtualizarVidas():

    internal void Perdeu()
    {
        telaPerdeu.SetActive(true);
        Time.timeScale = 0;
        GameObject.FindGameObjectWithTag("ControleItens").SetActive(false);
    }

Anteriormente, dentro do script Itens, desenvolvemos um código para exibir a tela de êxito ao fim da coleta dos objetos categorizados como Presentes. Agora, desenvolvemos uma função que fará algo parecido, mas exibindo a tela de “derrota”, também pausando a contagem de tempo, como no exemplo anterior.

Feito isso, salve o script ControladorFase e minimize o Visual Studio, voltando ao editor do Unity. Agora iremos abrir, por meio da aba Project, o script Itens, clicando duas vezes sobre seu ícone.

Dentro da função OnTriggerEnter2D, logo após o fechamento do colchete do bloco condicional iniciado por if (presente),  vamos inserir a continuação da verificação de detecção de colisão de objetos, desta vez voltado aos itens categorizados como Bombinhas, por meio da inserção do seguinte código:

        else if (bombinha)
        {
            if (other.gameObject.name.StartsWith("Personagem"))
            {
                Partida.Vidas--;
                Partida.atualControladorFase.AtualizarVidas();
                Destroy(gameObject);
            }
        }

O trecho de código acima irá decrescer a quantidade de vidas restantes, solicitar ao ControladorFase corrente a atualização do placar e verificações adicionais (por meio de chamada à função AtualizarVidas()) e destruir o GameObject do item em questão.

Salve o script Itens, minimize o Visual Studio e vamos voltar ao editor do Unity para realizarmos algumas alterações interessantes na estrutura de nossa primeira fase.

Um desafio bombástico

Até agora, tudo anda muito “pacífico” em nossa fase, pois temos à disposição apenas presentes posicionados no chão e nas plataformas. Vamos dar uma “apimentada” nesse desafio, espalhando algumas bombinhas pelo cenário.

Para isso, na aba Hierarchy, clique com o botão direito sobre o GameObject Coletavel07 (subordinado aos objetos Cenario, Itens) e selecione a opção Duplicate. Realize a mesma operação por mais duas vezes.


Renomeie os objetos duplicados para “Bombinha01”, “Bombinha02” e “Bombinha03”, sem as aspas. Na aba Inspector, remova a Tag Presente dos três itens, selecionando a opção Untagged, conforme indicado no exemplo a seguir:

Agora, vamos editar os sprites que irão compor a representação gráfica de nossas bombas. Na aba Project, acesse a pasta AssetsMultimidia e, por fim, Imagens. Selecione os arquivos “bombinha_A.png”, “bombinha_B.png” e “bombinha_C.png” e, na aba Inspector, modifique o parâmetro Pixels Per Unit das três para o valor 30, clicando em Apply ao final. 

Na aba Hierarchy, volte a selecionar os três GameObjects referentes às bombinhas e, na aba Inspector, altere o parâmetro Sprite de seus componentes Sprite Renderer para o sprite de nome “bombinha_A”, sem as aspas. Altere também, referente ao componente Itens dos três objetos, o parâmetro selecionado Presente para falso (false) e Bombinha para verdadeiro (true), conforme indicado abaixo:

Agora, para cada um dos três GameObjects em questão, altere os parâmetros Position de seus respectivos componentes Transform para o que se segue:
  • Bombinha01: X = 4, Y = -1.75, Z = 0;
  • Bombinha02: X = -7.25, Y = 3.85, Z = 0;
  • Bombinha03: X = -4, Y = 0.85, Z = 0.
Por fim, selecione o GameObject Controlador e, na aba Inspector, indique ao componente Controlador Fase quais são os objetos a que se referem as variáveis Placar Vida 01, Placar Vida 02 e Placar Vida 03; no caso, serão apontados os GameObjects Vida01, Vida02 e Vida03, respectivamente, conforme exemplo a seguir:

Depois de tantas alterações e códigos, chegou a hora de ver, na prática, tudo isso funcionando, por meio da simulação da execução do jogo: abra a aba Game, pressione Play e experimente o poder das explosões:

Finalize a simulação da execução, clicando novamente sobre o botão Play. Não se esqueça de salvar a cena (menu File, Save) e o projeto (menu File, Save Project) antes de fechar o Unity.

Próximos passos

Conseguimos avançar no desenvolvimento de dois aspectos importantes de nosso jogo; no caso, os sistemas de vidas e de game over. Porém, existem alguns pontos que necessitam de correções, antes de iniciarmos o desenvolvimento das próximas fases.

Por exemplo, talvez você tenha notado que o contador de vidas fica sobreposto às telas de vitória, de derrota e de pause; ou tenha percebido que, ao término da partida, o placar final ainda não está sendo atualizado; ou até mesmo o fato de que, ao se esgotar o tempo, ainda não é exibida a tela de game over.

Então, em nossos próximos encontros, teremos ainda diversos aspectos a receber “ajustes finos”, códigos e correções. Iremos, ainda, introduzir os efeitos sonoros e trilhas para, por fim, podermos desenvolver as próximas fases. A cada encontro, estamos cada vez mais próximos de nos tornarmos os “motoristas malucos” de nosso próprio game!

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.


Disqus
Facebook
Google