Aprendendo a programar jogos em Unity: inserindo telas de vitória e de fim de jogo

Vamos prosseguir com a modelagem e implementação das telas a serem exibidas em momentos-chave das partidas.

em 16/12/2023
Seja bem-vindo(a) ao GameDev: Aprendendo a programar jogos em Unity de hoje! Dando sequência ao processo de criação de nosso platformer 2D, iremos continuar a elaborar os itens gráficos que fazem parte da experiência de jogo, tais como as telas que serão exibidas ao se coletarem todos os presentes em uma fase e, também, a que será exibida caso não se concluam os desafios a tempo.


Caso esta seja a primeira vez que você acessa nossa série, sinta-se à vontade para juntar-se a nós neste processo de aprendizado. A partir do primeiro tópico, você poderá acompanhar todos os passos envolvidos na criação de um game, desde a instalação da ferramenta Unity em seu computador até a elaboração do jogo em si e dos elementos que o compõem, tais como os códigos e elementos multimídia.

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 um platformer 2D chamado Motorista da Pesada, cuja meta a ser cumprida pelo jogador nas fases é a coleta de diversas caixas de presente, nos caminhos apresentados, no menor tempo possível.


No tópico anterior, codificamos e inserimos elementos gráficos que envolvem o recurso de pausamento do game, além de descobrirmos como pode ser realizada a ação de “congelamento” do tempo no jogo, tanto em relação ao cronômetro quanto em relação a outros aspectos, por exemplo, sobre os recursos de física do Unity. Venha conosco e vamos juntos nesta jornada rumo a novos conhecimentos!

Pausando o game e voltando ao início

Em nosso último encontro, desenvolvemos diversos aspectos relativos à tela e ao botão de pause do game, mas um item ficou “de fora” das soluções que adotamos para desenvolver essa funcionalidade no jogo: o recurso de sair do game e voltar ao menu, representado pelo botão “Sair” do menu de pause.

Sendo assim, vamos codificá-lo e, assim, tornar nossa tela de pause 100% funcional. Para isso, 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.

A primeira atividade que iremos realizar envolverá, então, editar alguns aspectos do botão que irá permitir voltarmos ao menu inicial. Portanto, na aba Hierarchy, selecione o GameObject TelaPause, subordinado ao objeto CanvasFase. Na aba Inspector, deixe o objeto ativo, deixando selecionada a opção em destaque apresentada na imagem a seguir:

Agora, na aba Project, acesse a pasta Assets, Scripts e, por fim, clique duas vezes sobre o ícone do script ControladorFase, para realizarmos edições em seu código no Visual Studio.

Uma das tarefas a ser realizada ao se pressionar o botão para sairmos da fase e voltarmos ao menu principal é o carregamento da cena referente ao menu. Para isso, primeiro temos de acrescentar, logo abaixo das declarações de bibliotecas (primeiras linhas, contendo trechos de código começando pela palavra using...), a seguinte linha de código:

using UnityEngine.SceneManagement;

Dessa forma, indicamos que iremos utilizar determinadas funcionalidades que envolvem o manuseio das cenas em si (carregar uma cena, conhecer o nome da cena aberta, etc.), assim como fizemos anteriormente no script CarregarCena.

Agora, após o trecho de código referente à função Pausar(bool estado), acrescente o seguinte bloco de código:

    public void Encerrar()
    {
        Partida.TempoRestante = 0;
        Partida.Vidas = 0;
        SceneManager.LoadScene("MenuInicial");
    }

A função Encerrar() será responsável por, além de carregar a cena do menu inicial do game, zerar em memória a quantidade de tempo restante e de vidas.

Isso é importante pois, recordando o que inserimos na função Start() desse mesmo script, ao se iniciar a cena da fase, são verificadas as quantidades de vidas e de tempo restante; dessa forma, caso sejam iguais a zero, assim que o jogador volte a iniciar uma nova partida, serão concedidos os valores padrão (100 segundos e três vidas)

Por falar na função Start() , vamos acrescentar ao seu final, antes do fechamento dos colchetes, o seguinte trecho de código:

Time.timeScale = 1;

Lembre-se de que, ao se pausar o game, é concedido o valor zero à variável Time.timeScale; portanto, ao se iniciar nova fase ou partida, é bom garantir que a fase comece em ação, e não congelada, devido a algum pausamento anterior.

Feito isso, salve o script e volte ao editor do Unity, para prosseguirmos com a implementação da funcionalidade.

No Unity, na aba Hierarchy, iremos selecionar o objeto Sair, subordinado ao GameObject TelaPause. Na aba Inspector, insira um componente do tipo Button; para tal, clique no botão Add Component e, em seguida, UI, Button, ou por meio da ferramenta de pesquisa de componentes apresentada ao se clicar em Add Component.


