Tutorial Allegro 5 #12 – Ajustando Frames por Segundo FPS

Padrão

Olá pessoal! Voltando com a série sobre programação de jogos usando Allegro 5, hoje vamos falar sobre como ajustar a taxa de FPS (Frames Por Segundo). Geralmente, utilizamos essa técnica para evitar que um jogo seja executado em velocidades diferentes em máquinas com configurações diferentes. Por exemplo, um jogo pode rodar na velocidade “normal” em um PC mais antigo, e rodar extremamente rápido em um PC mais novo, de forma que seja impossível jogá-lo neste PC. Dessa forma, a limitação da taxa de frames é um requisito praticamente indispensável ao criar qualquer jogo.

Para mostrar como isso funciona, e a diferença entre limitarmos ou não a taxa de frames, vamos fazer um aplicativo que exibirá um texto descendo a tela. Ao pressionar a tecla Enter, limitaremos ou não a taxa de frames. Com isso vocês perceberão a diferença do movimento. Este tutorial foi adaptado do site Lazyfoo.net (lá ele utiliza SDL e C++).

Então, vamos dar uma olhada no código e em seguida comentar os pontos mais importantes dele.

#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>

#include <stdio.h>
#include <stdbool.h>

const int LARGURA_TELA = 640;
const int ALTURA_TELA = 480;

const int FRAMES_POR_SEGUNDO = 60;

ALLEGRO_BITMAP *fundo = NULL;
ALLEGRO_DISPLAY *janela = NULL;
ALLEGRO_EVENT_QUEUE *fila_eventos = NULL;
ALLEGRO_FONT *fonte = NULL;

double tempoInicial = 0;

bool inicializar()
{
    if (!al_init())
    {
        fprintf(stderr, "Falha ao iniciar a Allegro.\n");
        return false;
    }

    if (!al_init_image_addon())
    {
        fprintf(stderr, "Falha ao inicializar allegro_image.\n");
        return false;
    }

    al_init_font_addon();

    if (!al_init_ttf_addon())
    {
        fprintf(stderr, "Falha ao inicializar allegro_ttf.\n");
        return false;
    }

    if (!al_install_keyboard())
    {
        fprintf(stderr, "Falha ao inicializar teclado.\n");
        return false;
    }

    janela = al_create_display(LARGURA_TELA, ALTURA_TELA);
    if (!janela)
    {
        fprintf(stderr, "Falha ao criar a janela.\n");
        return false;
    }

    al_set_window_title(janela, "Teste: Taxa de Frames");

    fila_eventos = al_create_event_queue();
    if (!fila_eventos)
    {
        fprintf(stderr, "Falha ao criar fila de eventos.\n");
        return false;
    }

    al_register_event_source(fila_eventos, al_get_keyboard_event_source());
    al_register_event_source(fila_eventos, al_get_display_event_source(janela));

    return true;
}

bool carregarArquivos()
{
    fundo = al_load_bitmap("background.jpg");
    if (!fundo)
    {
        fprintf(stderr, "Falha ao carregar plano de fundo.\n");
        return false;
    }

    fonte = al_load_font("BAVEUSE.TTF", 32, 0);
    if (!fonte)
    {
        fprintf(stderr, "Falha ao carregar fonte.\n");
        return false;
    }

    return true;
}

void finalizar()
{
    al_destroy_bitmap(fundo);
    al_destroy_font(fonte);
    al_destroy_event_queue(fila_eventos);
    al_destroy_display(janela);
}

void iniciarTimer()
{
    tempoInicial = al_get_time();
}

double obterTempoTimer()
{
    return al_get_time() - tempoInicial;
}

