O mecanismo de eventos da SDL

22 12 2007

Eventos são mensagens que informam que algo aconteceu. São muito úteis para ações esporádicas, tais como o digitar de terminada tecla do teclado ou a exclusão de um objeto. Neste artigo, veremos sobre eventos externos, relacionados ao sistema de janelas e eventos gerados pelo usuário. Também veremos como a SDL encapsula esses eventos e quais estruturas prontas para manipulação de eventos existem.

Fila de eventos

Cada evento gerado no sistema operacional ou no ambiente de janelas é colocado numa fila. Existem vários tipos de eventos que são enfileirados, tais como pressionamento de teclas, redimensionamento de janelas ou movimentação do joystick. A SDL automaticamente lê uma série de eventos interessantes e os converte para seu próprio formato, multiplataforma, formando uma nova fila de eventos. Esses eventos podem ser lidos um-a-um e processados por nosso jogo.

O comando para ler eventos da fila é o SDL_PoolEvent. Esse comando retorna 1 se um evento foi retirado da fila ou 0, se não havia nenhum evento pendente. Como parâmetro, ele recebe um ponteiro para uma estrutura chamada SDL_Event, e é essa a estrutura que guadará a informações à respeito do evento gerado.

Tipos de eventos

Quando desenfileiramos um evento, precisamos descobrir o seu tipo para que ele possa ser tratado adequadamente. É o campo type que realiza essa tarefa. A SDL possui 12 tipos de eventos:

  • SDL_ActiveEvent: Eventos de ativação estão relacionados ao fato da janela estar ou não ativa ou se ela está ou não recebendo eventos de mouse ou de teclado;
  • SDL_KeyboardEvent: Eventos de teclado. Indica se uma tecla foi pressionada ou solta. Informa em qual tecla isso ocorreu e que teclas especiais (como shift, alt ou ctrl) estavam pressionadas no momento em que isso ocorreu;
  • SDL_MouseMotionEvent: Movimentação do mouse. Informa a nova posição do mouse, bem como a quantidade de pixels movimentada desde a última posição. Também informa o estado dos botões enquanto isso ocorreu;
  • SDL_MouseButtonEvent: Ocorre quando um dos botões do mouse é clicado ou solto. A posição do mouse onde isso ocorreu também é reportada;
  • SDL_JoyAxisEvent, SDL_JoyBallEvent, SDL_JoyHatEvent, SDL_JoyButtonEvent: Eventos de joystick. Informam sobre movimentações no joystick, pressionamento de botões, etc. O tema joystick é um pouco mais complexo, pois envolve também detecção dos joysticks e suas capacidades. Por isso será tratado em detalhes num artigo futuro. Se você estiver muito curioso, pode dar uma olhada em seu funcionamento aqui.
  • SDL_QuitEvent: Informa que o usuário deseja sair da aplicação. Isso geralmente ocorre quando o “x” no canto superior direito da janela é clicado. Pode também ocorrer no caso do pressionamento das teclas de atalho para a mesma função, tal como CTRL+F4 no Windows.
  • SDL_SysWMEvent: Indica que um evento específico do sistema de janelas não tratado pela SDL ocorreu. Sua estrutura informa um ponteiro para esse evento. O formato dos dados desse ponteiro é específico do sistema operacional utilizado. Pode ser usado, por exemplo, para se implementar copy&paste na sua aplicação, mas dificilmente precisaremos dele.
  • SDL_ResizeEvent: Informa que a janela foi redimensionada. Informa também o novo tamanho da janela.
  • SDL_UserEvent: Uma estrutura que pode ser usada para armazenar eventos criados por você. Embora a SDL forneça esse mecanismo, veremos estruturas muito mais interessantes e orientadas a objetos em artigos futuros.

Revisitando o processEvents

Agora que já sabemos um pouco mais sobre os eventos, podemos rever o método processEvents do artigo anterior com um pouco mais de detalhes:

void processEvents()
{
    //Primeiro, precisamos criar uma estrutura
    //SDL_Event que guardará informações à respeito
    //do evento gerado
    SDL_Event event;

    //Enquanto temos eventos para processar
    //lemos o evento recebido
    while (SDL_PollEvent(&event) != 0)
    {
        //Verificamos o tipo do evento
        switch (event.type)
        {
            //Se é um evento requisitando a
            //saída da aplicação, ou seja,
            //o pressionamento do 'x' da janela
            case SDL_QUIT:
                exit(0); //Fechamos a aplicação
                break;
        }
    }
}

Eventos de teclas

Muito mais claro, não? Que tal alterarmos o processEvents() e o processLogics() para rodar o triângulo através do pressionar das setas? Os eventos de teclas ocorrem sempre que uma tecla é solta ou pressionada. Como queremos movimentar o triângulo enquanto a tecla estiver pressionada, precisaremos guardar o estado da tecla em duas variáveis auxiliares. Vamos declara-las no início do nosso programa:

//Controle do tempo
Uint32 lastTicks;
Uint32 ticks;

//Estado das teclas
bool leftPressed;
bool rightPressed;

Não se esqueça de definir o valor dessas variáveis para false antes do loop principal começar.Agora, vamos alterar o processEvents para definir o valor dessas variáveis sempre que as teclas forem soltas ou pressionadas.

