En nuestro tutorial anterior, creamos una aplicación simple que mostraba los conceptos básicos del manejo de eventos en SFML. En este tutorial, ampliaremos un poco más ese concepto y mostraremos cómo leer el teclado, tanto mediante eventos como mediante sondeo directo. Aunque no está estrictamente relacionado con SFML (ya de todos modos), también cubriremos los generadores de números aleatorios como parte de nuestra demostración, una tarea muy común en el desarrollo de juegos.
Como siempre, hay un Versión en video HD de este tutorial.
Anteriormente usamos el método pollEvent() de RenderWindow para verificar y responder al evento Cerrado. Ahora veamos el proceso de respuesta a los eventos del teclado.
while (renderWindow.pollEvent(event)){ //Handle events here if (event.type == sf::Event::EventType::Closed) renderWindow.close(); //Respond to key pressed events if (event.type == sf::Event::EventType::KeyPressed){ if (event.key.code == sf::Keyboard::Space){ // Do something here } } }
Como podemos ver, responder a un evento KeyPressed es casi idéntico a un evento Closed. La diferencia clave es que, una vez que haya identificado que el evento es un evento de teclado, tendrá información adicional disponible en la estructura de teclas del evento. El valor de key.code es un sf::Teclado::Tecla enumeración para cada clave disponible. Además del evento KeyPressed, también hay un evento KeyReleased que se envía cuando se suelta la tecla. De manera predeterminada, KeyPressed se activará una y otra vez si se mantiene presionado. Si prefiere que solo se dispare un solo evento al presionar una tecla, puede cambiar este comportamiento usando RenderWindow setKeyRepeatEnabled().
Este es un ejemplo de programación impulsada por eventos en la que su programa responde a los eventos a medida que ocurren. A veces, sin embargo, preferiría sondear la entrada y luego esperar a que ocurra. Por ejemplo, es posible que desee preguntar «oye computadora, ¿qué teclas se presionan?» o “¿la tecla de control está presionada?”. Afortunadamente, SFML admite esto desde el primer momento, así:
if (sf::Keyboard::isKeyPressed(sf::Keyboard::R)) if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl)) // Do something
Este fragmento de código primero verifica si la tecla ‘R’ está presionada actualmente. Luego verifica si la tecla Control está presionada, como puede ver en el ejemplo anterior, las teclas de control izquierda y derecha son eventos separados. Puede mezclar y combinar el manejo de entradas impulsadas por eventos y sondeadas como desee, pero tenga cuidado de no encontrarse en una situación en la que esté manejando accidentalmente la misma entrada varias veces.
Vamos a crear un ejemplo completo en un segundo que reúne todos estos procesos cambiando aleatoriamente el color de la pantalla según las teclas que se presionen. Primero, cubramos ese proceso de crear un número aleatorio, un proceso muy común en el desarrollo de juegos. De hecho, la generación de números aleatorios solía ser parte de SFML, pero a partir de SFML 2.0 se eliminó, en parte porque no tenía sentido y en parte porque ahora está bien arraigado en el lenguaje de manera multiplataforma.
Hay dos formas en que podemos generar números aleatorios, utilizando el estilo C más antiguo o el estilo C++ 11 más moderno. La nueva forma es más apropiada para generar números verdaderamente aleatorios por razones de seguridad y no tiene un estado global, pero la antigua forma de estilo C debería ser más que suficiente para el desarrollo de juegos, así que elige la forma que más te guste. El proceso es muy similar… generas una secuencia de números aleatorios usando alguna forma de valor inicial, luego masajeas los resultados en el rango numérico que deseas. Veamos primero la forma C de generar aleatorios:
srand(time(NULL)); //seed random number generator with the current time auto randomNumber = rand() % 255;//generate a random number then confine it to a value of 0-255.
La primera llamada envía el generador de números aleatorios utilizando la hora actual del sistema. Luego llamamos a rand() para obtener un número aleatorio. Una cosa importante a tener en cuenta es que el proceso de inicialización es bastante lento, mientras que la llamada rand() es un poco más liviana. A continuación, observamos la forma C++:
std::uniform_int_distribution<int> randomColorRange(0, 255); std::random_device rd; std::mt19937 randomNumbers(rd()); auto randomVal = randomColorRange(randomNumbers);
Este ejemplo hace básicamente lo mismo que la versión anterior. El objeto uniform_int_distribution es un objeto liviano que se puede usar para transformar los datos generados por el algoritmo mt19937 en valores int reales dentro del rango definido. El mt19937 es una implementación del algoritmo Mersenne Twister para generar números aleatorios. El dispositivo aleatorio es un objeto incorporado para crear una semilla para su generador de números aleatorios en lugar de usar la hora actual (aunque es muy posible que el dispositivo aleatorio use el tiempo detrás de escena). El uso de random_device es pesado, al igual que mt19937, así que tenga cuidado donde genera números aleatorios. Sin embargo, usar esos números generados con uniform_int_distribution es liviano.
Bien, ahora sabemos cómo responder a los eventos del teclado, sondear el teclado directamente y generar números aleatorios, atámoslo todo en un solo ejemplo.
// This example demostrates the main loop #include "SFML/Graphics.hpp" #include <iostream> #include <random> int main(int argc, char ** argv) { sf::RenderWindow renderWindow(sf::VideoMode(640, 480), "SFML Demo"); sf::Event event; // A Clock starts counting as soon as it's created sf::Color color(sf::Color::Red); // C++ 11 way of generating a random between 0 - 255 // mt19937 is an implementation of the Mersenne Twister pseudo random number generator // random_device() returns a random number to use as a seed for the mt algorithm... slow however so that's why we dont just use it for all randoms if you were wondering // mt results arent in a human friendly format, so we use uniform_int_distribution to "shape" the results to our range and type // uniform_int_distribution is a fairly "light" object. random_device and mt19937 aren't. std::uniform_int_distribution<int> randomColorRange(0, 255); std::random_device rd; std::mt19937 randomNumbers(rd()); // Pre-C++ 11 but more common way (*with issues, see: //https://www.reddit.com/r/programming/comments/1rnudl/quite_interesting_why_cs_rand_is_considered/ // Mostly doesn't apply to game devs if not using rand for say... security. /* srand(time(NULL)); //seed random number generator with the current time auto randomNumber = rand() % 255; //generate a random number then confine it to a value of 0 - 255. */ while (renderWindow.isOpen()){ // Check for all the events that occured since the last frame. while (renderWindow.pollEvent(event)){ //Handle events here if (event.type == sf::Event::EventType::Closed) renderWindow.close(); //Respond to key pressed events if (event.type == sf::Event::EventType::KeyPressed){ if (event.key.code == sf::Keyboard::Space){ color.r = randomColorRange(randomNumbers); } } } // Now demonstrate input via polling if (sf::Keyboard::isKeyPressed(sf::Keyboard::R)) if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl)) color.r = 0; else color.r = randomColorRange(randomNumbers); else if (sf::Keyboard::isKeyPressed(sf::Keyboard::G)) if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl)) color.g = 0; else color.g = randomColorRange(randomNumbers); else if (sf::Keyboard::isKeyPressed(sf::Keyboard::B)) if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl)) color.b = 0; else color.b = randomColorRange(randomNumbers); renderWindow.clear(color); renderWindow.display(); } }
Todo lo que estamos haciendo en este ejemplo es configurar el color claro de RenderWindow en función de la entrada del teclado. Al presionar la barra espaciadora, manejamos esto usando la entrada controlada por eventos y configuramos el canal rojo de nuestro color claro en un valor aleatorio entre 0-255. Un valor de 0 significa que no hay rojo, mientras que 255 es completamente rojo. Luego sondeamos directamente el teclado para ver si se presionan las teclas R/G/B. Si lo son, asignamos ese canal de color a un valor aleatorio. Si también tenemos presionada la tecla de control, en su lugar ponemos ese canal a 0. Por lo tanto, al presionar CTRL+R, CTRL+G, CTRL+B, aparecerá una ventana en negro.
Como puede ver, SFML simplifica el manejo de la entrada del teclado, ya sea mediante un modelo controlado por eventos o mediante sondeo. Sin embargo, una cosa que no proporciona es ningún sentido de la historia. Podemos verificar si se presionó o soltó una tecla durante un cuadro determinado, pero realmente no podemos rastrear el historial, un requisito muy común. Dicho esto, desarrollar su propia solución es muy simple, como se muestra a continuación:
// This example demostrates the main loop #include "SFML/Graphics.hpp" #include <unordered_map> #include <iostream> int main(int argc, char ** argv) { sf::RenderWindow renderWindow(sf::VideoMode(640, 480), "SFML Demo"); sf::Event event; // If true, you will continue to receive keyboard events when a key is held down // If false, it will only fire one event per press until released renderWindow.setKeyRepeatEnabled(false); std::unordered_map<int, bool> keys; std::list<int> changedKeys; while (renderWindow.isOpen()){ changedKeys.clear(); while (renderWindow.pollEvent(event)){ if (event.type == sf::Event::EventType::Closed) renderWindow.close(); if (event.type == sf::Event::EventType::KeyPressed){ if (keys.count(event.key.code) == 0){ keys[event.key.code] = true; changedKeys.push_back(event.key.code); } } if (event.type == sf::Event::EventType::KeyReleased){ if (keys.count(event.key.code) == 1){ keys.erase(event.key.code); changedKeys.push_back(event.key.code); } } } std::cout << "Currently pressed keys: "; // To get the actual value as a string, you need to use Thor or write your own version for (auto& keyValue : keys) std::cout << keyValue.first << " "; std::cout << std::endl; if (!changedKeys.empty()){ std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!Keys changed" << std::endl; } renderWindow.clear(); renderWindow.display(); } }
Este enfoque es bastante típico de SFML. Las herramientas básicas están ahí para ti, pero es posible que tengas que construir una capa encima para que se ajuste a tu juego. Dicho esto, hay una biblioteca llamada Thor que cubre muchos de estos escenarios comunes, esencialmente una biblioteca de utilidades construida sobre SFML que proporciona funcionalidad adicional. Por ejemplo, además de proporcionar el tipo de funcionalidad que acabamos de implementar anteriormente, también hay una utilidad para convertir los valores de enumeración de clave SFML en un formato imprimible. Probablemente cubriremos el uso de al menos partes de Thor en un punto posterior de esta serie, pero por ahora quiero centrarme en el núcleo de SFML.
El video
Programación SFML Tutorial 2D CPP