12 Eventos

Hablemos más sobre los eventos en SDL2.

Como ya hemos visto anteriormente, la primera tarea en el bucle de juego consiste en procesar los eventos que se hayan producido desde la iteración anterior.

En realidad solo nos interesa procesar aquellos eventos que puedan influir en el desarrollo del juego, y en general podemos ignorar todos los demás.

Hasta ahora, nuestro procesamiento de eventos tenía esta forma:

while (SDL_PollEvent(&test_event)) {
     switch (test_event.type) {
        case evento:
            break;
...
        default: break; // Ignorar los que no nos interesen,
}

Es decir, procesamos aquellos eventos que nos interesen, e ignoramos el resto.

Filtrar eventos

SDL nos da la posibilidad de filtrar eventos. Para ello deberemos crear una función de retrollamada con el prototipo SDL_EventFilter. Esta función recibirá un puntero con datos definidos por la aplicación y una estructura SDL_Event. Si la función retorna con 1, el evento es añadido a la cola de eventos normalmente, y si retorna con 0, el evento se eliminará de la cola, es decir, será filtrado.

Para que esta retrollamada sea invocada hay que activar el filtro mediante la función SDL_SetEventFilter, indicando como primer parámetro la función de retrollamada, y como segundo parámetro la dirección de memoria que se usará para pasar parámetros entre la aplicación y la función de retrollamada.

Por ejemplo, podemos ignorar los primeros diez eventos de pulsaciones de botones del ratón:

int Filtro(void *datos, SDL_Event *evento) {
    int *cuenta= static_cast<int*>(datos);

    if(evento->type == SDL_MOUSEBUTTONDOWN && (*cuenta)--) return 0;
    return 1;
}
...
    int cuenta=10;

    SDL_SetEventFilter(Filtro, static_cast<void*>(&cuenta));
...

La función SDL_SetEventFilter está pensada para crear un filtro de eventos más o menos permanente a lo largo de la ejecución del programa. El filtro estará siempre activo mientras no se desactive o se cambie por otro.

Independientemente de que hayamos definido un filtro mediante esa función, en cualquier punto del programa podemos hacer un filtrado usando la funcioón SDL_FilterEvents. Al igual que SDL_SetEventFilter, esta función también requiere los parámetros de una función SDL_EventFilter, y una dirección de memoria que se usará para pasar parámetros entre la aplicación y la función de filtro. Pero en lugar de que la función de filtro sea invocada cada vez que se produzca un evento, será ejecutada inmediatamente, eliminado de la cola de eventos aquellos para los que la función de filtro retorne con 0.

Por último, podemos obtener la función de filtro actual y la dirección de los datos que recibe mediante la función SDL_GetEventFilter. Esto nos permite encadenar varias funciones de filtro, y al terminar la cadena retornar al estado inicial.

La función SDL_AddEventWatch funciona de forma parecida a SDL_SetEventFilter, con la diferencia de que el valor de retorno de la función de retrollamada de filtro será ignorada y el evento se añade a la cola normalmente.

int Filtro2(void *datos, SDL_Event *evento) {
    if(evento->type == SDL_MOUSEBUTTONDOWN) std::cout << "mouse button down" << std::endl;
    return 0;
}

    SDL_AddEventWatch(Filtro2, NULL);

Para desactivar una retrollamada añadida con SDL_AddEventWatch se puede usar la función SDL_DelEventWatch.

Verificar la presencia de ciertos eventos

A veces nos puede interesar procesar ciertos eventos antes que el resto, ya sea para darles prioridad o para procesarlos en un orden diferente al que se añadieron a la cola de eventos.

La función SDL_HasEvent verifica si la cola de eventos contiene alguno de un tipo concreto, y la función SDL_HasEvents verifica si la cola contiene algún evento dentro de un rango de tipos.

    if(SDL_HasEvent(SDL_MOUSEBUTTONDOWN))
        std::cout << "Se ha pulsado un botón del ratón" << std::endl;

Los eventos se pueden clasificar en diferentes categorías: comunes, ventanas, teclado, ratón, etc. Los identificadores de eventos de cada categoría tienen identificadores con valores consecutivos, de modo que la función SDL_HasEvents puede usarse para saber si la cola de eventos tiene alguno de una categoría determinada. Se pueden consultar los identificadores en el fichero de cabecera SDL_events.h. Por ejemplo, para los eventos del ratón tenemos SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP y SDL_MOUSEWHEEL, con valores 0x400, 0x401, 0x402 y 0x403, respectivamente. Para saber si la cola de eventos contiene alguno procedente del ratón podemos usar algo como esto:

    if(SDL_HasEvents(SDL_MOUSEMOTION, SDL_MOUSEWHEEL))
        std::cout << "Se ha producido algún evento del ratón" << std::endl;

Eliminar eventos

Si los eventos a eliminar ya están en la cola de eventos aún podemos eliminarlos. La función SDL_FlushEvent elimina todos los eventos del tipo indicado de la cola de eventos. La función SDL_FlushEvents eliminará los eventos en el rango especificado.

Usar cualquiera de estas funciones siempre es opcional, ya que podemos ignorar cualquier evento que no nos interese procesar sin que eso afecte al funcionamiento del programa.

Estados de eventos

Si existen determinados tipos de eventos que no nos interesa procesar, en lugar de eliminarlos o filtrarlos, podemos modificar su estado de modo que sean ignorados, o mejor dicho, que se eliminen de la cola de eventos automáticamente. La función SDL_EventState nos permite modificar el estado de un tipo de eventos. El primer argumento será el tipo de evento a cambiar de estado, y el segundo el estado a establecer. Los estados posibles son SDL_IGNORE (o SDL_DISABLE) para desactivarlo y SDL_ENABLE para activarlo. El valor de retorno será el estado del tipo de evento.

Para recuperar el estado de un tipo de evento se puede usar la misma función, con el valor SDL_QUERY, que no modificará el estado, pero recuperará el valor actual, o bien la macro SDL_GetEventState que hace lo mismo.

    SDL_EventState(SDL_MOUSEWHELL, SDL_IGNORE); // Ignorar eventos de rueda del ratón

Actualizar la cola de eventos

La función SDL_PumpEvents recogerá cualquier evento pendiente de todos los dispositivos y actualizará la cola de eventos. Generalmente no es necesario invocar esta función, ya que otras funciones que recuperan eventos de la cola de eventos la invocan implícitamente.

Recuperar un evento

Para procesar la cola de eventos usaremos la función SDL_PollEvent. Como parámetro pasaremos un puntero a un objeto de tipo SDL_Event. Si el valor de retorno es 1, el parámetro contendrá el primer evento retirado de la cola. Si es 0 indica que la cola está vacía. También se puede usar con un argumento NULL para consultar si la cola está o no vacía.

SDL Evento test_event;

while (SDL_PollEvent(&test_event)) {
     switch (test_event.type) {
        case evento:
            break;
...
        default: break; // Ignorar los que no nos interesen,
}

Esperar eventos

La función SDL_WaitEvent funciona de manera similar a SDL_PollEvent, con la salvedad de que si la cola de eventos está vacía, esperará indefinidamente a que se añada un evento.

En la documentación se recomienda no usar esta función en un hilo diferente al programa principal, de modo que no podemos usarla en segundo plano. Es decir, el programa se detendrá hasta que se produzca un evento, por lo que su utilidad es limitada.

La función SDL_WaitEventTimeout es similar, pero permite especificar un segundo parámetro que es un tiempo máximo de espera. Si transcurrido ese tiempo no se ha producido ningún evento, la función regresará con un valor cero.

Añadir eventos a la cola de eventos

No tenemos por qué limitarnos a los eventos definidos por la librería. Siempre es posible añadir eventos a la cola desde nuestros programas, utilizando la función SDL_PushEvent, y pasando como parámetro un puntero a una estructura SDL_Event debidamente inicializada.

Aunque podemos usar esta función para simular pulsaciones de tecla, movimientos o acciones del ratón, etc, su uso más normal es pasar eventos personalizados a la aplicación.

Eventos de usuario

Además de los eventos predefinidos por la librería, SDL permite registrar nuevos eventos de usuario. Podemos usar esos eventos para enviar mensajes a la aplicación desde otros hilos o como un modo de procesar ciertas situaciones que se produzcan durante el juego de forma asíncrona. Por ejemplo, en la fase de inicialización del juego registraremos nuestros eventos de usuario, y cuando se produzca el evento, en cualquier parte del código, asignaremos los miembros de una estructura SDL_Event y añadiremos el evento a la cola.

Para registrar eventos de usuario se usa la función SDL_RegisterEvents indicando como parámetro el número de eventos a registrar. El valor de retorno es el valor del tipo del primer evento registrado, el resto tienen valores consecutivos.

La estructura SDL_Event, que en realidad es una unión, tiene como una de las estructuras miembro SDL_UserEvent, que es la que usaremos para asignar valores al evento.

Disponemos de varios miembros en la estructura. Los comunes a todos los eventos: type y timestamp, un identificador de ventana, que usaremos si esa información es necesaria para procesar el evento, y otros tres miembros:

  • code: un entero de 32 bits. Podemos usarlo para extender el número de eventos, o para indicar un estado entre varios posibles para el evento concreto, o ignorarlo si no lo necesitamos.
  • data1: un puntero genérico que podemos usar para pasar información adicional sobre el evento.
  • data2: otro puntero genérico que podemos usar para pasar información adicional sobre el evento.

Es recomendable usar la macro SDL_zero para rellenar con ceros todos los bytes que forman una estructura SDL_UserEvent antes de asignar valores a los miembros que queramos utilizar.

SDL_Event evento;
Uint32 tipoUser;
Sint32 codigo = 1;
char *datos = "Datos para el evento";
...
    tipoUser = SDL_RegisterEvents(1); // Si el valor de retorno es 0xffffffff indica error
...
    SDL_zero(evento);
    evento.type = tipoUser;
    evento.user.timestamp = SDL_GetTicks();
    evento.user.windowID = 0;
    evento.user.code = codigo;
    evento.user.data1 = static_cast<void*>(datos);
    evento.user.data2 = 0;
    SDL_PushEvent(&evento);
...

Ejemplo 13

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 13 sdl_013.zip 2024-10-24 1763 bytes 35

Tipos de eventos

Existen muchos tipos diferentes de eventos, y probablemente no los veremos todos en este curso.

Puedes consultarlos en la referencia de SDL_Event.

Hay varios relacionados con el teclado, como SDL_KEYDOWN, SDL_KEYUP o SDL_TEXTINPUT.

Eventos relacionados con el ratón, como SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP o SDL_MOUSEWHEEL.

Eventos relacionados con el joystick, como SDL_JOYAXISMOTION, SDL_JOYBALLMOTION, SDL_JOYHATMOTION, SDL_JOYBUTTONDOWN, SDL_JOYBUTTONUP, SDL_JOYDEVICEADDED o SDL_JOYDEVICEREMOVED.

Eventos relacionados con el controlador de juego, como SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERBUTTONDOWN ,SDL_CONTROLLERBUTTONUP, SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMOVED o SDL_CONTROLLERDEVICEREMAPPED.

Eventos relacionados con dispositivos de sonido, como SDL_AUDIODEVICEADDED o SDL_AUDIODEVICEREMOVED.

Eventos de toque de dedo para pantallas táctiles, como SDL_FINGERMOTION, SDL_FINGERDOWN, SDL_FINGERUP o SDL_MULTIGESTURE.

Eventos de arrastre y suelta, como SDL_DROPFILE, SDL_DROPTEXT, SDL_DROPBEGIN o SDL_DROPCOMPLETE.