int main(void)
{
    bool sair = false;
    int frame = 0;
    bool limitado = true;

    if (!inicializar())
    {
        return -1;
    }

    if (!carregarArquivos())
    {
        return -1;
    }

    while (!sair)
    {
        iniciarTimer();

        while (!al_is_event_queue_empty(fila_eventos))
        {
            ALLEGRO_EVENT evento;
            al_wait_for_event(fila_eventos, &evento);

            if (evento.type == ALLEGRO_EVENT_KEY_UP)
            {
                if (evento.keyboard.keycode == ALLEGRO_KEY_ENTER)
                {
                    limitado = !limitado;
                }
            }
            else if (evento.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            {
                sair = true;
            }
        }

        al_draw_bitmap(fundo, 0, 0, 0);

        al_draw_text(fonte, al_map_rgb(255, 255, 255), LARGURA_TELA / 2,
                     ((ALTURA_TELA + al_get_font_ascent(fonte) * 2)
                      / FRAMES_POR_SEGUNDO) * (frame % FRAMES_POR_SEGUNDO)
                     - al_get_font_ascent(fonte), ALLEGRO_ALIGN_CENTRE,
                     "Testando Taxa de Frames");

        al_flip_display();

        frame++;

        if (limitado && (obterTempoTimer() < 1.0 / FRAMES_POR_SEGUNDO))
        {
            al_rest((1.0 / FRAMES_POR_SEGUNDO) - obterTempoTimer());
        }
    }

    finalizar();

    return 0;
}

Analisando o código acima, temos lá no começo a declaração da constante FRAMES_POR_SEGUNDO, definida como 60. Este é o número geralmente utilizado para jogos. Em seguida, após as declarações comuns da janela, fundo, fonte e fila de eventos, temos uma variável do tipo double chamada tempoInicial. Ela será utilizada no cálculo do tempo para a atualização da tela. Nas inicializações, nenhuma novidade (inicializamos as bibliotecas e criamos a janela e a fila de eventos). Em seguida, temos a função carregarArquivos() que carrega a imagem de fundo e a fonte, além da função finalizar(). Após isso, tempos duas funções de controle de tempo: a função inicializar() que obtém o tempo atual através da função al_get_time(). A função al_get_time() retorna um valor double correspondente a quantidade de segundos desde que a biblioteca Allegro foi inicializada. Logo após, temos a função obterTempo() que retorna a diferença entre o tempo atual (al_get_time() novamente) e o início, quando o contador foi disparado.

Na nossa função main(), temos uma variável chamada frame que será responsável pelo deslocamento do texto na tela (animação) e uma flag booleana chamada limitado, que indicará se a limitação de 60 frames por segundo deverá ser aplicada ou não.

No looping principal, temos a verificação de eventos para fechar a janela, além da verificação da tecla Enter, que inverterá o conteúdo da flag limitado. Após isso, temos a parte em que desenhamos o fundo e o texto. No caso do texto, o que é modificado é a posição y, representada pelo 4º parâmetro da função al_draw_text. Basicamente, o deslocamento é feito pelo resultado do trecho (frame % FRAMES_POR_SEGUNDO), que representará a posição que o texto será desenhado. Como a nossa taxa de frames por segundo é de 60, o resultado desse resto de divisão será um valor entre 0 e 59, de forma que o texto terá 60 valores progressivos para o parâmetro y, sendo também ajustado pela divisão pelos frames por segundo da posição do texto centralizado na tela. Ficou meio complicado de entender, né? Mas não se preocupe, com o tempo você aprende a bolar umas lógicas mirabolantes também 😀

Por fim, incrementamos o valor de frame, atualizamos a tela e, se houver a limitação e ainda não decorreu-se 1/60 de segundo, faremos uma pausa através da função al_rest() esperando que este tempo passe.

O resultado deverá ser algo mais ou menos assim:

Faça o teste por você mesmo. O texto deverá estar passando pela tela a uma velocidade razoável. Experimente agora apertar a tecla Enter para ver o quanto o seu processador aguenta: a diferença deverá ser muito grande (pelo menos aqui o negócio ficou extremamente rápido, em um i5). Imagine em um jogo isso… ficaria “injogável” numa velocidade tão alta.

Assim, dá pra perceber a importância da limitação de frames por segundo!

Para compilar:

  • Windows: -lallegro-5.0.5-mt -lallegro_image-5.0.5-mt -lallegro_font-5.0.5-mt -lallegro_ttf-5.0.5-mt
  • Linux: -lallegro -lallegro_image -lallegro_font -lallegro_ttf

Os arquivos que utilizei podem ser baixados daqui.

Então é isso pessoal! Até a próxima!