Menu

03 – Tutorial Intermediário – Personagem 1 – Habilidades

Bom dia, boa tarde ou boa noite! E ai, curtiram a aula passada? Se curtiram, então acho que vão curtir esse também, embora ela vá ser um pouco mais voltada a programação.


No tutorial de hoje iremos criar as três habilidades do nosso personagem (E um extra, que envolve uma das habilidades):

1 – Habilidade de ataque à distância;
2 – Habilidade de aumento de força;
3 – Habilidade de invencibilidade (Que aqui também terá relação com o fato de que quando o personagem receber um dano, não poder sofrer outro por 1 ou 2 segundos, iguais aos jogos de plataforma).

Habilidade 1

Bom, a primeira habilidade que iremos criar, será o ataque a distância, o qual chamarei de Slash. Então nada melhor do que baixar uma imagem de slash! Caso não tenha alguma legal, pode usar a que eu usarei nesse tutorial, que é essa aqui:

http://opengameart.org/content/slash-effect-collection

Mais precisamente, esta:

Slash symmetrical.png

Bom, com o Sprite em mão, vamos adiciona-lo dentro de uma nova pasta sprites que receberá o nome ataques (E o sprite vira slash):

Lembrando de mudar a Texture Mode de Texture para Sprite:

Agora, podemos arrastar esse Sprite para a aba Hierarchy que automaticamente ele já vai criar um objeto com o Sprite Renderer usando nossa imagem:

Se deixarmos apenas o Sprite lá parado, ai ficar feio, não é mesmo? Então bora criar uma animação usando um único Sprite?

Lembram-se como cria animação? Clica no objeto Slash na aba Hierarchy, vai na aba Animation e pede para criar animação:

Dentro da pasta animation criei uma pasta chamada ataques e nomei a animação para slash:

Agora, aqui é bem simples: Vamos arrastar o Sprite slash duas vezes para dentro da nossa aba Animation:

Clicando no segundo Sprite (Pode clicar ali no Losango, que indica um Keyframe), iremos na aba Inspector e dentro do Sprite Renderer vamos trocar a cor do nosso ataque para azul (ou vermelho ou verde ou o que achar melhor):

Caso, repare lá na aba Animation, apareceu um novo conteúdo Slash: Sprite Render.Color:

Tudo que você alterar e tiver com um keyframe selecionado, irá ser realizado apenas naquele keyframe. Dê um play e veja como ficou. Se ficou muito rápido, basta trocar o Sample para 5. Nessa animação ao sair da ultima cor, ele volta muito rapidamente para a primeira (Afinal ele vai repetir a animação), mas eu quero que seja algo gradual. Então eu clico no primeiro keyframe(no losango), dou ctrl+c e clico em um tempo mais a frente na nossa timeline, apertando em seguida ctrl+v:

Com isso, ele copiou o primeiro keyframe para a posição que você clicou. Assim ele sai do branco e vai para a sua cor selecionada e depois volta para o branco, deixando nossa animação mais natural.

Entendendo isso, você pode fazer o que quiser, pode alterar tamanho, rotacionar, adicionar componente, remover componente…

Bom, o próximo passo agora é adicionar um Box Collider 2D no nosso Slash e ativar a opção Is Trigger:

Se ao fazer isso, você reparar que ele adicionou a ação apenas um keyframe (Normalmente conteúdo alterado fica vermelho), você vai ali ao lado do play e clicar no símbolo de gravação (Record), que ai a alteração não será na animação e sim no objeto:

Lembre-se de excluir a ação realizada apenas na animação. Clica em cima do Slash: Box Collider 2D.Is Trigger e aperta del (delete), caso a alteração tenha ocorrido na animação:

Depois adiciona a opção Is Trigger novamente e verá que ela não foi adiciona como um keyframe na animação.

Agora vamos criar um Script para que cause dano igualzinho ao nosso script Ataque, porém como iremos adicionar a movimentação ao Slash, então ele terá uma pequena diferença. O nosso novo Script vai ser chamar AtaqueAndante (É, estou sem criatividade para nomes) que ficará dentro da pasta Jogador:

Bom, ai aqui vai umas explicações de programação. Nós vimos lá na classe abstrata que podíamos implementar uns conteúdos e que as nossas classes filhas também teriam essas mesmas funcionalidades, correto? Então porque não fazer o mesmo entre classes concretas com as nossas classes de ataque?

Então no nosso script ao invés de herdar de MonoBehaviour, o script AtaqueAndante herdará de Ataque (Que já herda de MonoBehaviour):

Script: AtaqueAndante.cs

using UnityEngine;
using System.Collections;

public class AtaqueAndante : Ataque {
    
	void Update () {
	
	}
}

Caso, observem eu deixei o método Update, pois será nele que eu irei cuidar do movimento. O movimento será bem ao estilo do movimento do nosso personagem, ou seja, vamos precisar saber a direção e a velocidade do ataque. Ambos irei definir como public para outras classes terem acesso:
Script: AtaqueAndante.cs

public bool direita = true; //Direção do movimento e sprite
public float velocidade;    //velocidade do movimento

