[POO] Herança na Unity

Herança é um conceito do paradigma de programação conhecido como Programação Orientada a Objetos , que tem como objetivo construir e trabalhar com abstrações de objetos do mundo real, em um nível de código. A Herança dá a capacidade de uma classe/objeto herdar comportamentos previamente definidos em outra classe, sem a necessidade de duplicação do código, permitindo a extensão e a sobrescrita de comportamentos já existentes, de acordo com a necessidade do novo objeto/classe. Para definir uma relação de herança no C#, a "palavra" reservada : (dois pontos) deve ser utilizada na definição da classe que estamos criando, indicando de qual outra classe o nosso script deve herdar. public class Jogador : MonoBehaviour { // Classe Jogador que herda da classe MonoBehaviour } Por padrão, todos os scripts criados na Unity herdam de MonoBehaviour, que é a classe base da Unity para Scripts que serão associados aos GameObjects do jogo. Embora essa definição de herança

Movendo GameObjects com Rigidbody através do teclado - 2D

Movendo GameObjects com Rigidbody através do teclado em um ambiente 2D

E aí, pessoal! Dessa vez, vamos implementar a movimentação de um GameObject utilizando as teclas A, WSD e as setas direcionais do teclado.

Nave controlada pelo teclado com animação de movimentação
Nave controlada pelo teclado com animação de movimentação

Para agilizar o nosso exemplo, vamos utilizar como base o projeto desenvolvido no post Importando Assets para o Jogo, que também pode ser baixado aqui.

No nosso projeto base (uma versão minimalista de Space Shooter), nós já temos os seguintes arquivos de Assets:

  1. Sprite da nave movendo para frente, chamado player;
  2. Sprite da nave movendo para a esquerda, chamado playerLeft;
  3. Sprite da nave movendo para a direita, chamado playerRight;
  4. Script para controlar a movimentação da nave, chamado Nave.
Assets disponíveis no projeto fornecido como base

Podemos ver também que na Hierarquia do projeto existe um GameObject chamado player, representando o personagem principal do nosso jogo: a nave. E é esse GameObject que nós vamos movimentar. Ok?

GameObject player disponível na hierarquia do projeto base

O script Nave já é capaz de controlar a animação de movimentação da nave (rodando para a esquerda e para a direta), quando as teclas A ou D são pressionadas, como podemos ver no script abaixo:

Script para animação de movimentação da nave

O resultado final em nosso exemplo, é a nave rotacionando visualmente, a partir da troca de sprites que é feita no script de exemplo. Caso queira saber mais detalhes sobre como esse exemplo foi desenvolvido, você pode clicar aqui e revisar nosso post Importando Assets para o Jogo, onde esse projeto foi desenvolvido.

Animação de movimentação da nave

Agora que já temos o contexto do exemplo, vamos lá!

Movimentando GameObjects com RigidBody

Quando falamos em movimentar GameObjects na Unity, existem diferentes formas de fazer isso, e cada uma delas é útil para um tipo diferente de mecânica. No nosso caso, estamos trabalhando em um jogo no estilo Space Shooter, e por isso nós vamos implementar a nossa movimentação utilizando o Rigidbody para alterar a velocidade de movimentação da nossa nave.

O que é um Rigidbody?

Rigidbody é o componente da Unity responsável por controlar a simulação de física.
Ao adicionar o rigidbody em um GameObject, mesmo sem a adição de nenhuma linha de código, o GameObject passará a ser controlado pelo sistema de física da Unity, sofrendo ação da gravidade (sendo empurrado para baixo) e reagindo a colisões com outros objetos, caso também possua um componente para colisão.

Existem dois tipos de Rigidbody disponíveis na Unity: Rigidbody e Rigidbody2D.
O Rigidbody é utilizado em GameObjects que devem interagir com a física do mundo 3D (tridimensional) da Unity, enquanto o Rigidbody2D, como o próprio nome sugere, deve ser utilizar em GameObject que devem interagir com a física 2D, ideal para jogos 2D (com apenas duas dimensões).

