GameDev

Aprendendo a programar jogos em Unity: iniciando o desenvolvimento do controlador da partida

Vamos começar a codificar os scripts responsáveis pelo controle da partida, do tempo restante e dos itens a serem coletados.

em 25/11/2023
Seja bem-vindo(a) ao GameDev: Aprendendo a programar jogos em Unity de hoje! Dando prosseguimento ao processo de criação de nosso primeiro platformer 2D, iremos iniciar uma etapa importante que irá impactar diretamente no funcionamento de todas as fases do game: a criação e desenvolvimento dos scripts de controle da partida atual.


Caso seja a primeira vez que você acessa esta série, fique à vontade e junte-se a nós para aprender a desenvolver jogos utilizando a ferramenta Unity. A partir do primeiro tópico, você poderá acompanhar todo o processo de criação de um game, desde a instalação e configuração da ferramenta até a elaboração do layout, inserção de itens multimídia e codificação de um jogo.

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 cujo nome é Motorista da Pesada.


No tópico anterior de nossa jornada, posicionamos itens coletáveis na cena da primeira fase de nosso projeto, codificamos o comportamento esperado ao se tocar em um presente com o carrinho e corrigimos alguns aspectos de movimentação do personagem principal. Venha conosco e vamos juntos nesta trilha rumo a novos conhecimentos!

Características de partidas e fases

Antes de iniciarmos o desenvolvimento do controlador de partida, temos que nos lembrar sobre alguns aspectos importantes que normalmente são levados em conta no desenvolvimento de jogos com múltiplas fases, como são os platformers 2D mais populares.

Normalmente, entre as diversas fases de um platformer, são compartilhadas informações sobre:
  • Placar do jogador;
  • Vidas restantes;
  • Recorde de pontuação; entre outros.
Além disso, para o controle da fase sendo jogada no momento, também costuma-se armazenar informações relevantes, como o tempo restante e os objetivos ainda a alcançar. Sabendo que alguns desses dados podem influenciar as fases subsequentes, iremos criar um script contendo uma classe estática, acessível pelos GameObject de qualquer cena do game.

Para começar essa implementação, 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.