Quanto a movimentação a farei de forma mais simples usando o Rotation desta vez, uma vez que o nosso Collider está centralizado e será em um objeto único. Então irei aplicar a movimentação sempre para a direita do objeto. Se ele rotacionar em 180 graus (Ou seja, para trás), o lado oposto ainda vai continuar sendo a direita do objeto (Para entender melhor pense que ele vai se mover sempre para frente. Caso ele gire, o outro lado será a nova frente):

Script: AtaqueAndante.cs

using UnityEngine;
using System.Collections;

public class AtaqueAndante : Ataque {

    public bool direita = true; //Direção do movimento e sprite
    public float velocidade;    //velocidade do movimento
     
	void Update () {
        if (!direita)
            transform.eulerAngles = new Vector2(0, 180); //Invertemos o eixo Y

        transform.Translate(Vector3.right * velocidade * Time.deltaTime);

	}
}

Bem simples, né? Esse pensamento de quando é melhor usar o que, você vai ganhando com o tempo. Mas caso ainda não tenha entendido o que eu fiz, vou mostrar na pratica:

Ali é o nosso objeto Slash e a sua Rotation no eixo de Y está em 0. Se eu por em 180…

Ele inverte. Para nós ele estará indo para a esquerda, mas para ele que virou (pense como se fosse uma pessoa), a direita agora é para o outro lado. Vamos ver na pratica? Adiciona o Script AtaqueAndante ao Slash (Aproveitando já pode definir o Dano e a Velocidade):

Caso, resolva dar play e desmarca a opção Direita, verá que ele vai mudar de sentido:

Mesmo que você marque novamente direita, nada vai acontecer, porque no script não colocamos nenhum else lá no if, para inverter ele para o lado da direita (Até porque não será necessário).

Tudo pronto? Então vamos criar um prefab dessa habilidade, arrastando ela para a pasta prefabs/jogador:

Já podemos inclusive excluir esse Slash dai da aba Hierarchy. O próximo passo agora será ir no script do Personagem1, lá no nosso ataque. Após o if (Input.GetButtonDown(“Ataque”)), que verifica se o jogador clicou no botão de ataque, comum, vamos adicionar um novo If, que verifica se o jogador apertou o botão da Habilidade1:

Caso, ele tenha apertado, iremos fazer praticamente a mesma coisa:

1 – Ativar a animação de ataque
2 – Instanciar o objeto Slash
3 – Definir a direção do objeto e Instancia-lo na posição correta
4 – Definir o tempo de destruição do objeto
5 – Dizer que estamos atacando.

Porém, tudo isso vai estar organizado dentro de uma habilidade e não no nosso script do personagem como fizemos antes com o ataque.

Então vamos criar um novo script chamado Slash, que não vai herdar MonoBehaviour e vai implementa a interface IHabilidade dentro de uma pasta em: scripts/jogador/habilidades:

Script: Slash.cs

using UnityEngine;
using System.Collections;
using System;

public class Slash : IHabilidade {
    public void executar() {
        throw new NotImplementedException();
    }

    public int getCustoHabilidade() {
        throw new NotImplementedException();
    }

    public void setPersonagem(GameObject personagem) {
        throw new NotImplementedException();
    }
}

Bom, implementando essa habilidade, vamos criar uma variável privada mesmo do tipo GameObject chamada personagem (Acho que já entenderam o que eu vou fazer de cara com essa habilidade, né?) :

Script: Slash.cs

using UnityEngine;
using System.Collections;
using System;

public class Slash : IHabilidade {

    public Personagem1 personagem;
...

Bom, o próximo passo é justamente esse que você pensou, vamos setar esse personagem dentro do método setPersonagem:

Script: Slash.cs

    public void setPersonagem(GameObject personagem) {
        this.personagem = personagem.GetComponent();
    }

Recupera o objeto GameObject e pegamos o script do Personagem1, afinal já sabemos a quem pertence essa habilidade, não é mesmo? (Ao personagem com o script Personagem1).

Em getCustoHabilidade, vamos definir quanto essa habilidade vai custar para o nosso personagem (2MP):

    public int getCustoHabilidade() {
        return 2;
    }

E em executar, vamos fazer praticamente a mesma coisa que fizemos lá com o ataque:

    public void executar() {
        var prefabSlash = Resources.Load("prefabs/jogador/Slash") as GameObject;
        personagem.animator.SetTrigger("atacou");
        var objSlash = GameObject.Instantiate(prefabSlash).GetComponent();
        objSlash.destroiObjeto(3f); //Destroi após 3 Segundos
        objSlash.direita = personagem.getDireita(); //Arrumar

        if (objSlash.direita)
            objSlash.transform.position = personagem.transform.position + (Vector3.right);
        else
            objSlash.transform.position = personagem.transform.position - (Vector3.right);

        personagem.atacando = true;
        
        personagem.getStatus().usaMagia(getCustoHabilidade());
    }

Linha 1 – Bora ver se vocês entendem o script acima. O Resources.Load serve para buscar algum conteúdo qualquer da sua aba Project (Desde que esteja dentro da pasta Resources). O que estamos buscar é o prefab Slash;
Linha 2 – Em seguida ativamos a animação do personagem através do trigger atacou;
Linha 3 – Na terceira linha estamos instanciando um novo objeto, porém como não estamos dentro de um objeto que herda as características de MonoBehaviour, então temos que instanciar chamando a classe GameObject. Após instanciar, nós já pegamos desse objeto o Script AtaqueAndante.
Linha 4 – Definimos que objeto Slash será destruído em 3 segundos
Linha 5 – Pegamos a direção do personagem e a informações para o script AtaqueAndante. Porém se observar estou usando método getDireita(), que não havíamos criado, então é hora de criar ele no script Jogador:

Script: Jogador.cs