Em nosso exemplo de Space Shooter, por ser um jogo 2D com movimentação apenas em duas direções, nós vamos utilizar o Rigidbody2D. Para adicionar um Rigidbody2D em nosso GameObject player, basta executar os seguintes passos:
  1. Selecionar nosso GameObject player na Hirarquia;
  2. Clicar no botão Add Component (adicionar componente) que aparecerá na aba Inspector, juntamente com os demais componentes do nosso GameObject;
  3. Digitar o nome do componente que deseja adicionar para filtrar a lista de componentes disponíveis. Neste caso, vamos digitar Rigidbody;
  4. Selecionar o componente Rigidbody2D.

Adicionando Rigidbody2D ao GameObject

Se apertar o botão play para testar o nosso jogo logo após adicionar o Rigidbody2D, poderemos notar que algo inesperado aconteceu...

Nave sofrendo ação da gravidade

A Nave caiu??

Sim...e isso se chama gravidade.
O Rigidbody2D é componente responsável por controlar a simulação de física em um mundo 2D (bidimensional). Quando o nosso jogo foi iniciado, o rigidbody começou a sofrer ação da física, mais especificamente da gravidade, fazendo com que a nossa nave seja puxada para baixo.

Entendi...mas a nossa nave deveria voar. Certo?!

Certo. Embora o Rigidbody2D seja automaticamente afetado pela força da gravidade, nós podemos desativar esse comportamento de uma forma bem simples. No Inspector, com o GameObject da nossa nave selecionado na Hierarquia, podemos ver todas as propriedades do Rigidbody2D. Dentre essas propriedades está a Gravity Scale (escala da gravidade), que define o quanto a gravidade deve afetar esse Rigidbody2D específico.

Propriedades do Rigidbody2D no Inspector, destacando a escala da gravidade

Para evitar que o Rigidbody2D da nossa nave seja afetado pela força da gravidade, basta alterarmos o valor da escala da gravidade (Gravity Scale) para zero, fazendo com que a gravidade não tenha nenhum efeito sobre o nosso GameObject.

Alterando a Escala da Gravidade para zero

Pronto, resolvido. Podemos testar o nosso jogo novamente e ver o que acontece...

Nave sem ação da gravidade

Bem...na verdade, nada aconteceu, mas nesse caso isso é um bom sinal. O nosso objetivo era fazer a nave não ser puxada para baixo pela gravidade e nós conseguimos.
Agora que a nossa nave não está mais caindo no espaço, podemos começar a trabalhar no script de movimentação. Vamos nessa?

Alterações no script Nave

Vamos começar analisando as mudanças que precisamos fazer no nosso script Nave, para movimentar o nosso GameObject, utilizando física e o Rigidbody.
Para utilizar o componente Rigidbody2D em nosso script, precisamos primeiro criar uma nova variável no script Nave, para ser associada ao Rigidbody do nosso GameObject. Em seguida, vamos alterar o método Update para identificar qual tecla está sendo pressionada pelo jogador e movimentar a nave na diretação correspondente. Sendo assim, temos os seguintes passos para serem implementados:
  1. Criar uma variável do tipo Rigidbody2D para ser associada com Rigidbody2D do nosso GameObject player;
  2. Identificar qual tecla dentre as possíveis (A, W, S, D e setas direcionais) está sendo pressionada;
  3. Mover a nave para a direção correspondente a tecla pressionada.

Passo 1 - Criando a variável Rigidbody2D

O primeiro passo é o mais simples e rápido. Basta editarmos o nosso script Nave para adicionar uma nova variável do tipo Rigidbody2D logo abaixo das variáveis já existentes. Aproveitei para nomear a nossa variável também como rigidbody2d.

Adicionando a variável Rigidbody2D ao script Nave

Após a criação da nova variável, nós teremos cinco variáveis em nosso script.

Script Nave após a adição da variável rigidbody2d

Se olharmos para o nosso script Nave no Inspector, veremos que a nossa nova variável rigidbody2d parece em nosso script, porém, ainda sem valor definido (None).