void processEvents()
{
    SDL_Event event;

    while (SDL_PollEvent(&event) != 0)
    {
        switch (event.type)
        {
            case SDL_QUIT:
                exit(0); //Fechamos a aplicação
                break;

            //Testamos se a tecla foi pressionada
            case SDL_KEYDOWN:
                if(event.key.keysym.sym == SDLK_LEFT)
                    leftPressed = true;
                else if (event.key.keysym.sym == SDLK_RIGHT)
                    rightPressed = true;
                break;

            //Testamos se a tecla foi solta
            case SDL_KEYUP:
                if(event.key.keysym.sym == SDLK_LEFT)
                    leftPressed = false;
                else if (event.key.keysym.sym == SDLK_RIGHT)
                    rightPressed = false;
                break;
        }
    }
}

Como notamos no código, apenas saber que um evento de tecla ocorreu não é suficiente para sabermos para que direção girar o triângulo. Para isso, tivemos que ler informações do evento, que nos foram trazidas na estrutura key, do tipo SDL_KeyboardEvent. Uma das informações mais importantes carregadas por essa estrutura é o keysym.sym. Esse dado refere-se a qual tecla foi pressionada. Cada tecla da SDL é mapeada para uma constante, iniciada por SDLK_. Essa constante independe se a tecla é maiúscula ou minúscula. Você pode ver a lista completa de constantes clicando aqui. Agora, basta alterarmos o processLogics(). Somaremos ou subtrairemos a distância de rotação do triângulo de acordo com a tecla pressionada pelo usuário:

void processLogics()
{
    //Distância para girar (em graus) =
    //velocidade (0.180f) * tempo (ticks)
    float distance = 0.180f * ticks;

    //Está com a seta esquerda pressionada?
    if (leftPressed)
        degreesToRotate += distance;
    //Está com a seta direita pressionada?
    else if (rightPressed)
        degreesToRotate -= distance;
}

Fácil não? Você pode baixar o código completo aqui.

Questionando valores diretamente

Você notou que tivemos que criar uma variável booleana para cada tecla pressionada? Imagine se tivessemos que criar uma variável também para os botões do mouse. Seria trabalhoso se nosso jogo tivesse dezenas de teclas. Além disso, se nosso jogo tiver várias classes, é muito provável que essa informação fique duplicada. Os projetistas da SDL também notaram isso e resolveram facilitar o trabalho criando maneiras de questionarmos diretamente os estados do teclado e do mouse.

Podemos, a qualquer momento, ler o estado das teclas usando a função SDL_GetKeystate. Essa função nos retorna um vetor primitivo, preenchido com o estado de todas as teclas conhecidas pela SDL. Como parâmetro, ela aceita um ponteiro para um inteiro, que será preenchido com o tamanho do vetor de teclas. Esse ponteiro pode ser nulo se não desejarmos essa informação (e isso é o que normalmente ocorre). O retorno da função é o vetor propriamente dito.

Da mesma forma, existe a função SDL_GetMouseState e SDL_GetRelativeMouseState, que aceita como parâmetros variáveis que serão carregadas com a posição do mouse, e retorna o estado dos botões do mouse. A diferença das duas funções é que SDL_GetMouseState irá retornar as posições absolutas do mouse na tela, enquanto SDL_GetRelativeMouseState retorna apenas a diferença em x e y desde a última movimentação.

Alterar o programa do artigo anterior usando essas funções seria ainda mais fácil. Não precisariamos criar qualquer variável global adicional, nem mexer no método processEvents(). O que precisaríamos era apenas alterar o processLogics() para:

void processLogics()
{
    //Distância para girar (em graus) =
    //velocidade (0.180f) * tempo (ticks)
    float distance = 0.180f * ticks;

    //Lemos o estado das teclas
    Uint8* keys = SDL_GetKeyState(NULL);

    //Está com a seta esquerda pressionada?
    if (keys[SDLK_LEFT])
        degreesToRotate += distance;
    //Está com a seta direita pressionada?
    else if (keys[SDLK_RIGHT])
        degreesToRotate -= distance;
}

O código completo dessa solução pode ser baixado aqui. É importante ressaltar que, os valores dessas funções é atualizado pelo SDL_PollEvent. Isso significa que, ainda que você só se utilize das funções SDL_GetXXX, deverá haver em algum ponto do seu código o while que faz a leitura da fila de eventos. Isso geralmente não é um problema, pois é sempre interessante tratar o evento SDL_Quit.

Resumindo

  • Você aprendeu que eventos são enfileirados, e que o método SDL_PollEvent é responsável por recolher esses eventos;
  • Que a estrutura SDL_Event é um union das diversas estruturas de eventos disponíveis, e é usada para descobrirmos informações sobre o evento ocorrido;
  • Quais os tipos de eventos que a SDL possui;
  • Que podemos usar os métodos SDL_GetKeyState e SDL_GetMouseState para questionar a SDL diretamente sobre o estado do mouse e teclado, o que facilita o tratamento desses dispositivos;

A prática leva a perfeição

Agora chegou a hora de você colocar a mão na massa. Altere qualquer um dos programas para:

  1. Fechar a janela quando a tecla ESC for pressionada. Faça isso alterando o método processEvents();
  2. Movimentar o triângulo para esquerda e direita também de acordo com os botões do mouse. Altere o método processLogics() usando o SDL_GetMouseState().

Links para documentação estão descritos acima. Você também pode consultar esse manual, caso ainda se sinta inseguro.


Ações

Informações

2 respostas

23 12 2007
Uzumaki Naruto

Muito bom! Tomara que você continue postando artigos sobre OpenGL e SDL…

[],s

21 02 2008
MarceloJava

Conseguí fazer girar com todos os botões do mouse menos o direito, estou sentindo que terei que estudar mais o C++ antes de continuar.

Deixe um comentário