    public bool getDireita() {
        return direita;
    }

Não deixamos a variável direita como public, pois não queremos que nenhuma classe vire nosso personagem de direção, sem ser através das ações do jogador.

Linha 6 – 9 – Definimos a direção em que o objeto será criado.
Linha 12 – Dizemos que nosso personagem está atacando
Linha 11 – Reduzimos os pontos de magia do jogador

Bom, agora precisamos voltar para o nosso script Personagem1.

Nesta parte eu percebi uma coisa que não havia percebido antes e peço desculpas a vocês por isso (Eu apenas planejo o jogo, maaaaaas o crio junto com vocês na hora em que escrevo esses tutoriais, ou seja, eu nunca criei esse jogo antes, por tanto alguns erros do tipo podem acontecer outras vezes .-.). A nossa lista do tipo IHabilidade no Script jogador, não aparece no Inspector mesmo estando como public:

Isso acontece porque o Unity não exibe conteúdo no Inspector do tipo Interface, porque ele não consegue serializar o conteúdo sem conhecer o seu tipo, o que causaria perda de referência entre as scenes. Ou seja, pode exclui essa variável da classe Jogador.

Agora, voltando ao Script do Personagem1, vamos criar uma variável do tipo IHabilidade com o nome habilidade1 e iniciamos a class Slash. Também no método Awake, vamos passar o personagem para o script Slash:

Script: Personagem1.cs

using UnityEngine;
using System.Collections;
using System;

public class Personagem1 : Jogador {

    public Ataque ataque;
    public IHabilidade habilidade1 = new Slash();

    void Awake() {
        status = new Status(20, 6, 10);
        habilidade1.setPersonagem(gameObject);
    }
...

Perfeito? Bom, então o próximo passo é apenas ir ao método ataque informamos para executar a habilidade caso a pessoa tenha apertado o botão:

  if (Input.GetButtonDown("Habilidade1")) 
                habilidade1.executar();

E ai, acabou? Sempre que eu falo isso, você já deve saber que não acabou, né? Pois bem, e o que é que está faltando então? Não percebeu nada estranho? O que está faltando é a gente verificar se o personagem tem MP suficiente para executar essa habilidade e o nível requerido.

Temos dois jeitos de fazer isso. Um é aqui no Script do Personagem1 e o outro é no Script da habilidade Slash, qual jeito vocês acham melhor? Melhor deixar tudo da habilidade lá na própria classe da habilidade, né?
Então primeiro vou alterar o script IHabilidade, para adicionar um novo método (podeUsar), que será responsável por esta verificação, retornando true caso possa usar a habilidade e false caso não:

Script: IHabilidade.cs

using UnityEngine;
using System.Collections;

public interface IHabilidade {

    void setPersonagem(GameObject personagem);
    int getCustoHabilidade();
    void executar();
    bool podeUsar();
}

Já no nosso script Slash, vamos criar lá o método podeUsar que faz a verificação se o personagem tem MP suficiente:

Script: Slash.cs

    public bool podeUsar() {
        return personagem.getStatus().getMP() >= getCustoHabilidade();
    }

Agora basta chamar esse método no script executar:

Script: Slash.cs

    public void executar() {
        if (podeUsar()) {
            var prefabSlash = Resources.Load("prefabs/jogador/Slash") as GameObject;
            personagem.animator.SetTrigger("atacou");
            var objSlash = GameObject.Instantiate(prefabSlash).GetComponent();
            objSlash.destroiObjeto(3f); //Destroi após 3 Segundos
            objSlash.direita = personagem.getDireita(); //Arrumar

            if (objSlash.direita)
                objSlash.transform.position = personagem.transform.position + (Vector3.right);
            else
                objSlash.transform.position = personagem.transform.position - (Vector3.right);

            personagem.atacando = true;

            personagem.getStatus().usaMagia(getCustoHabilidade());
        }
    }

Pronto, antes de executar, fazemos uma verificação se o jogador realmente tem MP maior ou igual ao custo dessa habilidade. Caso tenha, ai sim você pode executar. Pronto, só mandar bala agora no play!

E ai, mandou? E não aconteceu nada? E não faz ideia do que seja? Então acho que você não prestou atenção ao que eu falei em relação ao Resources.Load. Ele pode buscar qualquer conteúdo da sua aba Project, desde que esteja dentro da pasta “Resources”. Entãããão, vamos criar a pasta Resources e jogar nossa pasta prefabs lá pra dentro:

Aqui, se quisermos deixar organizado, podemos deixar já tudo que é recursos de criação aqui dentro, ou seja, animations e sprites:

Agora sim sua primeira Skill vai funcionar de boa.

“Ah Carlos, já que não vamos mais usar a List IHabiidade, podemos apagar a interface IHabilidade e usar a classe direto?”

Poder pode, mas não recomendo. Ela ainda manterá um padrão entre todas as habilidades e caso queira trocar, só basta trocar lá na declaração da variável EEEEE na hora de definirmos o HUD vamos precisar buscar as habilidades, então a interface IHabilidade vai facilitar. ^^

Habilidade 2

Depois que criamos essa habilidade, acho que você já consegue imaginar de forma muuuito fácil uma habilidade de Heal (Cura), não é mesmo? Basta criar o script, verificar se a pessoa apertou o botão e executar o script, que vai buscar o status do personagem e adicionar HP a ele.

Então bora fazer algo mais complicado. Uma habilidade parecida com a de cura, porém ela não vai restaura a vida e sim aumentar a força do personagem por um determinado tempo. Também vamos deixar o personagem meio avermelhado para indicar que ele está com o poder aumentado.

Então aqui é bem simples, se você souber o que tem que fazer, tu faz em… 1 minuto? Então bora lá ver o que teremos que fazer.

A primeira coisa é criar o Script chamado Berserker dentro de scripts/jogador/habilidade:

Agora, abrindo o Script, iremos dizer que ele deve implementar a Interface IHabilidade semelhante ao que fizemos com a Habilidade anterior:

Script: Beserker.cs

using UnityEngine;
using System.Collections;
using System;

public class Berserker : IHabilidade {

