Razón Artificial

La ciencia y el arte de crear videojuegos

SFML – Manejando eventos

Escrito por adrigm el 25 de marzo de 2011 en Desarrollo Videojuegos, Noticias, Programación | 17 Comentarios.

En el anterior tutorial vimos como crear una ventana, pero esta no se podía cerrar. Es esta tutorial aprenderemos a capturar eventos y manejarlos de manera adecuada.

Obtener eventos

Básicamente, hay dos formas de recibir los eventos en un sistema de ventanas:

  • Pedir a la ventana los eventos que han sucedido en cada iteración del bucle lo que se conoce como “sondeo”.
  • Darle a la ventana un puntero a una función que es llamada cuando recibe un evento.

SFML utiliza el sistema de sondeo para recibir eventos. Es decir, se debe pedir a la ventana los eventos en cada ciclo. La función a utilizar es GetEvent() que rellena una instancia de sf::Event y devuelve true si hay algún evento o falso si la pila está vacía.

// Esto va en el Game Loop del tutorial anterior

sf::Event Event;
if (App.GetEvent(Event))
{
    // Procesar evento
}

Pero puedo haber más de un evento en cada interacción del bucle y lo de arriba solo nos procesará el primero de la pila. La forma correcta sería:

sf::Event Event;
while (App.GetEvent(Event))
{
    // Procesar eventos
}

Debes colocar esto en la parte superior del bucle ya que es bueno obtener los eventos de la ventana en cada ciclo antes de hacer otra cosa para actuar en consecuencia.

while (Running)
{
    sf::Event Event;
    while (App.GetEvent(Event))
    {
        // Procesar eventos
    }

    App.Display();
}

Procesar eventos

Lo primero que hay que comprobar cuando llegamos a un evento es su tipo, SFML define los siguientes tipos de eventos, todos dentro de sf::Event:

  • Closed
  • Resized
  • LostFocus
  • GainedFocus
  • TextEntered
  • KeyPressed
  • KeyReleased
  • MouseWheelMoved
  • MouseButtonPressed
  • MouseButtonReleased
  • MouseMoved
  • MouseEntered
  • MouseLeft
  • JoyButtonPressed
  • JoyButtonReleased
  • JoyMoved

Dependiendo del evento la instancia del objeto tendrá diferentes parámetros:

  • Size events (Resized)
    • Event.Size.Width Nuevo ancho de la ventana, en píxeles
    • Event.Size.Height Nuevo alto de la ventana, en píxeles
  • Text events (TextEntered)
    • Event.Text.Unicode contiene la codificación UTF-32 del código del carácter que se ha introducido
  • Key events (KeyPressed, KeyReleased)
    • Event.Key.strong contiene el código de la tecla que se presiona / suelta
    • Event.Key.Alt Si la tecla Alt está o no presionada
    • Event.Key.Control Si la tecla Control está o no presionada
    • Event.Key.Shift Si la tecla Shift está o no presionada
  • Mouse buttons events (MouseButtonPressed, MouseButtonReleased)
    • Event.MouseButton.Button Botones del ratón que se presionan / sueltan
  • Mouse move events (MouseMoved)
    • Event.MouseMove.X Coordenadas X de la posición del ratón, coordenadas locales
    • Event.MouseMove.Y Coordenadas Y de la posición del ratón, coordenadas locales
  • Mouse wheel events (MouseWheelMoved)
    • Event.MouseWheel.Delta Movimiento de la rueda del ratón. Positivo si es hacia adelante y negativo hacia atrás.
  • Joystick buttons events (JoyButtonPressed, JoyButtonReleased)
    • Event.JoyButton.JoystickId Indice del Joystick (puede ser 1 ó 0)
    • Event.JoyButton.Button Índice de los botones del ratón pulsados / soltados , rango [0, 15]
  • Joystick move events (JoyMoved)
    • Event.JoyMove.JoystickId Indice del Joystick (puede ser 1 ó 0)
    • Event.JoyMove.Axis Movimiento del eje
    • Event.JoyMove.Position contiene la posición actual en el eje, en el rango [-100, 100] (excepto Punto de vista que está en [0, 360])

Las teclas del teclado son constantes de SFML definidas en Events.hpp, por ejemplo para la tecla F5 sería sf::Key::F5

Lo mismo para el ratón, tiene definidas cinco constantes: sf::Mouse::Left, sf::Mouse::Right, sf::Mouse::Middle (botón de la rueda), sf::Mouse::XButton1 y sf::Mouse::XButton2

Para el Joystick las constantes son: sf::Joy::AxisX, sf::Joy::AxisY, sf::Joy::AxisZ, sf::Joy::AxisR, sf::Joy::AxisU, sf::Joy::AxisV, y sf::Joy::AxisPOV.

