Tutorial Snake 3D usando Unity - Parte 4

Tutorial Snake 3D usando Unity - Parte 4

Iai galerinha, tudo tranquilo com vocês? Hoje vamos aprender como que funciona aquela parada que eu falei sobre o tick de movimento da cobra. De forma bem geral, imagina como se nosso mapa tivesse corredores na horizontal e na vertical, parecido com aqueles do "Bomberman", e a cobrinha só pode dobrar nas esquinas desses corredores.

Kids Grace: Classic Games Review: Super Bomberman 4
Bomberman 4 - PvP arena

No nosso caso, nós não vamos colocar esses pontos no mapa, nós vamos apenas simular os cantos com lógica de programação. Pra isso precisamos primeiro implementar o tick de movimento que vai ser responsável por ditar quando a cobra vai poder se mexer. Mas antes de mostrar o nosso incrível trecho de código, vou explicar pra vocês um conceito de programação que vai nos permitir dar uns passos a mais no nosso objetivo de estudos de lógica. Callbacks. Vamos começar nosso tutorial de hoje, temos muita lógica para absorver.

1. Tick de movimento

E agora, o tão sonhado tick. Pessoal, tudo que precisamos fazer para que este comportamento seja executado de forma desejada é garantir que vamos chamar uma função em intervalos de tempos pré determinados. Só aí então qualquer lógica seguinte será chamada (movimento da cobra, detectar colisões, spawn da fruta, entre outros). Vamos começar criando um novo script e chamando de TickController. Vamos criar um novo objeto na cena chamado TickController e arrastar o novo script pra ele.

Gif 1 - Criando e adicionado o TickController
public class TickController : MonoBehaviour
{
    public float tickCounter;
    float lastTick;

    // Update is called once per frame
    void Update()
    {
        if (Time.time > lastTick + tickCounter)
        {
            lastTick = Time.time;
            // TO DO: callback
        }
    }
}

Revisando este novo trecho de código temos:

  • tickCounter - uma variável pública do tipo float que vai servir pra gente contar o tempo até a próxima chamada de função;
  • lastTick - uma variável privada do tipo float que vai servir para verificar o tempo da última chamada;
  • Time.time - a classe Time da Unity que nos permite manipular algumas variáveis relacionadas do tempo da simulação do jogo. E aqui estamos acessando o valor time dessa variável que dita o tempo desde o começo da simulação.

Por fim, o lastTick recebe o valor atual e assumimos que este foi o momento da última chamada. Deixei um comentário para que a gente possa chamar nosso callback naquela posição específica. Por enquanto vamos adicionar apenas um Debug.Log("Flag") para que a gente possa verificar o tempo das chamadas. Este comando apenas exibe uma mensagem no terminal de logs.

Para o teste, eu coloquei 1 no script. Esperamos que um novo log seja exibido a cada segundo. Você pode controlar o tempo até o próximo movimento da cobra a partir dessa variável.

2. Mas enfim, o que são Callbacks?

Bem, você com certeza já está familiarizado com este conceito no dia-a-dia, mas talvez nunca tenha parado pra pensar em como isso poderia funcionar em um código. Um exemplo simples é você e um colega estarem preparando um bolo. Seu colega sabe decorar o bolo e você sabe preparar a massa para ir ao forno. Vocês então decidem dividir as funções e começam a fazer a receita. Em um dado momento você "Ei, pode ir resolver outra coisa, assim que o bolo estiver pronto eu te chamo para você decorar" e boom entramos no conceito de callbacks. Seu colega neste momento não precisa mais saber qual estado a receita se encontra, pois ele sabe que quando for a vez dele de atuar, ele será notificado.

Para gente poder fazer isso com nosso querido C# vamos usar um tipo de variável chamada delegate para atribuir uma função que será usada para novos comportamentos.

public class TickController : MonoBehaviour
{
    public delegate void OnTick();
    public OnTick onTick;

    public float tickCounter;
    float lastTick;

    // Update is called once per frame
    void Update()
    {
        if (Time.time > lastTick + tickCounter)
        {
            lastTick = Time.time;
            // TO DO: callback
            if (onTick != null)
            {
                onTick.Invoke();
            }
            Debug.Log("Flag");
        }
    }
}

Nosso trecho de código ficou dessa forma. Agora, explicando cada nova linha:

  • public delegate void OnTick - criamos a variável do tipo delegate void. Sim, é uma variável quase como se fosse uma função que retorna um valor void.
  • public OnTick onTick - criamos uma variável chamada onTick do tipo OnTick que acabamos de definir na linha de cima que usaremos para guardar nossas ações de notificação. No exemplo do bolo, sempre que tivermos um bolo pronto vamos notificar ao nosso colega para que ele possa vir decorar.
  • if (onTick != null) - verificamos se a variável tem algum valor ou se é nula para que não haja erros não controlados.
  • onTick.Invoke() - por fim invocamos a função que vai carregar todas as ações que já estão inseridas na variável. Isso vai ficar mais claro no próximo passo.

3. Movimento

Agora que temos um tick funcionando de acordo com um tempo que nós estipulamos vamos voltar lá pro componente da cobra e adicionar um trecho a mais pra gente poder usufruir desse tick.

public class SnakeComponent : MonoBehaviour
{
    TickController tickController;
   
    // Start is called before the first frame update
    void Start()
    {
        tickController = FindObjectOfType<tickController>();
        tickController.onTick += OnTick;
    }

    void OnTick()
    {
        // TO DO: perform action
    }
    
    void OnDestroy()
    {
        tickController.onTick -= OnTick;
    }
}

Ao adicionar esse trecho de código fazemos com que a função OnTick seja chamada a partir do componente Snake sempre que o TickController fizer aquela chamada a cada segundo que se passou. Essas linhas de código são:

  • TickController tickController - uma variável para guardar a referência ao script do tipo TickController responsável pelo tick.
  • tickController = FindObjectOfType<TickController>() - a busca pelo script do TickController e a referência salva em uma variável daquele mesmo tipo.
  • tickController.onTick += OnTick - aqui está o truque dos callbacks. No script do TickController temos uma variável chamada onTick que agora nesta linha está sendo incrementada com o valor de uma função que estamos definindo mais embaixo. Sempre que a função do TickController for executada naquela chamada Invoke() nossa função OnTick também será executada.
  • void OnDestroy() - função padrão da Unity que é chamada quando este componente for destruído. Por garantias de boa execução adicionamos uma remoção da nossa função do tick geral.

Ual. Só falta agora implementar o movimento da cobra a cada tick. E pronto, já devemos ter um resultado satisfatório do nosso movimento. Vejamos aqui, onde tem um comentário dentro da nossa função OnTick do componente Snake, remova o trecho da função Update adicione neste espaço:

void OnTick()
{
    // TO DO: perform action
    transform.position += direction * speed * Time.deltaTime;
}

Conclusão

Só isto deve ser o suficiente para que você já tenha resultados agradáveis no movimento. Vamos fazer correções de acordo com a nova lógica implementada. Ainda precisamos ajustar a direção que nos permite ir em direções opostas mesmo não sendo permitido pela regra do jogo. Ficamos por aqui hoje, espero que este primeiro conceito tenha sido claro e nos próximos tutoriais vamos focar em código ainda mais interessantes, não percam o que está por vir.