    private Personagem1 personagem;

    public void executar()  {
        
    }

    public bool podeUsar() { 
        return personagem.getStatus().getMP() >= getCustoHabilidade();
    }

    public int getCustoHabilidade()
    {
        return 3;
    }

    public void setPersonagem(GameObject personagem)
    {
        this.personagem = personagem.GetComponent();
    }
}

Lembrando, aos que não lembram como a gente implementa a interface de forma rápida no Visual Studio, basta apenas clicar em Implements Interface:

Ali no script acima, criamos a variável do tipo Personagem1, a qual setamos no método setPersonagem e em getCustoHabilidade, definimos que essa habilidade irá custar 3 MP.

Bom, agora é agora é a hora de implementar a classe executar. Nela iremos pegar o ataque do personagem e somar mais + 5 ao seu poder de ataque atual. Para indicar que o personagem está com a habilidade ativa, também irei aplicar um tom meio avermelhado no personagem. Nós vimos durante a animação do ataque, que podemos mudar a cor do Sprite, através do componente Sprite Renderer, não é mesmo? Então o nosso script executar será:

Script: Beserker.cs

      public void executar() {
        if (podeUsar()) {
            var ataque = personagem.getStatus().getAtaque(); //Recupera o Ataque atual
            personagem.getStatus().setAtaque(ataque+5);      //Adiciona +5 ao ataque
            personagem.gameObject.GetComponent().color = Color.red; //Adicionamos uma cor avermelhado ao Sprite
            personagem.getStatus().usaMagia(getCustoHabilidade());
        }
    }

Mais simples que o anterior, né? Mas vamos a explicação:

Linha 1 – Verificamos se o personagem tem MP suficiente para usar a habilidade
Linha 2 – Recuperamos o ataque do personagem buscando o script Status e depois o ataque
Linha 3 – Alteramos o ataque adicionando + 5 ao ataque anterior
Linha 4 – Buscamos o componente Sprite Render e aplicamos a sua cor para vermelho igual ao que fizemos lá na animação do Slash.
Linha 5 – Reduzimos o custo da habilidade ao MP atual do jogador

A cor usada você pode escolher entre várias como Color.black, Color.blue, Color.green… ou aplicando os valores da cor. Exemplo:

personagem.gameObject.GetComponent().color = new Color(1f, 0, 0);

Onde new Color(1f, 0, 0) é igual a Color(R, G,B) que é igual a Color(Red, Green, Blue), que é igual a Color(Vermelho, Verde, Azul). Dai a combinação das cores fica a sua escolha. Se quiser aplica a opacidade, é só adicionar o novo parâmetro ali após o blue: new Color(1f, 0f, 0f, 0.1f); (Quase transparente). Os valores no Inspector vão de 0 (0%) a 255 (100%), porém no script vão de 0f (0%) a 1f (100%), então tu faz uma estimativa ou uma regra de 3.

Bom, agora vamos voltar lá ao nosso script do Personagem1, onde iremos adicionar a variável habilidade2:

Script: Personagem1.cs

using UnityEngine;
using System.Collections;
using System;

public class Personagem1 : Jogador {

    public Ataque ataque;
    public IHabilidade habilidade1 = new Slash();
    public IHabilidade habilidade2 = new Berserker();

