Menu

13 – Tutorial Intermediário – Zumbi

Chegamos a parte 13 e costumam dizer que 13 é o numero da sorte, mas não será para vocês, pois esse post vai ser trabalhoso o//. Eu acho que de tamanho ele será menor do que vários passados, mas iremos ter fazer toda aquela parte inicial de criar uma a Interface IInimigo, a classe Abstrat Inimigo com as funções em comuns de todos os inimigos, para só então criar o script do nosso primeiro e querido Zumbi. E para que fique tudo bonitinho, redondinho também vamos trabalhar o que está faltando no script do ataque do Personagem e no script Jogador.

O bom é que depois de tudo isso, os próximos inimigos só precisaremos trabalhar no script final e nas animações.

Bom, sem enrolação, bora iniciar! Aqui vamos criar um script (C#) chamado IInimigo dentro de uma pasta chamada scripts/inimigos:

01- Tutorial Fase 1 Inimigo

Como vocês já devem saber, os conteúdos que começam com um I antes do nome que representa as classes são as Interface, ou seja, esse script será uma interface bem simples por hora:

Script: IInimigo.cs

using UnityEngine;
using System.Collections;

public interface IInimigo {
    void recebeDano(int dano);
}

Essa interface por hora só terá o recebeDano. Acho que será a única interação entre outras classes com os inimigos.
O próximo passo agora é criar a classe abstrata Inimigo também na pasta scripts/inimigos:
Script: Inimigo.cs

using UnityEngine;
using System.Collections;

public abstract class Inimigo : MonoBehaviour, IInimigo {

	void Start () {
	
	}
		
	void Update () {
	
	}

    public void recebeDano(int dano) {

    }
}

Os movimentos dos inimigos serão diferentes de inimigo para inimigo, então aqui não vai entrar informações como direção e velocidade chaoVerificador, forcaPulo, velocidade, direita e por ai vai. Os inimigos também não terão habilidades especiais, porém os métodos abstratos atacar e mover ainda vão existir e serão chamadas no nosso Update:
Script: Inimigo.cs

	void Update () {
	    if (!GCPause.getPausado()) {
            mover();
            atacar();
        }
	}

    protected abstract void atacar();
    protected abstract void mover();

Como o personagem irá se mover ou atacar vai ficar por conta do script do inimigo especifico.

Todavia todo inimigo será ainda terá a classe Status com seu poder de ataca, magia e vida e o animator:

Script: Inimigo.cs

    public Animator animator;
    protected Status status;

O Animator será como public, pois iremos pegar ele direto no objeto no Unity. O Status será como protected porque os objetos filhos também vão usa-los, ok?

Vocês se lembram do problema que eu tinha com exportar o projeto para vocês e ocorrer de perder a tag e a layer? Então, vou definir isso no código, para que nunca perca essas informações. Iremos realizar essa ação logo no método Awake, para caso outro objeto o busque no método Start, não temos nenhum problema. Porém as classes filhas também vão precisar desse método Awake, sendo assim, vamos ter que dizer que esse método é um método virtual para que possam ser modificados pelas classes filhas:

Script: Inimigo.cs

    protected virtual void Awake() {
        this.gameObject.tag = "Inimigo";
        this.gameObject.layer = 11;         //Layer Inimigo
    }

“Opa, e esse layer = 11 ai, Carlos?”.

Vocês provavelmente ainda não criaram a Layer do inimigo, certo? Então, bora cria-la que vocês logo vão entender. De volta ao Unity vá no menu Edit >> Project Settings >> Tags and Layers. Lá você adiciona a Layer do inimigo e vê a sua posição na lista:

02- Tutorial Fase 1 Inimigo

E ai é que está o 11 😉

Voltando ao script, no nosso inimigo, quando a gente recebe dano ficamos invencíveis por um tempo, correto? Aqui não vai acontecer isso, para permitir o personagem fazer combos de dano no inimigo, porém vamos aplicar uma cor avermelhado no inimigo por 0,3 segundo, beleza?

Já que vamos fazer isso, já podemos fazer todo o script do dano:

Script: Inimigo.cs

    public void recebeDano(int dano) {
        status.sofrerDano(dano);

        if (status.estaMorto()) {
            animator.SetTrigger("morreu");                                          //Chama a animação de morte
            var duracaoAnimacao = animator.GetCurrentAnimatorStateInfo(0).length;   //Duração da animação
            Destroy(this.gameObject, duracaoAnimacao);                              //Destroi objeto após animação
            enabled = false;                                                        //Desabilita esse script com os ataques e movimentos
        } else {
            GetComponent().color = Color.red;   //Deixa o inimigo avermelhado
            Invoke("corNormal", 0.3f);                            //Normaliza a cor após 0.3 segundo
        }
    }

    void corNormal() {
        GetComponent().color = Color.white;
    }

Nós já vimos tudo que tem nesse script, mas vamos explicar ainda sim. Primeiro nós causamos o dano ao inimigo (status.sofrerDano). Após isso verificamos se o inimigo morreu ou se continua vivo.

Caso tenha morrido, chamamos a animação morreu através do SetTrigger, pegamos a informação de quantos segundos essa animação dura (animator.GetCurrentAnimatorStateInfo(0).length) e dizemos que esse objeto com o script, será destruído da scene (Destroy) após o tempo da animação concluir. E também desabilitamos esse script (enabled = false), afinal se a animação de morte durar 10 segundos, ele ainda estaria movendo e atacando o inimigo durante a animação.

Caso o inimigo não tenha morrido, vamos aplicar uma cor avermelhada ao sprite do inimigo (GetComponent().color = Color.red) e após 0,3 segundos vamos chamar o método corNormal (Invoke). E no método corNormal, só faz trazer a coloração normal ao inimigo.

Para finalizar esse script, vamos adicionar o método inverter como protected? Alguns scripts provavelmente não vão utiliza-los, porém evitará a gente ter que escrever esse método em mais de um script dos inimigos.

Lembram-se do método Inverter? Ele muda a escala do objeto para o valor inverso, mudando assim a direção dos colliders e do sprite, o utilizamos lá no script Jogador:

Script: Inimigo.cs

    protected void inverter() {
        float x = transform.localScale.x;
        x *= -1;
        transform.localScale = new Vector3(x, transform.localScale.y, transform.localScale.z);
    }

Bom, agora bora para uma particularidade dos inimigos. Alguns inimigos eles não vão estará lá na tela e sim vão surgir com uma animação. Então entrará mais duas variáveis. Uma para saber se o personagem já apareceu e outra para saber a qual distância do personagem o inimig deve aparecer:

Script: Inimigo.cs

    public bool visivel;
    public float distanciaParaSurgir;

Iremos criar um método para verificar se já chegou na distância e caso sim, iremos chamar a animação. E após completar o tempo da animação ativamos a variável visivel:

Script: Inimigo.cs

    IEnumerator surgir() {
        var posicaoPlayerX = GameObject.FindGameObjectWithTag("Player").transform.position.x;
        var distancia = Mathf.Abs(transform.position.x - posicaoPlayerX);
        
        if (distancia < distanciaParaSurgir) {
            animator.SetTrigger("surgiu");
            var duracaoAnimacao = animator.GetCurrentAnimatorStateInfo(0).length;
            yield return new WaitForSeconds(duracaoAnimacao);
            visivel = true;
        }
    }

O que temos de novo aqui é o WaitForSeconds que para de executar o script por alguns segundos. Porém para usar esse método temos usar o “yield return” e retornaremos o tipo IEnumerator, por isso esta estrutura ai diferente para vocês.

Mas explicando o método, nós iremos pegar a posição do personagem no eixo X. Depois verificamos a distância entre o personagem e o objeto. O Math.Abs serve para pegar o valor absoluto (positivo), ou seja, tanto faz se o personagem está a esquerda ou a direita do inimigo, a distância sempre será positiva. Sabendo qual é essa distância, então verificamos se ela é menor que a distancia que definimos para o inimigo surgir.

Caso seja, ativamos a animação através do SetTrigger(“surgiu”), recuperamos o tempo dessa animação e pedimos para o script esperar o tempo da animação para só então mudarmos o status de visibilidade do inimigo (variável visivel) para true. Se preferir pode usar o Invoke como fizemos antes, estou ensinando as duas formas de fazer e você escolhe qual achar melhor.

Por fim, agora no método Update, vamos chamar esse método. Porém a forma de chamar esse método é um pouco diferente, temos que chamar através do método StartCoroutine (Devido ao WaitForSeconds):

Script: Inimigo.cs

	void Update () {
	    if (!GCPause.getPausado()) {
            if (!visivel) {
                StartCoroutine(surgir());
            } else {
                mover();
                atacar();
            }
        }
	}

Ou seja, se o personagem não estiver visível, ele entrará no método surgir. Quando a variável visível se tornar true, então o inimigo poderá se mover e atacar.

Diante disso, podemos dizer também que nosso inimigo só vai receber dano quando o inimigo estiver visível:

Script: Inimigo.cs

    public void recebeDano(int dano) {
        if (visivel) { 
            status.sofrerDano(dano);

            if (status.estaMorto()) {
                animator.SetTrigger("morreu");                                          //Chama a animação de morte
                var duracaoAnimacao = animator.GetCurrentAnimatorStateInfo(0).length;   //Duração da animação
                Destroy(this.gameObject, duracaoAnimacao);                              //Destroi objeto após animação
                enabled = false;                                                        //Desabilita esse script com os ataques e movimentos
            } else {
                GetComponent().color = Color.red;   //Deixa o inimigo avermelhado
                Invoke("corNormal", 0.3f);                            //Normaliza a cor após 0.3 segundo
            }
        }
    }

Com isso terminamos o nosso segundo script de 3 faremos hoje! Ai vai ele completo:

Script: Inimigo.cs

using UnityEngine;
using System.Collections;

public abstract class Inimigo : MonoBehaviour, IInimigo {

    public Animator animator;
    protected Status status;    
    public bool visivel;
    public float distanciaParaSurgir;

    protected virtual void Awake() {
        this.gameObject.tag = "Inimigo";
        this.gameObject.layer = 11;         //Layer Inimigo
    }
		
	void Update () {
	    if (!GCPause.getPausado()) {
            if (!visivel) {
                StartCoroutine(surgir());
            } else {
                mover();
                atacar();
            }
        }
	}

    IEnumerator surgir() {
        var posicaoPlayerX = GameObject.FindGameObjectWithTag("Player").transform.position.x;
        var distancia = Mathf.Abs(transform.position.x - posicaoPlayerX);
        
        if (distancia < distanciaParaSurgir) {
            animator.SetTrigger("surgiu");
            var duracaoAnimacao = animator.GetCurrentAnimatorStateInfo(0).length;
            yield return new WaitForSeconds(duracaoAnimacao);
            visivel = true;
        }
    }

    protected abstract void atacar();
    protected abstract void mover();

    public void recebeDano(int dano) {
        if (visivel) { 
            status.sofrerDano(dano);

            if (status.estaMorto()) {
                animator.SetTrigger("morreu");                                          //Chama a animação de morte
                var duracaoAnimacao = animator.GetCurrentAnimatorStateInfo(0).length;   //Duração da animação
                Destroy(this.gameObject, duracaoAnimacao);                              //Destroi objeto após animação
                enabled = false;                                                        //Desabilita esse script com os ataques e movimentos
            } else {
                GetComponent().color = Color.red;   //Deixa o inimigo avermelhado
                Invoke("corNormal", 0.3f);                            //Normaliza a cor após 0.3 segundo
            }
        }
    }

    void corNormal() {
        GetComponent().color = Color.white;
    }

    protected void inverter() {
        float x = transform.localScale.x;
        x *= -1;
        transform.localScale = new Vector3(x, transform.localScale.y, transform.localScale.z);
    }

}

Agora iremos criar o script do Inimigo que iremos criar o zumbi. Crie um script(C#) chamado Zumbi na pasta scripts/inimigos, que herdará a classe Inimigo:

Script: Zumbi.cs

using UnityEngine;
using System.Collections;
using System;

public class Zumbi : Inimigo {

    protected override void atacar() { }

    protected override void mover() {
        
    }
}

O método atacar, na realidade não iremos fazer nada, ele ficará vazio mesmo. O dano que ele causará no inimigo será ao tocar o inimigo ou seja no OnColliderStay2D. O método mover iremos implementar sim, para o zumbi se mover entre uma posição mínima de X e uma posição máxima de X, mas antes a primeira coisa a fazer é definir os dados do Status do inimigo:

Script: Zumbi.cs

    protected override void Awake() {
        base.Awake(); //Chama o Awake da classe Inimigo
        status = new Status(10, 0, 5); //HP = 10 | MP = 0 | Ataque = 5
    }

Pronto, agora para o movimento, iremos precisar saber se ele está andando para direita ou para esquerda. E a posição que ele vai andar no máximo para esquerda (minX) e o máximo para direita (maxX), assim como a velocidade que nosso zumbi vai andar:

Script: Zumbi.cs

    public bool direita;
    public float velocidade;
    public float minX;
    public float maxX;

O sprite do inimigo que usaremos já é virado para a esquerda, então se ele estiver com o checkbox da direita marcada, no método Awake, já invertermos ele da esquerda para direita:

Script: Zumbi.cs

    protected override void Awake() {
        base.Awake(); //Chama o Awake da classe Inimigo
        status = new Status(10, 0, 5); //HP = 10 | MP = 0 | Ataque = 5

        if (direita)
            inverter(); 
    }

Agora no método mover é bem simples. Vamos fazer o personagem sair andando sem parar só trocando o lado e caso ele ultrapasse o limite invertemos a sua direção:

    protected override void mover() {
        if (direita) {
            transform.Translate(Vector3.right * velocidade * Time.deltaTime); //Anda para direita

            if (transform.position.x > maxX) {  //Passou do limite da direita
                inverter();
                direita = false;
            }
        }
        else {
            transform.Translate(-Vector3.right * velocidade * Time.deltaTime); //Anda para esquerda

            if (transform.position.x < minX) { //Passou do limite da esquerda
                inverter();
                direita = true;
            }
        }
    }

Quando direita for true, o inimigo vai andar para direita até passar do maxX que você definiu, o invertendo de direção e fazendo ele andar para esquerda. Quando direita for false, ele vai andar para esquerda e quando passar do minX ele vai virar para direita e seguir para direita.

Pronto, nosso inimigo já anda agora irá atacar quando tocar no inimigo através do método OnCollisionStay2D:

Script: Zumbi.cs

    void OnCollisionStay2D(Collision2D colisor) {
        if (colisor.gameObject.tag.Equals("Player") && !status.estaMorto()) {
            var personagem = colisor.gameObject.GetComponent();
            personagem.recebeDano(status.getAtaque());
            animator.SetTrigger("atacou");
        }
    }

Bem simples o código. Apenas verificamos se quem encostou foi um objeto com a tag Player e se o nosso zumbi não está morto, depois buscamos o script IPersonagem desse objeto, causamos o dano de acordo com o ataque do inimigo e por fim chamamos a animação de ataque através da Trigger atacou! Acabamos o terceiro e ultimo script que iremos criar hoje:

Script: Zumbi.cs

using UnityEngine;
using System.Collections;
using System;

public class Zumbi : Inimigo {

    public bool direita;
    public float velocidade;
    public float minX;
    public float maxX;

    protected override void Awake() {
        base.Awake(); //Chama o Awake da classe Inimigo
        status = new Status(10, 0, 5); //HP = 10 | MP = 0 | Ataque = 5

        if (direita)
            inverter(); 
    }

    protected override void atacar() {}

    protected override void mover() {
        if (direita) {
            transform.Translate(Vector3.right * velocidade * Time.deltaTime); //Anda para direita

            if (transform.position.x > maxX) {  //Passou do limite da direita
                inverter();
                direita = false;
            }
        }
        else {
            transform.Translate(-Vector3.right * velocidade * Time.deltaTime); //Anda para esquerda

            if (transform.position.x < minX) { //Passou do limite da esquerda
                inverter();
                direita = true;
            }
        }
    }

    void OnCollisionStay2D(Collision2D colisor) {
        if (colisor.gameObject.tag.Equals("Player") && !status.estaMorto()) {
            var personagem = colisor.gameObject.GetComponent();
            personagem.recebeDano(status.getAtaque());
            animator.SetTrigger("atacou");
        }
    }
}

Embora a gente só vá criar esses 3 scripts, ainda temos que alterar outros scripts do personagem. No script Jogador, vamos ao criar o método awake como um método protected e virtual para definir o tag e layer do personagem direto no código:

Script: Jogador.cs

    protected virtual void Awake() {
        gameObject.tag = "Player";
        gameObject.layer = 10;
    }

Consequentemente, teremos que mudar o script dos Personagens:

Script: Personagem1.cs

    protected override void Awake() {
        base.Awake();
        status = new Status(20, 6, 10);

        habilidade1 = new Slash();
        habilidade2 = new Berserker();
        habilidade3 = new Invencibilidade();
        
        habilidade1.setPersonagem(gameObject);
        habilidade2.setPersonagem(gameObject);
        habilidade3.setPersonagem(gameObject);
    }

Script: Personagem 2.cs

    protected override void Awake() {
        base.Awake();
        status = new Status(10, 12, 5); //HP 10 | MP 12 | Ataque 5

        habilidade1 = new AtaqueTriplo();
        habilidade2 = new PuloDuplo();
        habilidade3 = new Cura();

        habilidade1.setPersonagem(this.gameObject);
        habilidade2.setPersonagem(this.gameObject);
        habilidade3.setPersonagem(this.gameObject);
    }

Prontinho. Agora no script GCFase, vamos criar um método para reiniciar a fase, quando o jogador morrer:

Script: GCFase.cs

    public void reiniciarFase() {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

O SceneManager.LoadScene como já vimos carrega uma scene através da sua posição no Build Scenes ou do seu nome. O SceneManager.GetActiveScene(), pega a scene atual. Então estamos pegando a scene atual e depois o seu nome, a chamando no LoadScene, fazendo com que a fase seja recarregada!

Bora continuar nossas alterações! Agora no script Jogador (Cuidado para não confundir os scripts, prestem atenção no nome dos scripts), lá na parte onde o jogador morre, iremos pegar o tempo da duração da animação e invocar o método reiniciarFase após esse tempo:

Script: Jogador.cs

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

            if (status.estaMorto()) {
                animator.SetTrigger("morreu");
                var duracaoAnimacao = animator.GetCurrentAnimatorStateInfo(0).length;
                var GCFase = FindObjectOfType();
                GCFase.Invoke("reiniciarFase", duracaoAnimacao);
            }
                
            invencibilidade = 3f;
        }   
    }

Estamos quase terminando! Agora no script Ataque no método OnTriggerEnter2D, além de destruir o nosso ataque, iremos causar dano ao inimigo:

Script: Ataque.cs

    void OnTriggerEnter2D(Collider2D colisor) {
        if (colisor.gameObject.tag.Equals("Inimigo")) {
            var inimigo = colisor.gameObject.GetComponent();
            inimigo.recebeDano(dano);
            Destroy(gameObject);
        }
    }

Também vamos dizer que todo objeto que tiver esse script Ataque ou scripts filhos de Ataque vai receber a Layer do Personagem, através do método Awake, deixando o nosso script assim:

Script: Ataque.cs

using UnityEngine;
using System.Collections;

public class Ataque : MonoBehaviour {

    public int dano;  //Dano causado pelo ataque

    void Awake() {
        gameObject.layer = 10; //Layer do personagem
    }

    public void destroiObjeto(float tempo) {
        Destroy(gameObject, tempo);
    }

    void OnTriggerEnter2D(Collider2D colisor) {
        if (colisor.gameObject.tag.Equals("Inimigo")) {
            var inimigo = colisor.gameObject.GetComponent();
            inimigo.recebeDano(dano);
            Destroy(gameObject);
        }
    }
}

Pronto, agora sim, acredito que terminamos todos os ajustes dos nossos scripts. Criamos a Interface de inimigos, o script abstrato com os métodos padrões a todos os inimigos, o script especifico do inimigo zumbi. Geramos o dano no inimigo quando o ataque o acerta e reiniciamos a fase quando o nosso personagem morre.

Então todos os próximos passos serão no editor do Unity. Quanto ao sprite do nosso inimigo será o Zombie disponibilizado gratuitamente no site OpenGameArt pelo autor irmirx:

http://opengameart.org/content/zombie-animations

Após baixar, basicamente iremos usar os sprites appear, attack, die e walk. Então crie as pastas correspondentes (andando, atacando, morrendo, surgindo) em Resources/sprites/inimigos/zumbi:

03- Tutorial Fase 1 Inimigo

Crie um objeto na aba Hierarchy e o chame de Zumbi (Tanto faz a Scene, no final vamos transforma-lo em um prefab que pode ser exportado para qualquer outra scene, mas se você por na Fase1, melhor). Em seguida na aba Animation peça para criar uma animação:

04- Tutorial Fase 1 Inimigo

Salve a animação com o nome “escondido”, dentro da pasta Resources/animations/inimigos/zumbi:

05- Tutorial Fase 1 Inimigo

Essa animação será vazia assim, sem nada mesmo, então já pode criar uma nova animação chamada surgindo (Lembre-se de arrumar a velocidade da animação através do sample):

06- Tutorial Fase 1 Inimigo

Depois crie o andando (Essa animação eu recomendo por um sample baixo como 5, pois a velocidade do zumbi será lenta, afinal zumbis são lentos e não rápido como no Guerra Mundial Z u.u), atacando e morrendo:

07- Tutorial Fase 1 Inimigo

Agora é a vez de irmos para a aba Animator, onde vamos criar os parâmetros:

atacou – Trigger
surgiu – Trigger
morre – Trigger

08- Tutorial Fase 1 Inimigo

A animação inicial será a escondido que irá para surgindo quando a condição do Trigger surgiu for ativado (Desmarquem o Has Exit Time e a Transition Duration vai para 0):

10- Tutorial Fase 1 Inimigo

Depois iremos criar a transição de surgindo para andando sem condições, apenas o Has Exit Time, porém podem tirar o Transition Duration:

11- Tutorial Fase 1 Inimigo

Em seguida a transição de andando para atacando, quando o trigger atacou for ativado (Desativar o Has Exit Time e Transition Duration):

12- Tutorial Fase 1 Inimigo

Fazemos agora o script voltando de atacando para andando apenas com o Has Exit Time e sem o Transition Duration (Aqui não precisa de imagem, né?). E por fim de Any State para morrendo quando o Trigger morreu for ativado sem o Has Exit Time e com o Transition Duration igual a 0:

13- Tutorial Fase 1 Inimigo

Agora temos que definir quais animações ficam ou não em Loop. As animações atacando, morrendo e surgindo não terão loops:

19- Tutorial Fase 1 Inimigo

Perfeito, agora é a hora da gente adicionar o Box Collider 2D, Circle Collider 2D, Rigidbody2D e o script Zumbi ao nosso querido e amável Zumbi. Na hora de Colocar os Colliders no Sprite Renderer que foi adiciando durante a animação, você pode por o Sprite do Zumbi andando só para ter uma noção do tamanho dele:

14- Tutorial Fase 1 Inimigo

Se quiser pode aumentar a sua escala (Scale) para ficar mais ou menos do tamanho do nosso personagem, porém sem estragar a qualidade da imagem do zumbi:

15- Tutorial Fase 1 Inimigo

16- Tutorial Fase 1 Inimigo

(Quando for fazer essas alterações, cuidado para o Record da aba Animation não estar selecionada. Caso um dos campos fique vermelho, você já fica atendo que a alteração foi apenas em um dos sprites e não na animação toda).

17- Tutorial Fase 1 Inimigo

Crie a Tag Inimigo caso não já a tenha criado anteriormente.

18- Tutorial Fase 1 Inimigo

Agora adicione o Script ao Zumbi informando para pegar o seu Animator, a distancia que o personagem tem que estar dele para ele surgir, a velocidade da movimentação (Coloquei 2, para ser lento) e a posição mínima e máxima que ele pode andar horizontalmente:

20- Tutorial Fase 1 Inimigo

Para saber uma distância legal, você mesmo pode mover o zumbi pela scene e ver a posição em X:

21- Tutorial Fase 1 Inimigo

22- Tutorial Fase 1 Inimigo

E aplicar lá no Script Zumbi. Depois do teste feito usando o sprite, pode deixar ele sem o sprite no Sprite Renderer (como None mesmo) e adicionar um ícone para a gente saber onde ele está:

23- Tutorial Fase 1 Inimigo

Para finalizar, agora nós vamos nas configurações do seu jogo em Edit >> Project Settings >> Physics 2D e removemos a interação dos colisores do Inimigo em tudo que não for Personagem e Piso. Isso evitará que um inimigo fique prendendo ou empurrando outro por exemplo.

24- Tutorial Fase 1 Inimigo

Já pode testar seu jogo:

25- Tutorial Fase 1 Inimigo

26- Tutorial Fase 1 Inimigo

Estando tudo certo é só agora criar o prefab do zumbi na pasta Resources/prefabs/inimigos:

27- Tutorial Fase 1 Inimigo

E com isso finalizamos mais um tutorial! o///
Nem foi difícil foi? O próximo Boss também será fácil e baseado neste inimigo, ou seja, apenas criar as movimentações.

Í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

13 comments

  1. Fabio Pimenta disse:

    Muito bom Carlos !!! Além de muito bem explicado como todos seus tutoriais, é super gratificante ver tudo funcionando no final.

    Nos próximos Tutoriais de Inimigos (Fase 2 – Monstro – 11/03/2016, Fase 3 – Monstro – 21/03/2016 e Fase 4 – Monstro – 01/04/2016) veremos outros comportamentos de I.A. como perseguir (e deixar de perseguir) o Player, Ataques a Distância, etc ?

    Estou tentando implementar uma forma de mostrar o Level e HP do mob ou acima de sua cabeça ou então uma HUD à direita na tela (talvez mais indicada para Boss). Veremos algo do tipo ?

    Mais uma vez Obrigado !

    • Durante o segundo Boss, senão me engano, eu pensei em fazer, mas terminei deixando como desafio para vocês, pois na realidade é algo bem simples e fácil igual ao do personagem.

      Apenas cria uma barra de vida que está atrelada ao Boss. Quando ativa-lo, também habilita o hud (hudBoss.SetActive(true))

  2. Fabio Pimenta disse:

    Como fazer o inimigo virar antes de atacar ? O que está ocorrendo é que se o Personagem colide com o Inimigo enquanto ele anda para a direita ele não se vira para atacar.

    • em um tutorial mais a frente eu explico, mas adiantando, basicamente para ver o inimigo para o lado do player, você pode subtrair a posição dele atual em X com a posição do Player

      Digamos que o inimigo está na posição X = 3 e o player na posição X = 5.

      Então:
      3 – 5 = 2

      Se o valor for maior que zero, significa que ele está a direita do inimigo. Se for menor que zero, ele está a esquerda. Ai é só fazer um flip ou rotacionar ^^

  3. Fabio Pimenta disse:

    Tentei dentro do método da colisão testar se o x do Player for menor que o x do Inimigo ele chamar o método inverter mas não funcionou. Testei com print os valores e na colisão sempre o x do Player é menor que do Inimigo ao colidir por trás o que prova que ele está à esquerda.

    Zumbi.cs

    void OnCollisionStay2D(Collision2D colisor) {
    if (colisor.gameObject.tag.Equals(“Player”) && !status.estaMorto()) {

    if (GameObject.FindGameObjectWithTag(“Player”).transform.position.x < transform.position.x)
    inverter();
    animator.SetTrigger("atacou");
    personagem.recebeDano(status.getAtaque());

    }
    }

    • ao invés de buscar novamente o objeto com a tag, você pode chamar direto o colisor. E além disso também é preciso de uma variavel para indicar se o inimigo está para esquerda ou direita:

      //personagem a esquerda e inimigo virado para direita
      if (colisor.gameObject.transform.position.x < transform.position.x && direita) { direita = false; inverter(); } Em tutoriais mais para frente, vão ter algumas mudanças nesse scripts, que vão se adaptando com a chegando de novos inimigos ^^

      • Fabio Pimenta disse:

        Vlw Carlos, funcionou dessa forma !!!

        • Fabio me tira uma dúvida. Seu objeto com a tag Player, tem algum objeto filho na aba Hierarchy? Se tiver, verifica se eles também possuem a tag “Player”. Caso sim, é dai de onde veem os outros objetos com essa tag.

          Outra coisa que pode ser feita é lista todos esses objetos:

          var lista = FindGameObjectsWithTag(“Player”);
          foreach (var obj in lista)
          Debug.Log(“Nome do objeto: ” + obj.name);

    • ah esqueci de comentar. Você estava tendo problema com a tag Player, né? Pode ser que ela esteja pegando outro objeto ao invés do Player de verdade, então por isso use o colisor ao invés do FindObjectWithTag 😉

  4. Mauricio Araujo disse:

    Sobra a distancia minima e maxima que o inimigo pode andar. Como eu faria para faze-lo andar 5 unidades de medida para direita e depois para a esquerda a partir do lugar do eixo x que ele começou?

    Tentei usar da seguinte forma:
    criei uma variável public Transform posicaoInimigo e atrelei ao Inimigo
    e tentei fazer o seguinte

    public float minX = posicaoInimigo.position.x – 5;
    public float maxX = posicaoInimigo.position.x + 5;

    o que está errado?

    • Na lógica nenhuma.Você realmente pega a posição máxima e minima subtraindo da posição do inimigo, porém

      você não vai conseguir usar o posicaoInimigo já na declaração das variáveis minX e maxX, por elas ainda não terem sido iniciadas. Você terá que usar dentro do:
      void Awake() {
      minX = posicaoInimigo.position.x – 5;
      maxX = posicaoInimigo.position.x + 5;
      }

      Mas não porque você cria o posicaoInimigo, se poderia usar o transform? O.o

  5. julio disse:

    meu zumbi não sai do chão ele fica flutuando no ar e quando eu aperto o botão de ataque ele desce pra baixo e morre, e quando ele morre aparece (The objective of type ataque has been destroyed como faço para arrumar ? )

    • huuuu. Tem algo beem estranho ai

      O fato estar flutuando pode ser que ele esteja com um Collider maior do que o seu tamanho ou esteja sem o Rigidbody2D.

      A mensagem sobre o Objeto do tipo ataque foi destruído, é porque algo está tentando acessar um objeto que não existe mais.

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!