Eventos, qué son y cómo se usan.
Bienvenidos a un nuevo tutorial billyberjas(C), éste capítulo trata sobre los eventos en SDL. Perdonad el retraso en las entregas, pero tenía muchos eventos pendientes en cola....(después de leer el tutorial, entedereís el chiste

)
Muchos de vostros estareís acostumbrados a la programación lineal (en otros círculos conocida como, "Programación por mis Coj*n*s"

), es decir, mi programa comienza, pide una serie de datos al usuario, y muestra un resultado. Éstos programas son muy típicos al iniciarse en el fascinante mundo de la programación. Vamos a ver un ejemplo:
#include <stdio.h>
void main(void)
{
int a,b,suma;
printf("Introduce dos enteros (separados por comas):"); //imprimo comentario
scanf( "%d,%d" ,&a, &b); //recibo los numeros
suma=a+b; //los sumo
printf("\n La suma de los numeritos es :%d ", suma); //muestro resultado
printf("\n\nGracias por confiar en nosotros "); //Comentario de Salida
}// fin del main
Con éste sencillo programa vemos uno de los fundamentos principales de la programación "por mis Coj*n*es", el usuario del programa sigue NUESTRO camino. El programa no admite mostrar la suma antes de pedir los numeros, ni admite mostrar el comentario de salida antes de mostrar el de entrada, tampoco admite hacer otra cosa que sea introducir dos números separados por comas. Es decir, el usuario está limitado a hacer lo que nostros hayamos pensado.
Ahora imaginaos una típica ventanita de cualquier programa, tiene un botón de cerrar, maximizar, minimizar, un par de campos de texto (para introducir valores), un botón de aceptar, otro de cancelar,etc...
El usuario puede hacer lo que le plazca, en el ORDEN que le plazca. Nada le impide minimizar la ventana, irse a otro programa, volver al nuestro, maximizar la ventana, introducir un valor, equivocarse, borrarlo, volver a introducirlo...y luego pensárselo mejor y darle al botón de cancelar.
Ésto es la llamada programación ORIENTADA A EVENTOS, cuya filosofía es radicalmente distinta a la programación "por mis Coj*n*s" ;-), en vez de decir por dónde va a ir el usuario, y programar ése camino, tendremos que programar TODOS los posibles caminos que el usuario pueda tomar (minimizar,cancelar,etc...), pero no os preocupéis que no es tán difícil como parece, e incluso podremos continuar con nuestra programación lineal con ligeros cambios.
"Muy bien"-Pensareís-" Ahora tengo que programar orientado a eventos, ¿qué narices es un evento? ¿es lo mismo que la programación orientada a objetos? porque yo el Java lo llevo muy mal...."
Calma y tranquilidad, la programación orientada a eventos NO ES LO MISMO que la programación orientada a objetos. Así que podeís respirar tranquilos, que no muerde :).
Podríamos definir un evento como cualquier cosa que requiera nuestra atención, en un entorno visual, podría recibir nuestra atención un movimiento de ratón, una pulsación de teclado, un aviso del Sistema Operativo (me he colgado, voy a cerrar el sistema, etc....) o incluso un evento predefinido por el programador.
Para ello, el Sistema Operativo nos facilitará la tarea de conseguir los eventos, el SO és el que nos va a proporcionar los eventos. Cuando nuestro usuario pase su recién estrenado ratón óptico por nuestra preciosa ventana, el SO nos va a lanzar cienes y cienes de eventos, más concretamente, un evento por cada movimiento que haga. Éste evento se compondrá del tipo de evento:"Movimiento de ratón sobre tu ventana" y de unos parámetros variables (en el caso del ratón, las coordenadas X e Y del puntero).
Obviamente, no necesitamos tratar todos los eventos, (un juego que funcione con el ratón no necesita preocuparse por el teclado, salvo para usar cheats :) ), si un evento no lo vamos a tratar, se lo devolvemos al Sistema Operativo, y nos quedamos tan anchos. Si le devolvemos el evento, significa que el SO tiene que tratar el evento por nostros, así que él SO hará lo que mejor le parezca.
Obviamente, nuestro programa necesitará una cierta remodelación en todo lo referido a la entrada de datos por parte del usuario (teclado,ratón,etc...), ya no vamos a parar nuestro programa hasta que nuestro usuario escriba "OK", sino que vamos a entrar en un bucle donde trataremos los eventos que nos interesan. Vamos a ver un ejemplo, que me parece a mí que os estaís liando :
Imaginaos típico programa visual (con ventanitas,checkboxes y tal) que necesita dos archivos para funcionar, uno de entrada y otro de salida. Tendríamos algo así como ésto.
Una programación lineal tradicional obligaría al usuario a introducir primero el archivo de entrada, y después el archivo de salida. La programación orientada a eventos deja esa decisión en manos del usuario. ¿Quien nos dice que primero va a seleccionar el archivo de salida? ¿Y el de entrada? ¿Puede el usuario introducir a mano la ruta y el nombre del archivo? ¿O puede usar el botón examinar? Tódo esto tendríamos que controlarlo. Pero no os preocupeís porque en muchos casos va a ser incluso más sencillo que la programación lineal :).
En éste ejemplo en concreto tendríamos que tratar el evento "Hago click en el primer botón Examinar", "Hago click en el segundo botón examinar", "Hago click en Aceptar" y "Hago click en cancelar".
En los dos primeros eventos, lanzaríamos un cuadro de selección de fichero, una vez seleccionado y comprobado que és valido, introduciríamos en la Caja de Texto (TextBox) el nombre y la ruta del fichero.
Para el botón cancelar tendríamos que cerrar la ventana, o mostrar un mensaje de "¿Estás seguro?" y continuar con el flujo normal de nuestro programa.
Para el botón aceptar, simplemente tendríamos que comprobar que hay texto en las textbox (recordemos que lo puede haber introducido a mano, o con el botón de examinar), comprobar que es un fichero, que existe y que nos vale. Entonces ya podríamos hacer lo que quisieramos con los ficheros.
Pero cuidado, el usario también puede querer maximizar la ventana, cerrar el sistema operativo, pasar a otra tarea, etc... ¿Pero esos eventos ni siquiera están codificados en nuestro programa? ¿Que pasaría entonces? , pues simplemente, el Sistema Operativo se haría cargo de ellos. Así de sencillo. :)
Después de ésta ¿pequeña? introducción a los eventos, vamos al tema que nos ocupa, SDL y sus eventos.
Antes de empezar, deciros que SDL va a ser el que se peleé con el SO y sus eventos específicos, a nostros siempre nos van a llegar eventos de SDL, ésto facilita mucho las cosas a la hora de hacer nuestro programa/juego multiplataforma, pues los eventos van a ser los mismos. Así que ya no hay excusa para portar tu pedazo de juego a linux, seguro que muchos usuarios te lo agradecerán :).
En primer luegar, deciros que el sistema de eventos se inicializa llamando a
SDL_Init(SDL_INIT_VIDEO);, es decir, que al inicilizar el subsistema de vídeo de SDL, también inicializamos el sistema de eventos.
SDL tratará los eventos que le lleguen del SO metiéndolos en una cola, y gurdándolos hasta que nosotros le pidamos que nos los dé. La estructura en la que SDL nos informará del evento que nos llega es
SDL_Event ( lógico,¿no?).
typedef union{
Uint8 type; //tipo de evento
SDL_ActiveEvent active; //evento de activación (de ventana,foco,etc...)
SDL_KeyboardEvent key; //evento de teclado (tecla pulsada, tecla soltada,etc...)
SDL_MouseMotionEvent motion; // evento de movimiento de ratón
SDL_MouseButtonEvent button; // evento de pulsación de botones de raton
SDL_JoyAxisEvent jaxis; // evento de movimiento del joystick
SDL_JoyBallEvent jball; // evento de movimiento del ¿trackball? del joystick
SDL_JoyHatEvent jhat; //evento de pulsación de los ¿hats? del joystick
SDL_JoyButtonEvent jbutton; //evento de pulsación de los botones del joystick
SDL_ResizeEvent resize; // evento de redefinición de tamaño de la aplicación (resize)
SDL_ExposeEvent expose; // evento de exposición de ventana ( alguna ventana se ha puesto encima de nosotros)
SDL_QuitEvent quit; // evento de .... salida...pues claro...;)
SDL_UserEvent user; //evento de usuario, éstos los lanzamos nosotros, ¿como mola,eh ? XD
SDL_SywWMEvent syswm; //evento del manejador de Ventanas ( nos pelearemos nostros mismos con los eventos de nuestro SO)
} SDL_Event;
-Un momento!!, ¿no habías dicho que era una
estructura ? , ¡¡¡Eso que me has puesto es una
unión!!!
- Yepes ;), una unión de
estructuras, para ahorrar memoria, y facilitar la programación, ahora lo veremos....:)
Cuando nos llegué una
unión de SDL_Event, lo primero que haremos será comprobar su tipo. Una vez que sepamos de qué tipo és. Sólamente tendremos que utilizar la
estructura adecuada para comprobar sus datos. Aquí van los tipos y luego veremos un ejemplo.
SDL_ACTIVEEVENT SDL_ActiveEvent
SDL_KEYDOWN/UP SDL_KeyboardEvent
SDL_MOUSEMOTION SDL_MouseMotionEvent
SDL_MOUSEBUTTONDOWN/UP SDL_MouseButtonEvent
SDL_JOYAXISMOTION SDL_JoyAxisEvent
SDL_JOYBALLMOTION SDL_JoyBallEvent
SDL_JOYHATMOTION SDL_JoyHatEvent
SDL_JOYBUTTONDOWN/UP SDL_JoyButtonEvent
SDL_QUIT SDL_QuitEvent
SDL_SYSWMEVENT SDL_SysWMEvent
SDL_VIDEORESIZE SDL_ResizeEvent
SDL_VIDEOEXPOSE SDL_ExposeEvent
SDL_USEREVENT SDL_UserEvent
El ejemplo:
Imaginaos que tenemos un bucle que lo único que hace es esperar a que haya eventos, y si hay uno, tratarlo.
SDL_Event evento;
int salir=0;
while(!salir) //mientras que no tenga que salir del programa
{
while(dame_evento(&evento)!=0) //mientras que haya un evento en la cola pendiente para ser procesado.
{
switch(evento.type) //miramos de que típo es el evento que nos ha llegado
{
case SDL_SDL_MOUSEMOTION: // es un evento de ratón, en concreto un evento de movimiento
//, oseasé un SDL_MouseMotionEvent, definido en la unión SDL_Event // como [I]motion[/I]
printf("Estás moviendo el ratón en las coordenadas x:%d,y:%d\n",evento.motion.x,evento.motion.y);
break; //salimos del case MOUSEMOTION
case SDL_QUIT: //nos llega un evento de salir del programa, por lo que saldremos del programa
salir=1;
break;
default:
printf("no sé que evento és....\n");
break;
} //fin del switch
} //fin del while (evento)
//si ésto fuera un programa normal, podríamos hacer cosas mientras no haya eventos pendientes :)
} //fin del while(!salir)
printf("Programa terminado\n");
Las variables del evento SDL_MOUSEMOTION las he sacado de la documentación de las SDL.
Ahora veamos cúales son las funciones para obtener eventos de la cola de SDL.
La más importante (y la que se suele utilizar más a menudo) es:
int SDL_PollEvent(SDL_Event *event);Devuelve 0 si no hay eventos en la cola.
Si hay eventos en la cola devolverá 1, y además rellenará el puntero a SDL_Event que le hemos pasado.
Ésta función es muy útil si la utilizamos dentro de un bucle (como en el ejemplo)
Más funciones
int SDL_WaitEvent(SDL_Event *event);Ésta función espera INDEFINIDAMENTE a que ocurra un evento (de cualquier tipo) y rellena el puntero que le hemos pasado.
Devuelve 0 si ha habido un error en la espera, o 1 si ha devuelto algo.
int SDL_PushEvent(SDL_Event *event);Con ésta función podremos "empotrar" nuestros propios eventos en la cola,simplemente nos creamos un evento, rellenamos sus datos y llamamos a ésta función. Puede sernos útil para hacer demos (vamos metiendo las pulsaciónes del teclado, y nuestro programa las interpretará como pulsaciones reales), o para otras cosas que no quiero ni sé contaros :)
int SDL_PeepEvents(SDL_Event *events, int numevents, SDL_eventaction action, Uint32 mask);Ésta función no la vamos a tocar, puesto que es relativamente complicada, y tampoco nos va a servir de mucho de momento. Comentaros simplemente que es una función de propósito general, que sirve para dar altas,bajas y consultas XD en la cola de eventos. También podemos pedir que nos dé los eventos que cumplan una determinada condición. Mas info en la Documentación.
void SDL_PumpEvents(void); Ésta función "actualizará" nuestra cola de eventos, es decir que pedirá al SO los nuevos eventos que hayan podido ocurrir, puede sernos útil cuando empezemos a hacer "virguerías" con el SDL. Digamos que es un
fflush() de eventos.
Nota: Las únicas funciones que llaman implícitamente a SDL_PumpEvents() son SDL_PollEvent y SDL_WaitEvent por lo que si vamos a utilizar las demás funciones relativas a eventos, tenemos que recordar llamar a SDL_PumpEvents para que tengamos eventos en la cola!!Hay varias funciones relacionadas con eventos que no voy a explicar, porque sería muy aburrido y no son indispensables para un correcto funcionamiento de nuestro aplicativo :). Y como lo mejor es predicar con el ejemplo, vamos a intentar hacer algo un poco decente, mover un muñequito por la pantalla, uno de los máximos deseos de cualquier programador serio ;).
Lo vamos a hacer de dos maneras, la tradicional con eventos llamando a
SDL_PollEvent, y la "commando", actualizando nostros mismos los eventos con
SDL_PumpEvents y usuando funciones avanzadas de SDL.
Tradicional
int heroex,heroey;
int salir=0;
SDL_Event evento;
while(!salir) //mientras no haya que salir
{
while(SDL_PollEvent(&evento)!=0) //mientras haya algún evento en la cola
{
switch (evento.type)
{
case SDL_KEYDOWN: //entramos jústo donde queremos, cuando pulsen una tecla.
//entramos en otro switch para saber qué tecla hemos pulsado (buscad en la doc las keysims)
switch(evento.key.keysym.sym)
{
case SDLK_LEFT: //ha pulsado el cursor izquierdo
heroex--;
break;
case SDLK_RIGHT: //ha pulsado el cursor derecho
heroex++;
break;
case SDLK_UP: //arriba
heroey--;
break;
case SDLK_DOWN: //abajo
heroey++;
break;
case SDLK_ESC: //ha pulsado la tecla ESCAPE .... ¿Por qué? ¿No le gusta? ;)
salir=1;
break;
} //fin del switch de los keysyms
break; //fin del case SDL_KEYDOWN
case SDL_QUIT: //si es un evento de salida
salir=1;
break;
default:
printf("No sé que narices me estás contando, pero ya lo tratará nuestro gestor de ventanas :)\n");
} //fin del switch de eventos
} //fin del while (SDL_PollEvent() )
dibuja_heroe (heroex,heroey); //dibuja a nuestro héroe en las coordenadas que queramos
SDL_UpdateRect(.....) //Actualiza pantalla
} //fin del while(!salir)
Éste sería el método tradicional orientado a eventos, como vereís , es un poco engorroso al principio (sobre todo hasta que te acostumbres a los nombres de las estructuras de eventos), pero cuando lleves un par de miles de bucles de éstos....te salen con los ojos cerrados :)
Éste trozo de código entraría en un bucle mientras no haya que salirse del programa
while(!salir), después entraríamos en otro bucle que estaría activo
mientras haya eventos, en el cual,trataremos dos eventos, uno de teclado(SDL_KEYDOWN) y otro de sistema(SDL_QUIT). Si és un evento de teclado, trataríamos la tecla pulsada y actuaríamos en consecuencia, actualizando las coordenadas de nuestro héroe, si es un evento de sistema (SDL_QUIT) pues actualizaríamos el valor de la variable
salir para que en la próxima iteración del bucle, nos vayamos a la playa (o a la piscina :) ).
Después de tratar los eventos, dibujaríamos nuestro héroe en la posición indicada, y actualizaríamos la pantalla.
Y fín de la historia :)
Ahora veamos la manera
friki de tratar eventos. En ocasiones nos va a salir más rentable utilizar ésta manera de tratar eventos, pero saber cúando usar una y cúando usar la otra es un conocimiento que nos dará la experiencia.
Vamos a usar
SDL_PumpEvents(),
SDL_GetKeyState y
SDL_PeepEvents B]Friki-way of life[/B] :P
int heroex,heroey;
int salir=0;
SDL_Event evento;
Uint8 *teclas; //nuestro array de teclas
while(!salir)
{
SDL_PumpEvents(); //actualizamos eventos
teclas=SDL_GetKeyState(NULL); // recogemos el estado actual de las teclas en el array teclas.
if(teclas[SDLK_LEFT]) heroex--; //pa la izquierda
else if(teclas[SDLK_RIGHT]) heroex++; //pa la derecha
if(teclas[SDLK_UP]) heroey--; //parriba
else if(teclas[SDLK_DOWN] ) heroey++; pabajo
if(teclas[SDLK_ESC]) salir=1; //ha pulsado ESC
if(SDL_PeepEvents(NULL, 1, SDL_GETEVENT, SDL_QUIT)>=1) //si hay algun evento en la cola que sea SDL_QUIT
salir=1; //pues salgo.
dibuja_heroe (heroex,heroey); //dibuja a nuestro héroe en las coordenadas que queramos
SDL_UpdateRect(.....) //Actualiza pantalla
}
Mucho más lioso, ¿verdad?. Queda en vuestras manos utilizar la manera que queraís, la ventaja de la segunda es que el muñeco podrá moverse a la vez en los dos ejes, mientras que en el primero (por utilizar un switch, aunque se puede cambiar) solo podrá moverse en un eje a la vez. También se puede utilizar
SDL_GetKeyState cuando veamos que el movimiento no es todo lo preciso que deseamos ya que varias teclas pulsadas a la vez, generan varios eventos, que de la primera manera vamos a tratar por separado, pero en la
friki-way podremos tratarlas a la vez.
Señores, estoy harto, así que si me permiten....me largo.
Ya sabeís si teneís alguna duda, estoy en el msn:
sergio_cia@hotmail.com
Saludos cordiales
Nota: Gracias a las condiciones insalubres de mi trabajo, al finalizar éste tutorial, he acabado con un dolor de manos que no veas....
NO MÁS SANGRE POR PETROLEO