    void Awake() {
        status = new Status(20, 6, 10);
        habilidade1.setPersonagem(gameObject);
        habilidade2.setPersonagem(gameObject);
    }
...

E para finalizar, após o if da habilidade1, vamos adicionar o if da habilidade2:
Script: Personagem1.cs

protected override void atacar() {
        if (!atacando) {
            if (Input.GetButtonDown("Ataque")) {
                animator.SetTrigger("atacou");
                var objAtaque = Instantiate(ataque);
                objAtaque.destroiObjeto(animator.GetCurrentAnimatorStateInfo(0).length);

                if (direita)
                    objAtaque.transform.position = transform.position + (Vector3.right * 2);
                else
                    objAtaque.transform.position = transform.position - (Vector3.right * 2);

                objAtaque.dano = status.getAtaque();

                atacando = true;
            }

            if (Input.GetButtonDown("Habilidade1")) 
                habilidade1.executar();

            if (Input.GetButtonDown("Habilidade2"))
                habilidade2.executar();

        } else {
            atacando = animator.GetCurrentAnimatorStateInfo(0).IsName("atacando");
        }
    }

Você já pode dá play e ver o resultado. Caso seu personagem fique muito vermelho, basta fazer umas combinações de cores. Minha dica é, altere a cor lá no próprio Inspector e depois anote os valores RGB:

255 = 100% = 1f
106 = mais ou menos 45% = 0.45f
41 = mais ou menos 15% = 0.15f

OBS: O f é apenas para indicar que o valor é do tipo float.

Lembrando que depois do seu teste, volte tudo para a cor branca, ou seja, tudo 255:

Se esse script fosse do tipo de cura, praticamente acabaríamos aqui, mas eu quero algo um pouco mais trabalhoso para vocês. Eu quero que esse PowerUp dure apenas 5 segundos.

Na programação você pode fazer tudo de milhares de formas diferentes. A forma que eu vou trabalhar aqui, é a que eu acho mais simples e rápida, porém não a mais adequada. Mas fica ai como aprendizado para vocês o que eu quero ensinar.

O Unity possui um método chamado Invoke que chama algum método do seu script após um certo tempo que você determinar. O problema é que esse método só é valido para classes que herdem de MonoBehaviour e a nossa habilidade não herda.

“Ah, então é só colocar lá no nosso script que ela herda de MonoBehaviour e está tudo ok!”

Não, não está, pois esse script não está como componente de nenhum objeto nossa lá na aba Hierarchy (Para ser mais claro, nenhum objeto tem ele adicionado lá no Inspector), ou seja, as funções do MonoBehaviour não vão funcionar, darão erro de referencia nula, já que não existe nenhuma gameObject nele. Neste caso a alternativa seria o método que será chamado não estar na habilidade, mas sim no Personagem1 (Entendeu, por que eu não considero a melhor solução, embora a mais rápida e fácil?).

Bom, então vamos criar um método lá no nosso Personagem1, bem simples:

Script: Personagem1.cs

    void removeBerserker() {
        var ataque = status.getAtaque();
        status.setAtaque(ataque - 5);
        GetComponent().color = Color.white;
    }

Aqui, você já entendeu né? É praticamente o caminho inverso da habilidade Beserker. Recuperamos o ataque, removemos os pontos extras do ataque e depois voltamos a cor original do nosso personagem.

E como faremos para chamar esse método? Bom, aqui é simples, basta voltar agora no script da habilidade Berserker e invocar o método após 5 segundos:

Script: Personagem1.cs

    public void executar() {
        if (podeUsar()) {
            var ataque = personagem.getStatus().getAtaque(); //Recupera o Ataque atual
            personagem.getStatus().setAtaque(ataque+5);      //Adiciona +5 ao ataque
            personagem.gameObject.GetComponent().color = new Color(1f, 0.45f, 0.15f); //Adicionamos uma cor avermelhado ao sprite
            personagem.getStatus().usaMagia(getCustoHabilidade());
            personagem.Invoke("removeBerserker", 5f);
        }
    }

E pronto, o método removeBerserker do script Personagem1 será chamado após 5 segundos.

E com isso acabamos mais uma habilidade. Eu disse que se você souber o que tem que fazer, fazia num piscar de olhos, não?

Habilidade 3

Então bora termina esse tutorial fazendo nossa ultima habilidade? Depois que você já montou a estrutura e a entendeu, fica tudo muito fácil não é mesmo? Com essa aqui será tão rápido senão mais rápido do que a habilidade passada, porém antes de cria-la temos que criar uma coisinhas, que também é simples, mas vai somando um tempinho a mais nesse tutorial, beleza?

Bom, então eu lanço ai a pergunta. Você que está fazendo esse tutorial deve já ter jogado diversos jogos de plataformas tipo esse aqui que estamos fazendo, não é? Então o que é que normalmente acontece quando o personagem sofre um dano e não morre?

Ele fica imortal/invencível por um período de 1 a 3 segundos não é mesmo? Então é justamente isso que iremos fazer agora.

Para isso ser possível, vamos trabalhar é em cima do script da classe abstrata Jogador, onde iremos iniciando adicionar uma variável do tipo float chamada invencibilidade. Essa variável será uma espécie de contador com contagem regressiva. Enquanto for maior que zero, nosso personagem será “imortal”.

Script: Jogador.cs

public float invencibilidade;       //Por quanto tempo o personagem ainda está invencível

O próximo passo é no nosso método de recebeDano, verificar se o personagem pode ou não sofrer dano. Quando é que ele vai poder sofrer dano? Quando o contador de invencibilidade chegar a zero ou for menos que zero, então a atualização é bem simples, basta a gente adicionar adicionar um if no método com essa condição. Porém, se ele entrou na condição, significa que ele sofreu um dano e se ele sofreu um dano, a contagem vai voltar a estaca inicial (Vamos definir como sendo 3 segundos de invencibilidade):

Script: Jogador.cs