Variável rigidbody2d no Inspector no script Nave

Agora que temos a nossa variável, precisamos associá-la com o Rigidbody2D que adicionamos anteriormente ao GameObject player. Para isso, basta arrastar o componente Rigidbody2D para a variável rigidbody2d do nosso script Nave, e isso pode ser feito de forma simples a partir do Inspector.

Associando Rigidbody2D ao script Nave

Depois de associar o componente Rigidbody2D com a nossa variáviel rigidbody2d, estamos prontos para utilizar o componente Rigidbody2D em nosso script e seguir para o passo 2.

Passo 2 - Identificando a tecla pressionada

Para a movimentação da nossa Nave, nós precisamos identificar qual tecla está sendo pressionada pelo jogador, e como você deve imaginar, essa verificação precisa ser feita a cada frame do jogo para que possamos reagir o mais rápido possível a uma mudança na tecla pressionada.
Para a nossa sorte, temos um método que permite executar ações a cada frame. Qual? Qual? ...Sim! O método Update.

No projeto que utilizamos como base para este exemplo, o método Update já estava sendo utilizado com a mesma finalidade (identificar a tecla pressionada e reagir adequadamente), mas dessa vez nós faremos isso de uma forma diferente.

Vamos ignorar o código existente no projeto base, por enquanto, para focarmos na nossa nova abordagem de movimentação. Ok?

O que muda nessa nova abordagem de movimentação?
O nosso objetivo é identificar se qualquer uma das teclas A, W, S, D ou uma das setas direcionais está sedo pressionada. A Unity possui uma classe específica para nos ajudar com a identificar os inputs (teclas pressionadas, cliques, toques em telas e etc.) do jogador. Como você pode imaginar, essa classe se chama Input.

A classe Input possui métodos como GetKey e GetKeyDown que permitem verificar se uma tecla específica foi ou está sendo pressionada, através de comandos como o exemplo abaixo, onde conseguimos identificar se a tecla A está sendo pressionada:

   void Update()
    {
        if (Input.GetKeyDown(KeyCode.A)) {
            Debug.Log("Tecla A foi pressionada.");
        } else {
            Debug.Log("Tecla A NÃO está sendo pressionada.");
        }       
    }

No nosso caso, onde temos um total de oito teclas para serem verificadas, precisaríamos escrever uma grande quantidade de código para identificar para qual direção o jogador desejar se movimentar.
Existe uma forma mais simples de fazer isso?...por acaso, existe!

Além dos métodos GetKey e GetKeyDown, a classe Input também possui o método GetAxis que permite identificar se alguma das teclas associadas aos eixos x e y está sendo pressionada.

Espera! Como assim, teclas associadas aos eixos x e y?
É simple. A utilização das teclas A, W, S, D e das setas direcionais é um comportamento comum no jogos, por isso, essa teclas já são automaticamente associadas pela Unity, como sendo teclas relacionadas aos eixos x (movimentação para os lados) e y (movimentação para cima e para baixo).

Acho que estou entendendo, mas como isso funciona?
Utilizando o método GetAxis nós podemos informar qual dos eixos nós queremos verificar e o método nos retornará um valor entre -1 e 1, dependendo das teclas pressionadas. Funciona assim:

Para utilizar o método GetAxis nós temos duas opções de eixo:
  • Horizontal
  • Vertical
O eixo Horizontal identifica a movimentação para os lados, a partir do uso das teclas A, D, seta para esquerda ou seta para direita. Já o eixo Vertical, identifica a movimentação para cima e para baixo, a partir do uso das teclas W, S, seta para cima ou seta para baixo.
A movimentação em cada um desses eixos pode variar entre -1 e 1, da seguinte forma:

Quando falamos do eixo Horizontal, o jogador pode mover-se para a esquerda ou para a direita. Certo? Sendo assim, caso a movimentação seja para a esquerda, o método GetAxis retornará um valor negativo, até chegar em -1. Por outro lado (literalmente), caso a movimentação seja para a direita, o método GetAxis retornará um valor positivo, até chegar em 1.
O mesmo comportamento pode ser observado no eixo Vertical, onde valores negativos são retornados pelo método GetAxis quando o jogador pressiona uma tecla de movimentação para baixo, e valores positivos são retornados quando o jogador pressiona uma tecla de movimentação para cima.