Así que … volvamos a nuestra aplicación, y añadir algo de código para controlar eventos. Vamos a añadir algo para detener la aplicación cuando el usuario cierra la ventana, o cuando se presiona la tecla de escape:

sf::Event Event;
while (App.GetEvent(Event))
{
    // Si se cierra la ventana
    if (Event.Type == sf::Event::Closed)
        Running = false;

    // Si se presiona Escape
    if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape))
        Running = false;
}

Cerrando la ventana

Si has jugado un poco con las ventanas de SFML te habrás dado cuenta que al pulsar cerrar (la cruz superior) se genera un evento de cierre, pero no se cierra la ventana, esto es así para dar la posibilidad al programador de implementar su cierra, por ejemplo que pregunte al jugador si desea guardar la partida o si está seguro.

Podemos usar el método Close() para cerrar la ventana. Hay otro método llamado IsOpened() que devuelve verdadero si la ventana está abierta y falso si está cerrada, con estos dos métodos podemos controlar el bucle de nuestro juego sin variables auxiliares.

while (App.IsOpened())
{
    sf::Event Event;
    while (App.GetEvent(Event))
    {
        // Window closed
        if (Event.Type == sf::Event::Closed)
            App.Close();

        // Escape key pressed
        if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape))
            App.Close();
    }
}

Entrada en tiempo real

Este sistema de eventos es lo suficientemente bueno para reaccionar ante eventos como el cierre de la ventana, o pulsar una tecla. Pero si deseas controlar, por ejemplo, el movimiento continuo de un personaje cuando se presiona una tecla de dirección, a continuación, pronto verás que hay un problema: habrá una demora entre cada movimiento, el retraso se define por el sistema operativo cuando se siguen presionando una tecla.

Para solucionar esto empleamos la clase sf::Input. Esta instancia no puede vivir por si misma, está ligada a una ventana y se debe de definir a que ventana es cuando se crea.

const sf::Input& Input = App.GetInput();

Como vemos es una referencia al método GetInput() de nuestra ventana. Con esto podemos comprobar los valores en tiempo real de la siguiente forma:

bool         LeftKeyDown     = Input.IsKeyDown(sf::Key::Left);
bool         RightButtonDown = Input.IsMouseButtonDown(sf::Mouse::Right);
bool         Joy0Button1Down = Input.IsJoystickButtonDown(0, 1);
unsigned int MouseX          = Input.GetMouseX();
unsigned int MouseY          = Input.GetMouseY();
float        Joystick1X      = Input.GetJoystickAxis(1, sf::Joy::AxisX);
float        Joystick1Y      = Input.GetJoystickAxis(1, sf::Joy::AxisY);
float        Joystick1POV    = Input.GetJoystickAxis(1, sf::Joy::AxisPOV);

Como vemos, un método mucho mejor para controlar la entrada de un videojuego.