    public void recebeDano(int dano) {
        if (invencibilidade <= 0f) {
            status.sofrerDano(dano);

            if (status.estaMorto())
                animator.SetTrigger("morreu");

            invencibilidade = 3f;
        }   
    } 

Conseguiu entender o que fizemos? Basicamente informamos que se o contador de invencibilidade já tiver zerado, então nosso personagem pode sofrer o dano. Por que eu coloquei menor e igual à zero? Por que não só igual à zero? Porque a diferença entre um frame e outro, pode fazer nosso contador passar do 0.

As linhas responsáveis por sofrer dano e verificar se o personagem morreu, nós já sabemos o que fazem (Coisas do tutorial passado). E por fim definimos o tempo que irá demorar ao personagem poder voltar a sofrer dano, que no caso são os 3 segundos.

Bom, o próximo passo é criar um método que irá cuidar da contagem. Chamarei esse método de “invencivel”, beleza? Então o que você acha que temos que fazer nesse método. Uma primeira coisa é fazer nosso personagem piscar, então uma solução simples é fazer o valor alpha channel do nosso Sprite Render (Ou opacidade, como quiser chamar), ficar alterando entre 0f (0%) e 1f (100%). Lembra de lá quando alteramos a cor, que tinha o RGB? Logo abaixo tinha o A de alpha:

Caso A seja 0:

Nosso personagem some. Caso seja 255 (ou 1f no script) que representa 100%, então ele aparece:

Então o nosso script irá apagar e trazer de volta a transparência do Sprite. Porém não podemos já chegar lá aplicando a cor branca com o alpha 0f e a cor branca com o alpha 1f, pois já vimos que nosso personagem pode mudar de cor. Então temos que recuperar a cor atual do personagem, antes de alterar o atributo Color do Sprite Render:

Script: Jogador.cs

    void invencivel() {
        var cor = GetComponent().color;
        if (invencibilidade > 0f) {
            if (cor.a == 1f)
                cor.a = 0f;
            else
                cor.a = 1f;
            invencibilidade -= Time.deltaTime;
        }        
        GetComponent().color = cor;
    }

Então, nesse método aqui recuperamos a cor atual do personagem. Caso invencibilidade for maior que zero, então a gente vai ficar alterando sua transparente. Caso a opacidade (Canal Alpha) já esteja em 100% (cor.a == 1f), então vamos transformar a opacidade em 0f (0%). Senão, fazemos o contrário (transformamos a opacidade em 100% - cor.a = 1f).

Lembrando-se de reduzir a contagem da invencibilidade, do contrário seu personagem vai estar invencível para todo o sempre.

Por fim adicionamos a cor atual com o canal alpha alterado ao Sprite Render. Porém, esse script ainda tem um pequeno problema. Consegue ver qual é? Já pensou se no final da contagem, nosso personagem está com a opacidade zerada em 0%? Nosso personagem vai ficar invisível, então não rola, né?

Vamos aplicar um else, para caso ele não entre nessa condição, então já mude a opacidade para 100% (cor.a = 1f):

    void invencivel() {
        var cor = GetComponent().color;
        if (invencibilidade > 0f) {
            if (cor.a == 1f)
                cor.a = 0f;
            else
                cor.a = 1f;
            invencibilidade -= Time.deltaTime;
        }
        else
            cor.a = 1f;
        
        GetComponent().color = cor;
    }

Bom, bom, já estamos no final da invencibilidade. Agora precisamos colocar ele dentro de um método que fique repetindo direto enquanto o objeto estiver ativo. Vamos por ele dentro do Update, correto?

Errado! Caso se lembrem o método Update roda uma quantidade de vezes diferente de cada pc. Um pc mais fraquinho pode rodar em um segundo 15 frames (15 vezes o update em um segundo), já um pc mais robusto pode rodar o seu jogo a 30 frames pro segundo (30 vezes o update em um segundo), ou seja, em um pc ele ia piscar mais do que em outro.

Então iremos adicionar o nosso método invencivel dentro de outro método do Unity, chamado FixedUpdate. Esse método ele tenta seguir um padrão em todos os pc’s, ou seja ele pode ser executado mais ou menos vezes do que método Update.

Exemplificando melhor, digamos que seu pc rode 10 frames por segundo e o FixedUpdate rode 5 frames por segundo, porém o pc do segundo amigo roda 20 frames por segundo, mas independente disso ele ainda vai rodar o FixedUpdate a 5 frames por segundo. Então, teremos algo do tipo:

Então o Update vai depender do seu processador e o FixedUpdate vai tentar (Tentar, porque pode ser que ele não consiga sempre, mas ao menos tenta), simular o tempo real.

“Massa Carlos! Mas como eu sei qual é o tempo médio de um FixedUpdate?”

Bom, isso você mesmo pode definir lá nas configurações do seu jogo em Edit>>Project Settings >> Time:

Aqui teremos o tempo médio que o seu jogo tentará simular a física real, ou seja, a cada 0,02 segundos o FixedUpdate será executado. Entendido porque usaremos o FixedUpdate aqui e não o Update (Para tentar, ao menos, tentar deixar o pisca pisca igual entre os pc’s):

Script: Jogador.cs