Vamos ver o GetAxis em ação para entendermos melhor, utilizando o código de exemplo abaixo para acompanhar a variação do valor do eixo Horizontal quando as teclas A e D são pressionadas:

Código de teste com GetAxis

Ao executar o nosso jogo com o código exibido anteriormente, as seguintes ações foram realizadas na ordem descrita abaixo:
  • Jogo iniciado sem tecla pressionada. O valor 0 é exibido no Console.
  • Tecla A pressionada, fazendo o o valor variar de 0 até -1.
  • Tecla A liberada, fazendo o valor variar de -1 até 0.
  • Tecla D pressionada, fazendo o valor variar de 0 até 1.
  • Tecla liberada, fazendo o valor variar de 1 até 0.
Variação dos valores do eixo Horizontal

Legal...mas como podemos usar isso para a nossa movimentação?
Agora que já estamos identificando a movimentação Horizontal, precisamos apenas identificar também a movimentação Vertical, antes de seguirmos para o próximo passo. Com base no exemplo anterior, isso pode ser feito da seguinte forma:

    void Update()
    {
        float movimentoHorizontal = Input.GetAxis("Horizontal");
        float movimentoVertical = Input.GetAxis("Vertical");
    }
  • A variável movimentoHorizontal possuirá um valor entre -1 e 1, indicando se o jogador deve mover para a esquerda ou para a direita.
  • A variável movimentoVertical também possuirá um valor entre -1 e 1, porém, indicando se o jogador deve mover para baixo ou para cima.

Passo 3 - Movimentando a nave

Para executar a movimentação, nós utilizaremos o componente Rigidbody2D para alterar a velocidade do nossa nave, de acordo com as teclas pressionadas pelo jogador. A alteração da velocidade do Rigidbody2D pode ser feita através da propriedade velocity (velocidade) do Rigidbody2D.
A propriedade velocity (velocidade) do Rigidbody2D é representada por um Vector2. O Vector2 é um vetor bidimensional (duas dimensões) que, neste caso, serve para definir a velocidade de movimentação em dois eixos diferentes: x e y. O eixo x representa a velocidade de movimentação horizontal (para os lados) e o eixo y representa a velocidade de movimentação vertical (para cima e para baixo).

A alteração da velocidade de um Rigidbody2D pode ser feita de forma simples, criando um novo Vector2 com a velocidade de movimentação desejada em cada um dos eixos (x e y), conforme o exemplo abaixo, onde o Rigidbody2D moverá para o lado com velocidade -1 (esquerda).

Alterando a velocidade do Rigidbody2D para (-1, 0)

O resultado da adição desse trecho de código no método Update da nossa nave, gera o seguinte resultado:
Nave movendo para a esquerda com velocidade -1