Na propriedade On Click () de seu componente Button, clique no símbolo de mais (+). No novo campo apresentado, do lado esquerdo iremos preenchê-lo fazendo referência ao GameObject Controlador (clique e arraste o ícone do objeto, da aba Hierarchy, até o campo). Após isso, selecione o menu do lado direito e, na lista de seleções, escolha ControladorFase e, em seguida, a opção Encerrar(), ficando os campos preenchidos conforme exemplo a seguir:


Selecione o GameObject TelaPause e desative-o, deixando a caixa em destaque na imagem a seguir sem seleção:

Agora experimente simular a execução do jogo, indo à aba Game e clicando sobre o botão Play. Note que há a adequada transição entre a opção “Sair” do menu de pause, o menu principal e a opção de iniciar nova partida:

Interrompa a simulação da execução, clicando novamente sobre o botão Play e retornando à aba Scene do Unity.

Reaproveitando telas

Iremos criar agora novas telas na fase, que serão exibidas ao se coletar todos os presentes da fase ou ao se esgotar o tempo em uma partida.

Como a estrutura das telas que iremos criar é bem semelhante ao que já criamos para a tela de pause da fase, iremos “cloná-la” algumas vezes e realizar alterações pontuais em cada nova tela gerada. Assim, economizamos tempo e mantemos, de certa forma, a identidade visual dos menus.

Na aba Hierarchy, clique com o botão direito sobre o GameObject TelaPause e selecione a opção Duplicate, conforme indicado na imagem a seguir:


Selecione o novo objeto, temporariamente denominado TelaPause (1), e, na aba Inspector, ative-o, clicando na caixa de seleção em destaque da imagem a seguir.

Agora renomeie o objeto em questão para “TelaGanhou”, sem as aspas. Para fazer isso, clique com o botão direito sobre o GameObject e selecione a opção Rename.

Vamos começar a personalizar a tela de vitória na fase. A primeira coisa que iremos alterar é a cor; ainda com seu GameObject selecionado, clique sobre a propriedade Color de seu componente Image e altere a cor para as coordenadas R = 110, G = 220, B = 180 e A = 200, conforme exemplo a seguir:


Clique no objeto Titulo, subordinado a TelaGanhou, e altere o texto do componente Text para “Você passou de fase!”, sem as aspas.

Veja que, com a nova cor de fundo escolhida, há pouco contraste entre as letras e a tela em si. Para corrigirmos isso sem precisarmos trocar a cor da letra, acrescente o componente Outline ao objeto, mantendo a cor padrão e alterando os parâmetros Effect Distance X = 2 e Y = -2, conforme exemplo a seguir:

Assim, teremos um bonito contorno em volta do texto em questão, dando um contraste adequado aos componentes visuais que estamos editando.

Selecione agora o objeto Voltar, subordinado à TelaGanhou, e renomeie-o para AvancarFase. Altere o texto de seu componente Text para “Avançar”, sem as aspas. Acrescente também um componente Outline ao objeto, mantendo seus valores padrão.

Por fim, selecione o objeto Sair e apenas acrescente um componente Outline, mantendo os valores padrão. Não iremos realizar nenhuma alteração adicional ao objeto, já que o comportamento desse botão em específico será exatamente o mesmo do objeto Sair da tela de pause.

Finalizadas as edições gráficas da tela em questão, selecione o GameObject TelaGanhou e desative-o na aba Inspector.

Vamos criar agora o elemento que irá representar nossa outra tela, a ser exibida para quem "perder" a partida. Para isso, desta vez, iremos utilizar como base a tela de vitória recém-editada.

Clique com o botão direito sobre o GameObject TelaGanhou e selecione a opção Duplicate. Renomeie o objeto recém-duplicado para “TelaPerdeu”, sem as aspas. Ative o GameObject clicando no item em destaque na aba Inspector:

Vamos alterar a cor dessa tela para algo menos “festivo”. Clique sobre a propriedade Color de seu componente Image, na aba Inspector, e altere a cor para as coordenadas R = 50, G = 40, B = 80 e A = 200, conforme imagem a seguir:


Selecione o GameObject Titulo, subordinado a TelaPerdeu, e troque o texto de seu componente Text para “O jogo acabou!”, sem as aspas:

Renomeie o GameObject AvancarFase, subordinado a TelaPerdeu, para PlacarFinal. Na aba Inspector, altere a propriedade Width de seu Rect Transform para 600, a fim de acomodar mais caracteres na tela, e o texto de seu componente Text deverá ser alterado para “Placar final: 000 presentes”, sem as aspas:

Feito isso, selecione o GameObject TelaPerdeu e desative-o na aba Inspector para prosseguirmos, agora, com a codificação dos botões.

Ganhando de verdade