    void FixedUpdate() {
        invencivel();
    }

Você já pode dá play lá no Unity e ver como ficou o seu personagem. Basta apenas alterar o campo Invecibilidade para ver algum outro valor e ver seu personagem piscar por esse tempo:

Com tudo isso terminamos, vamos finalmente para a nossa habilidade? A Habilidade é bem simples, como falei, é mais simples que a passada. Eu poderia até deixar como desafio para vocês, mas bora lá.

A ideia aqui é que quando o personagem aperte o botão da Habilidade3, iremos deixar nosso personagem invencível por 5 segundos. Ou seja, basta apenas fazer uma habilidade que altere o valor da variável invencibilidade para 5f!

Então lá, vamos criar o script “Invencibilidade” dentro da pasta habilidades:

Vamos, abrir esse script e dizer para ele implementar a interface IHabilidade, já podemos aproveitar e fazer aquele mesmo esquema que vocês já conhecem, de definir o custo da habilidade e setar o personagem em uma variável do tipo Personagem1:

Script: Invencibilidade.cs

using UnityEngine;
using System.Collections;
using System;

public class Invencibilidade : IHabilidade {

    private Personagem1 personagem;

    public void executar() {
    
    }

    public bool podeUsar() {
        return personagem.getStatus().getMP() >= getCustoHabilidade();
    }

    public int getCustoHabilidade() {
        return 6;
    }

    public void setPersonagem(GameObject personagem) {
        this.personagem = personagem.GetComponent();
    }
}

“Dai já deve estar pensando, bem que poderíamos criar uma classe abstrata que já fizesse esses métodos iguais automaticamente, né?”

Se quiser, fique a vontade, não tem problema algum e facilitaria bastante, reduzindo várias linhas de código caso tivesse muitas habilidades. Mas no nosso exemplo que é algo pequeno, acho que é desnecessário, mas como falei não atrapalha em nada.

Mas continuando, no nosso método executar, vamos verificar se o personagem tem mp suficiente. Caso tenha, vamos apenas alterar a variável invencibilidade do personagem:

Script: Invencibilidade.cs

    public void executar() {
        if (podeUsar()) {
            personagem.invencibilidade = 5f;
            personagem.getStatus().usaMagia(getCustoHabilidade());
        }
    }

Eu disse que era mais simples que a outra não? Então para finalizar nossa habilidade, vamos adicionar lá ao script Personagem1.

Criar a variável e o inicio no método Start:

Script: Personagem1.cs

using UnityEngine;
using System.Collections;
using System;

public class Personagem1 : Jogador {

    public Ataque ataque;
    public IHabilidade habilidade1 = new Slash();
    public IHabilidade habilidade2 = new Berserker();
    public IHabilidade habilidade3 = new Invencibilidade();

    void Awake() {
        status = new Status(20, 6, 10);
        habilidade1.setPersonagem(gameObject);
        habilidade2.setPersonagem(gameObject);
        habilidade3.setPersonagem(gameObject);
    }
...

Por fim, no método de ataque, vamos adicionar a condição de que se apertou o botão, executa a habilidade:

Script: Personagem1.cs

    protected override void atacar() {
        if (!atacando) {
            //Ataque Normal
            if (Input.GetButtonDown("Ataque")) {
                animator.SetTrigger("atacou");
                var objAtaque = Instantiate(ataque);
                objAtaque.destroiObjeto(animator.GetCurrentAnimatorStateInfo(0).length);

                if (direita)
                    objAtaque.transform.position = transform.position + (Vector3.right * 2);
                else
                    objAtaque.transform.position = transform.position - (Vector3.right * 2);

                objAtaque.dano = status.getAtaque();

                atacando = true;
            }

            //Habilidades
            if (Input.GetButtonDown("Habilidade1")) 
                habilidade1.executar();

            if (Input.GetButtonDown("Habilidade2"))
                habilidade2.executar();

            if (Input.GetButtonDown("Habilidade3"))
                habilidade3.executar();

        } else {
            atacando = animator.GetCurrentAnimatorStateInfo(0).IsName("atacando");
        }
    }

Como nosso personagem já está pronto, com suas habilidades e movimentos, nós podemos até transforma-lo em um prefab dentro da pasta jogador:

Para quem não lembra como faz, é só arrastar o Personagem1 da aba Hierarchy para a aba Project.

E assim finalizamos a construção das nossas habilidades e desse tutorial! Espero que tenham gostado e até a próxima o/

OBS: Caso queira e é a ideia, após testar lá no método podeUsar nas habilidades, você pode também adicionar uma restrição por level do personagem.

Índice Tutorial Intermediário

Download do Projeto

Download do Jogo

Criador do Jogos Indie, amante de jogos, terror, música, anime e programação. Estudante de mestrado com foco em jogos na educação. Louco por Resident Evil e... sei lá, acho que é isso O.o

21 comments

  1. Fabio Pimenta disse:

    Carlos, muito bom este Tutorial estou um pouco atrasado pois só descobri há poucos dias mas logo chego na aula 10 🙂

    Não consegui chamar o método para remover o Berserker.

    “Trying to Invoke method: Personagem1.removeBerserker couldn’t be called”

    O que pode estar havendo ?

  2. Fabio Pimenta disse:

    Dúvida : Recover de HP e MP ao longo do tempo.

    Estou tentando implementar no Update da Classe Jogador.cs :