Está movendo! \o/
Muito bom...mas eu ainda não consigo controlar a nave. :(

Hora de controlar a nave
Pelo nosso exemplo anterior, conseguimos mover a nave definindo uma velocidade fixa no Rigidbody2D, usando a propriedade velocity (velocidade). Agora sabemos como podemos alterar a velocidade da nave, precisamos apenas utilizar as variáveis movimentoHorizontal e movimentoVertical que criamos anteriormente para controlar a velocidade da nave de acordo com as teclas pressionadas pelo jogador. Vamos tentar?

Conforme pode ser visto no trecho de código abaixo, precisamos alterar o Vector2 que define a velocidade da nave para utilizar as variáveis criadas anteriormente, a partir dos valores obtidos a partir do método GetAxis:

    void Update()
    {
        float movimentoHorizontal = Input.GetAxis("Horizontal");
        float movimentoVertical = Input.GetAxis("Vertical");
        this.rigidbody2d.velocity = new Vector2(movimentoHorizontal, movimentoVertical);
    }

Hora de testar nossa movimentação!

Controlando a nave com as teclas (A, W, S, D e setas direcionais)

É isso aí! Funciona! \o/
...mas está muito devagar :(

Definitivamente, precisamos melhorar essa velocidade de movimentação.
...mas primeiro, vamos entender o que está acontecendo.

A velocidade de movimentação da nossa nave está sendo definida de acordo com o valor retornado pele método GetAxis, que como já falamos anteriormente, esse valor varia entre -1 e 1. Sendo assim, nossa velocidade máximo é -1 (quando movendo para a esquerda ou para baixo) e 1 (quando movendo para a direita ou para cima). Para mover mais rápido, precisamos criar um Vector2 com valores mais significativos que -1 e 1. Felizmente, isso também é bem simples de ser feito.

O que vamos fazer?
Nós vamos continuar utilizar GetAxis para identificar para onde o jogador deseja mover a nave. Além disso, nós precisamos criar novas variáveis calcular a velocidade de movimentação da nave, e utilizar essa velocidade calculada para alterar o valor da propriedade velocity do Rigidbody2D.
Como podemos fazer isso? Bem...criando algumas variáveis (e uma constante).
Nossos próximos passos serão os seguintes:
  1. Criar uma constante para determinar a velocidade máxima de movimentação da nave;
  2. Criar uma variável para determinar a velocidade de movimentação no eixo X;
  3. Criar uma variável para determinar a velocidade de movimentação no eixo Y;
  4. Alterar o código que define a velocidade do Rigidbody2D para utilizar nossas duas novas variáveis;
Criar uma constante para determinar a velocidade máxima
Antes de calcularmos a velocidade que será aplicada ao Rigidbody2D em cada um dos eixos, precisamos primeiro definir qual será a velocidade máximo da nossa nave. Podemos definir a velocidade máxima da nossa nave através de uma constante (já que esse valor não será alterado), e utilizar esse valor para calcular a velocidade de movimentação da nave, de acordo com o valor retornado pelo GetAxis.

// Velocidade máxima de movimentação da nave
const int velocidadeMaxima = 8;

Ao criar a constante velocidadeMaxima, decidi atribuir o valor oito por achar que representa uma boa velocidade para a nave. Após completarmos o exemplo, você poderá aumentar e/ou diminuir esse valor de acordo com a velocidade desejada.

Criar uma variáveis para movimentação nos eixos X e Y
Agora vamos calcular a velocidade de movimentação em cada um dos eixos (x e y) de acordo com o valor retornado pelo método GetAxis (Horizontal e Vertical). Esse cálculo será realizado a partir da multiplicação do valor retornado pelo método GetAxis e o valor que definimos para a nossa velocidade máxima, conforme o exemplo abaixo:

// Velocidade de movimentação no eixo X
float velocidadeX = (movimentoHorizontal * velocidadeMaxima);
// Velocidade de movimentação no eixo Y
float velocidadeY = (movimentoVertical * velocidadeMaxima);

Dessa forma, conforme o valor das variáveis movimentoHoziontal e movimentoVertical oscila entre -1 e 1, o valor calculado irá oscilar entre -8 e 8, por ser multiplicado pela nossa constante velocidadeMaxima, que possui valor 8, fazendo com que a nossa nave se mova 8 vezes mais rápido que anteriormente. Entendido?

Em seguida, basta alterarmos a criação do nosso Vector2 que define a velocidade do Rigidbody2D, para utilizar nossas duas novas variáveis velocidadeX e velocidadeY, conforme o trecho de código abaixo:

this.rigidbody2d.velocity = new Vector2(velocidadeX, velocidadeY);

Com isso, o nosso Update completo fica assim:

    void Update()
    {
        float movimentoHorizontal = Input.GetAxis("Horizontal");
        float movimentoVertical = Input.GetAxis("Vertical");

        // Velocidade máxima de movimentação da nave
        const int velocidadeMaxima = 8;

        // Velocidade de movimentação no eixo X
        float velocidadeX = (movimentoHorizontal * velocidadeMaxima);
        // Velocidade de movimentação no eixo Y
        float velocidadeY = (movimentoVertical * velocidadeMaxima);

        this.rigidbody2d.velocity = new Vector2(velocidadeX, velocidadeY);
    }

E agora a nossa além de poder ser controlada pelo jogador, também está movendo mais rápido

Nave movendo mais rápido

Então...chegamos ao fim? Quase...
Lembra do código do nosso projeto base, que deixamos de lado temporariamente? Pois é! Esse é o momento de utilizarmos ele novamente. A partir de agora, essas alterações não afetarão a movimentação da nave, mas serão um extra para termos uma melhoria visual durante a movimentação. Vamos lá?

Nós vamos realizar pequenas alterações no código da animação para podermos utilizá-la com a nossa nova abordagem de movimentação. O código original da animação da nave pode ser visto abaixo:

Trecho de código para rotação da nave, utilizada no projeto base

No código original, a rotação da nave era baseada na tecla pressionada. Se o jogador pressionar a tecla A, a nave vira para a esquerda. Se o jogador pressionar a tecla D, a nave vira para a direita. Caso nenhuma tecla seja pressionada, a nave não vira para nenhum dos lados.
Esse mesmo código servirá para o nosso exemplo, porém, precisaremos alterar as condições que verificam as teclas pressionadas, para utilizar a velocidade de movimentação do jogador no eixo X, da seguinte forma:
  • Se a velocidadeX for menor que zero, a nave vira para a esquerda;
  • Se a velocidadeX for maior que zero, a nave vira para a direita;
Após essas alterações, o trecho de código da animação ficará assim:

if (velocidadeX < 0) {
    // Mostra o sprite de movimentação para a Esquerda
    this.spriteRenderer.sprite = this.spriteEsquerda;
} else if (velocidadeX > 0) {
    // Mostra o sprite de movimentação para a Direita
    this.spriteRenderer.sprite = this.spriteDireita;
} else {
    // velocidade X = 0
    // Mostra o sprite de movimentação para a Frente
    this.spriteRenderer.sprite = this.spriteFrente;
}

Juntando esse novo trecho de código com as nossas alterações anteriores, teremos um método Update que permite o controle da nave a partir das teclas pressionadas pelo jogador e a animação de rotação durante a movimentação:

    void Update()
    {
        float movimentoHorizontal = Input.GetAxis("Horizontal");
        float movimentoVertical = Input.GetAxis("Vertical");

        // Velocidade máxima de movimentação da nave
        const int velocidadeMaxima = 8;

        // Velocidade de movimentação no eixo X
        float velocidadeX = (movimentoHorizontal * velocidadeMaxima);
        // Velocidade de movimentação no eixo Y
        float velocidadeY = (movimentoVertical * velocidadeMaxima);

        this.rigidbody2d.velocity = new Vector2(velocidadeX, velocidadeY);

        if (velocidadeX < 0) {
            // Mostra o sprite de movimentação para a Esquerda
            this.spriteRenderer.sprite = this.spriteEsquerda;
        } else if (velocidadeX > 0) {
            // Mostra o sprite de movimentação para a Direita
            this.spriteRenderer.sprite = this.spriteDireita;
        } else {
            // velocidadeX = 0
            // Mostra o sprite de movimentação para a Frente
            this.spriteRenderer.sprite = this.spriteFrente;
        }
    }


E o resultado final no nosso jogo é o seguinte:

Nave controlada pelo teclado com animação de movimentação

Agora, sim! Nossa nave pode ser controlada pelo jogador usando das teclas A, W, S, D e setas direcionais, além de ter a animação de movimentação que adaptamos do nosso projeto base. Que tal?

O projeto completo desenvolvido nesse post pode ser baixado aqui.

Gostou? Comenta aí...






Comentários

Postagens mais visitadas deste blog

Transição de Fases (navegação entre Cenas) na Unity

GetComponent - Obtendo a referência para outros componentes do GameObject

[POO] Herança na Unity