17 Comentarios en "SFML – Manejando eventos"

  1. Angel Rubén dice:

    Hola!
    Primero, gracias por esta página y por intentar (al menos) difundir los conocimientos.
    Luego, tengo un problema con el codigo, mas exactamente en “&&” que me genera 4 estupendos errores en el Codelite.

    Un saludo.

  2. Angel Rubén dice:

    Codigo:

    #include

    int main()
    {
    //Cuerpo
    sf::Window App(sf::VideoMode(640, 480, 32), “SFML Window”);
    // Esto crea una ventana a pantalla completa
    //App.Create(sf::VideoMode(800, 600, 32), “SFML Window”, sf::Style::Fullscreen);

    // Bucle principal del juego
    bool Running = true;
    while (Running)
    {
    while (App.IsOpened())
    {
    sf::Event Event;
    while (App.GetEvent(Event))
    {
    // Window closed
    if (Event.Type == sf::Event::Closed)
    App.Close();

    // Escape key pressed
    if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape))
    App.Close();
    }
    }
    // Mostramos la ventana
    App.Display();
    }

    return EXIT_SUCCESS;

    }

    Resultado:
    ———-Build Started——–
    /bin/sh -c ‘”make” -j 2 -f “Tutorial_SFML_wsp.mk”‘
    ———-Building project:[ SegundaVentana – Debug ]———-
    make[1]: se ingresa al directorio «/home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana»
    make[1]: se sale del directorio «/home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana»
    make[1]: se ingresa al directorio «/home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana»
    g++ -c “/home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp” -g -O0 -Wall -o ./Debug/main.o -I. -I.
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp: In function ‘int main()’:
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:24: error: ‘amp’ was not declared in this scope
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:24: error: expected ‘)’ before ‘;’ token
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:24: error: ‘amp’ was not declared in this scope
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:24: error: expected ‘;’ before ‘)’ token
    make[1]: *** [Debug/main.o] Error 1
    make[1]: se sale del directorio «/home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana»
    make: *** [All] Error 2
    ———-Build Ended———-

  3. Angel Rubén dice:

    Bueno, parece que el editor de los comentarios pone el código de distinta manera..pero de todas formas en esta línea:

    if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape))

    el &&, si lo dejo asi me da aun más errores..

    ./Debug/main.o: In function `main':
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:6: undefined reference to `sf::VideoMode::VideoMode(unsigned int, unsigned int, unsigned int)’
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:6: undefined reference to `sf::Window::Window(sf::VideoMode, std::basic_string<char, std::char_traits, std::allocator > const&, unsigned long, sf::WindowSettings const&)’
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:6: undefined reference to `sf::Window::~Window()’
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:21: undefined reference to `sf::Window::Close()’
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:25: undefined reference to `sf::Window::Close()’
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:17: undefined reference to `sf::Window::GetEvent(sf::Event&)’
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:14: undefined reference to `sf::Window::IsOpened() const’
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:29: undefined reference to `sf::Window::Display()’
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:32: undefined reference to `sf::Window::~Window()’
    /home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana/main.cpp:32: undefined reference to `sf::Window::~Window()’
    collect2: ld returned 1 exit status
    make[1]: *** [Debug/SegundaVentana] Error 1
    make[1]: se sale del directorio «/home/ubuntufamiliapc/.codelite/Tutorial_SFML/SegundaVentana»
    make: *** [All] Error 2
    ———-Build Ended———-
    10 errors, 0 warnings

  4. adrigm dice:

    Ya esa es la línea, pero sin ver lo demás no te puedo decir intenta poner el enlace del código en un pastebin o cualquier otra página para poner código y ponme el enlace aquí.

  5. Angel Rubén dice:

    Envío los dos códigos y los resultados de los mismos:

    http://dl.dropbox.com/u/28147341/CodigoYResultados.zip

  6. Angel Rubén dice:

    Bien… De esto es lo que vale los ejemplos y los ejercicios prácticos..para aprender. Ya se cual ha sido mi error..
    Como no es mi entorno de desarrollo que suelo usar y ni siquiera el lenguaje, pues ando bastante perdido..
    Exacto, no estaba enlazado nada de nada, es decir, en la configuracion, en linker, Options lo tenia vacío..
    Ya compila bien y se ejecuta.

    Lo último, en ambos ejemplos de ventana, en mi caso, se dibuja mal, es decir, no la ventana si no mas bien el interior de la ventana que sale como un vídeo cuando se ha “jodio” y salen muchas líneas “malamente”..no se si se comprende..

    Bueno, un saludo y gracias.

    • adrigm dice:

      Es por que no tienes un control de tiempo y se ejecuta a máxima velocidad. Prueba a añadir antes del bucle del juego lo siguiente.

      App.UseVerticalSync(true);

      Esto hace que los FPS se fijen en 60 independientemente de la máquina. Si se sigue viendo mal es que el problema es de OpenGL. Recuerda que SFML corre sobre OpenGL y si no hay aceleración gráfica en la máquina no irá.

      • Angel Rubén dice:

        Buenas. He puesto esa línea pero sigue saliendo todo igual. He mirado que tenga aceleración gráfica con el comando glxinfo y me aparece que si.
        ¿Cual podría ser el problema?

      • Angel Rubén dice:

        Además, he probado el juego de ejemplo (Pong) y se ve igual de mal. Envío enlace de lo que se ve (pantallazos):

        http://dl.dropbox.com/u/28147341/Pantallazo.zip

        • adrigm dice:

          obviamente tienes problemas o con OpenGL o con los drivers gráficos. Prueba a probar otros juegos que usen OpenGL (todos los 3D de linux) a ver si van. Si es así prueba algún juego hecho en SFML que encuentres por la red a ver.

          • Angel Rubén dice:

            Buenas. He probado OpenArena, SuperTuxKar, OpenTTD,…. y todos funcionan bien…Eso sí, muchos juegos SFML no he encontrado (o he sabido si eran..)..

  7. Esteban dice:

    Hola que tal, a mi me dá excepción en tiempo de ejecución al crear la ventana…
    Este es mi codigo:

    [[

    #include
    #include

    int main()
    {
    sf::Window window;
    window.create(sf::VideoMode(800, 600), “Ventana de prueba…”);
    return 0;
    }

    ]]

    Estoy con el netbeans en C++.
    Desde ya muchas gracias.

Deja un comentario