    private float recoverMP = 1;
    status.setMP += recoverMP * Time.deltaTime;

    Mas retorna o erro :

    Assets/Scripts/Jogador/Jogador.cs(43,24): error CS1656: Cannot assign to `setMP’ because it is a `method group’

    • setMP é um método então você tem que suar como um método. Ali você está usando como uma variavel.

      Variável:
      variavel = recoverMP * Time.deltaTime;
      ou
      variavel += recoverMP * Time.deltaTime;

      Método:
      setMP(recoverMP * Time.deltaTime);
      ou setMP(getMP() + (recoverMP * Time.deltaTime));

      No caso eu recomendo você criar um método para ir recuperando ao invés de setar o valor (Que já faça a soma com o existente)

  3. Fabio Pimenta disse:

    Vlw Carlos, eu acabei fazendo umas modificações e usando o Método recuperaMagia ao invés do setMP e percebi também que as variáveis são int e não float, daí tive que converter.

    private float tempo = 0;

    void Update() {
    //Recupera 1 MP a cada 1 segundo
    if (status.getMP() = 1) {
    status.recuperaMagia (Mathf.FloorToInt (tempo));
    print (“Tempo Int: ” + Mathf.FloorToInt (tempo));
    tempo = 0;
    }
    } //Fim if MP < MaxMP

    Dei uma "otimizada" para só executar quando precisa (MP < MaxMP). Depois coloco isso num método.

  4. JULIO disse:

    Outro erro “The thing you instantiate is null” o que poderia ser ?

  5. wellington disse:

    quando eu aperto a habilidade Invencibilidade ele n sai mais e fica contando infinito

    • nesse caso pode ser que não esteja contando a diminuição do tempo da variável invencibilidade.

      Então certifica que realmente colocou o método invencivel() dentro do FixedUpdate(). (Se digitar errar, o nome FixedUpdate, ele não é executa a cada correção de frame)

      Outro problema é verificar se está usando o termos > e < nos locais corretos! if (invencibilidade <= 0f) (No recebeDano) if (invencibilidade > 0f) (No método invencibilidade)

  6. wellington disse:

    vlw a atenção era só um erro bobo, em Time.deltaTime ao invés de -= tinha colocado +=

  7. Alessandro de Souza disse:

    Boa noite Carlos, por curiosidade quanto custa um jogo com uma fase só, minha empresa pediu para criar algo mas não sei de preço.

    • Olha Alessandro.

      Depende muito. Pelo que eu entendi você não está pensando comercializar o jogo e sim ser contratado para produzir o jogo e APENAS UMA EMPRESA irá comprar, correto?

      Bom, nesse caso tem alguns fatores para definir o preço. Mas minha recomendação é:
      1 – Somar todos os gastos que você vai ter (caso vá precisar comprar sprite, trilha ou algo assim, mas sempre busque conteúdos gratuitos, a menos que seja realmente necessário ter uma identidade própria)

      2 – Montar um cronograma para ter a noção de quantos dias (E HORAS) você vai gastar na produção do jogo. Depois de saber quantas horas o projeto vai ter, você verifica quanto você quer receber por hora.
      Exemplo:
      40 horas de projeto x 15 reais a hora = 600 reais
      +
      50 reais de conteúdos comprados
      (Caso possa reaproveitar os sprites, áudios, texturas, objetos, desconsidere esse custo, pois será um recurso de beneficio próprio e não da empresa)

      Custo Bruto: 650 reais.

      Depois de descobrir o custo bruto, tu pode aplicar em sua própria tabela de bônus e ônus. Exemplo:
      – Cliente chato = Bônus de 50%
      – Se programador e chefe de uma equipe = Bônus de 25%
      – Cliente parceiro = ônus\Desconto de 25%
      – Risco médio – Bônus 20%
      – Risco alto – Bônus 50%

      Muitas pessoas vêem jogo como jogo e software como software, mas um jogo também é um software, então o processo de definir seu preço é o mesmo, com pequenas diferenças (licença, direitos autorais e essas coisas caso haja).

  8. 19129696 disse:

    Boa tarde, Carlos obrigado pela explicação.

  9. Lucas Souza disse:

    Quando eu deixo
    @Resources.Load(“Prefabs/Jogador/Slash”);
    Diz q o objeto q eu tento instanciar é nulo. Ai eu pesquisei e vi que tinha que criar uma pasta Resources no assets e colocar o prefabs la, ai o codigo fica assim:
    @Resources.Load(“Slash”);
    Desse jeito funciono. O que eu tava fazendo de errado no outro?
    Obrigado

  10. Lucas Souza disse:

    kk esquece, não tinha chegado na parte do tutorial que voce explica. Vlw

Deixe uma resposta

Parceiros

Steam Brasil LoboLimão Centro RPG Lab Indie
Mundo Gamer PodTerror

Anunciantes

Aglomerando - Agregador de conteúdo
Uêba - Os Melhores Links GeraLinks - Agregador de links Piadas Idiotas - São idiotas mas o faz rir Tedioso: Os melhores links LinkLog MeusLinks.com - Informação e conteúdo todos os dias para você! Agregador de Links - Madruga Links 4Blogs - Agregador de conteúdo Está no seu momento de descanso né? Entao clique aqui!