Com a cena aberta, na aba Project, clique duas vezes sobre o ícone representando a pasta Assets e, depois, entre na pasta Scripts. Clique com o botão direito do mouse em alguma área vazia e crie um novo script (no menu suspenso, selecione Create e, depois, C# Script).

O nome de nosso novo script será “Partida” (sem as aspas), conforme exemplo a seguir. Clique duas vezes sobre seu ícone para abrir o arquivo no Visual Studio.

A fim de declararmos a nova classe estática de forma a suas propriedades serem acessíveis a todos os GameObjects e scripts do jogo, logo após a seção de declaração de uso das bibliotecas (linhas contendo a expressão using…), troque o código padrão para o descrito a seguir:

    public static class Partida
    {
        public static int PontosJogador, ObjetosRestantes, Vidas, MaxScore;
        public static float TempoRestante;
    }

As variáveis PontosJogador, ObjetosRestantes, Vidas, MaxScore e TempoRestante, declaradas dentro da classe Partida, representarão, respectivamente, o placar do jogador, quantos objetos faltam ser coletados na fase, quantas vidas o jogador ainda tem, qual é o recorde de pontuação e quanto tempo resta para aquela partida.

Observe que, assim como a classe Partida, suas variáveis também compartilham a mesma característica de serem static, ou seja, estáticos e, portanto, acessíveis por qualquer instância de objetos e scripts dentro do jogo.

Salve o script e volte ao editor do Unity para criarmos outro, dessa vez responsável por gerir a fase em questão.

Na aba Project, dentro da pasta Scripts, repita os passos para criação de um novo script, dando-lhe o nome de “ControladorFase”, sem as aspas.

Tags

Antes de iniciarmos a redigir o conteúdo do novo script, iremos trabalhar com um conceito do Unity que facilita a vida do programador, na hora de localizar um GameObject em específico via código: as tags.

Tags são descrições simples, dadas a um GameObject, para classificá-lo de alguma forma. Por exemplo, o programador pode criar uma tag de nome “Bola” para aplicar a todos os objetos de uma cena que correspondam a esse parâmetro.

Em nosso caso, iremos criar uma tag para rotular objetos que representam "presentes" na fase. Para isso, selecione um objeto na aba Hierarchy (Coletavel01, por exemplo) e, na aba Inspector, clique na caixa de seleção de nome Tag, selecionando a opção Add Tag...

Clique no símbolo de mais (+), preencha o campo de texto com a palavra “Presente” (sem as aspas) e clique no botão Save.

Agora, selecione todos os GameObjects que representam "presentes" na fase (de Coletavel01 a Coletavel07) e, na aba Inspector, clique na caixa de seleção de nome Tag e selecione a opção Presente.

Veja que, selecionando qualquer um dos itens relacionados, no campo Tag da aba Inspector irá aparecer a opção Presente selecionada. Um GameObject só pode ter uma tag atrelada.

Controlador da fase

Com os GameObjects dos presentes devidamente identificados com a tag Presente, agora sim iremos iniciar a edição do script de controle da fase. Para isso, na aba Project, acesse a pasta Assets e, depois, a pasta Scripts. Clique duas vezes sobre o ícone do script ControladorFase para abri-lo no Visual Studio.
Diferentemente das últimas edições de scripts que realizamos, dessa vez iremos utilizar as funções padrão void Start() e void Update(). Inicialmente, iremos declarar a seguinte variável antes de void Start():

    internal bool controladorAtivo = true;

Dentro dos colchetes de void Start(), insira o seguinte trecho de código:

        Partida.atualControladorFase = this;

        if (Partida.TempoRestante <= 0 || Partida.Vidas == 0)
        {
            Partida.TempoRestante = 100;
            Partida.Vidas = 3;
        }
        else
            Partida.TempoRestante += 10;

        Partida.ObjetosRestantes = GameObject.FindGameObjectsWithTag("Presente").Length;

Finalmente, dentro dos colchetes de void Update(), insira o seguinte trecho de código:

        if (controladorAtivo)
            Partida.TempoRestante -= Time.deltaTime;

        Debug.Log(Partida.TempoRestante);

O que esse código irá realizar para nosso jogo? Vamos analisá-lo por partes:

Dentro de void Start(), caso a execução do script esteja começando a partir de uma partida nova (identificada pelas quantidades de tempo e vidas zeradas), os valores padrão serão fornecidos às variáveis (100 segundos de tempo e três vidas). Por sua vez, se a execução do script estiver começando a partir de uma transição entre fases, temos uma partida em andamento, e nesse caso concedemos dez segundos adicionais ao tempo que o jogador já tem à disposição.

Ainda em void Start(), por meio da função GameObject.FindGameObjectsWithTag, realizamos uma contagem de quantos objetos ativos na cena temos com a tag Presente, armazenando essa informação na variável Partida.ObjetosRestantes, que será utilizada mais tarde.

Já dentro de void Update(), iremos decrescer periodicamente o tempo restante do total que o jogador tem à disposição, fazendo com que ele tenha de coletar logo os presentes pelo cenário . Essa redução realizada em Partida.TempoRestante somente será realizada enquanto a variável ControladorAtivo estiver com valor verdadeiro (true). Por enquanto não iremos alterá-la, mas ela nos será muito útil posteriormente.

Também em void Update() está presente a expressão Debug.Log(Partida.TempoRestante). A função Debug.Log é responsável por passar alguma informação textual pela interface do Unity para o programador, sem impactar no jogo em si. No caso, iremos aferir quanto tempo resta de partida antes de inserirmos os elementos visuais que permitirão ao gamer consultar essa informação.

Salve o script, mas não minimize ainda o Visual Studio. Note que há um trecho do código em que é apontado um erro, mas é proposital:

Por meio desse código iremos elaborar uma “artimanha” para fazer com que outros scripts da cena ativa no momento possam realizar ações ou modificações necessárias, como a posterior mudança de estado da variável ControladorAtivo, mesmo sem a classe em questão ser estática.

Retorne ao Unity e, na aba Project, clique duas vezes sobre o ícone do script Partida. No Visual Studio, logo após a linha de declaração da variável TempoRestante, insira a seguinte linha de código e salve o script:

public static ControladorFase atualControladorFase;

Dessa forma, será possível posteriormente realizarmos modificações ao controlador da fase em questão por meio de chamadas a Partida.atualControladorFase.

Após salvar o script, minimize o Visual Studio e volte ao Unity. Iremos criar um GameObject para atrelar o script controlador que acabamos de criar. No final da listagem de objetos da aba Hierarchy, em um espaço vazio da tela, clique com o botão direito do mouse e selecione a opção Create Empty.

Dê o nome de “Controlador”, sem as aspas, ao novo objeto criado. Insira o valor 0 para as coordenadas X, Y e Z de seu Transform. Por meio do botão Add Component da aba Inspector, insira o componente Controlador Fase ao objeto, conforme exemplo a seguir:

Experimente testar a execução do jogo, indo à aba Game e pressionando o botão Play. Preste atenção na informação que será exibida tanto na aba Console quanto na barra inferior da janela do Unity: o número em questão é o tempo restante de partida, que está decrescendo conforme programamos anteriormente, e sendo exibido para nós por meio da ação da função Debug.Log(Partida.TempoRestante).

Interrompa a simulação da execução, pressionando novamente o botão Play. 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 iniciamos a programação dos controladores de fases e partidas de nosso game. Embora não possamos ainda aferir visualmente dentro do jogo o que está ocorrendo, por meio da funcionalidade Debug.Log do Unity já pudemos sentir um “gostinho” do que está por vir, com o desafio do game sendo construído a partir das regras de jogo que implementarmos no projeto. Além disso, experimentamos também a funcionalidade das tags, úteis para auxiliar o programador em tarefas de localização e interação com objetos ativos na cena via código.

Nossos próximos passos envolvem a criação de elementos na tela para indicar ao jogador sobre as diversas variáveis que desenvolvemos, tais como o tempo, vidas restantes e quantos objetos faltam ser coletados. Para isso, iremos alternar entre a realização de atividades no editor do Unity com a codificação via Visual Studio.

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.