Como nosso jogo irá apresentar mais de uma fase durante o decorrer da partida, devemos indicar no código de nossos scripts funções para que essa passagem seja realizada de forma correta, carregando sequencialmente as fases em ordem e exibindo a tela de vitória na fase assim que for concluída a coleta dos presentes na tela.

Vamos iniciar esse processo realizando alterações no script ControladorFase. Abra-o e, após as linhas que codificamos agora há pouco (referentes à função Encerrar()), insira o seguinte trecho de código:

    public void AvancarFase()
    {
        switch (SceneManager.GetActiveScene().name)
        {
            case "Fase01":
                SceneManager.LoadScene("Fase02");
                break;
            case "Fase02":
                SceneManager.LoadScene("Fase03");
                break;
            case "Fase03":
                SceneManager.LoadScene("Fase01");
                break;
        }        
    }

A função AvancarFase() será a responsável por carregar corretamente a fase seguinte, de acordo com o nome da fase ativa no momento.

Também iremos inserir, logo abaixo da declaração da variável controladorAtivo (no início do script), a seguinte declaração de variáveis:

    public GameObject telaGanhou, telaPerdeu;

Em breve, essas variáveis serão úteis na interação entre o ato da coleta dos presentes e a detecção do fim da fase. Salve o script, minimize o Visual Studio e volte ao Unity, para darmos prosseguimento às ações.

Selecione o GameObject TelaGanhou e, na aba Inspector, ative-o. 

Agora, selecione o objeto AvancarFase e, na propriedade On Click () de seu componente Button, clique no símbolo de menos (-) até limpar a lista, deixando-a vazia, conforme exemplo a seguir:


Clique uma vez sobre o símbolo de mais (+) e, no novo campo apresentado, do lado esquerdo iremos fazer referência ao GameObject Controlador. Logo após, no campo do lado direito, escolha ControladorFase e, em seguida, a opção AvancarFase(), conforme imagem ilustrativa a seguir:


Feito isso, selecione o GameObject TelaGanhou e, na aba Inspector, desative-o.

Mostrando a tela

Já temos o botão de avançar fase configurado, agora iremos instruir o jogo, por meio de códigos, a saber em qual hora deverá exibir a tela que representa a vitória na fase em questão.

Para tal, iremos editar o script Itens; na aba Project, acesse a pasta Assets, Scripts e, por fim, clique duas vezes sobre seu ícone.

Iremos trocar o código que, por enquanto, apresenta a mensagem na tela “Você venceu!” ao se coletar todos os presentes pelo código que irá ativar a tela da vitória. Dentro dos colchetes de OnTriggerEnter2D(), substitua o trecho logo após o último “else” pelo código a seguir (incluindo seus colchetes):

                {
                    Partida.atualControladorFase.telaGanhou.SetActive(true);
                    Time.timeScale = 0;
                    GameObject.FindGameObjectWithTag("ControleItens").SetActive(false);
                }

Esse código será responsável por realizar as seguintes ações:
  • Ativar o GameObject telaGanhou, cuja variável declaramos agora há pouco no script ControladorFase;
  • Pausar o tempo do jogo, por meio da concessão do valor zero à variável Time.timeScale; e 
  • Desativar o indicador visual na tela, referente a quantos presentes faltam ser coletados.
Salve o script e feche o Visual Studio. Iremos voltar ao editor do Unity para realizar uma última, mas não menos importante, alteração, referente à funcionalidade que estamos implementando.

Como declaramos as variáveis públicas telaGanhou e telaPerdeu no script ControladorFase, ambas são visíveis via editor e devem ter seus valores devidamente apontados para os objetos corretos, permitindo, por exemplo, que o script Itens possa ativar a tela de vitória no momento certo.

Então, na aba Hierarchy, selecione o GameObject Controlador. Na aba Inspector, note que o componente ControladorFase agora exibe dois campos adicionais: Tela Ganhou e Tela Perdeu. Para cada um dos campos faça referência aos objetos TelaGanhou e TelaPerdeu, respectivamente, conforme exemplo a seguir:

Vamos finalizar nossas intervenções de hoje, experimentando simular a execução do jogo. Note que, ao término da coleta de itens, há a exibição da tela de vitória, mas, ao se clicar na opção "Avançar", nada ocorre, pois ainda não temos outras fases implementadas.

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

Realizamos mais progressos em nosso jogo; desta vez, implementando uma tela funcional de vitória na fase e, também, a possibilidade de se voltar ao menu inicial, por meio de opção na tela de pause.

Em nossos próximos encontros daremos prosseguimento à elaboração da tela de fim de jogo, implementaremos o sistema de “vidas” do game e, por fim, começaremos a gerar as outras fases desse desafio, fechando o “ciclo” das partidas e tendo, enfim, nosso platformer 2D implementado em sua totalidade. Estamos chegando lá!

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.