Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


12. October 2015

 

In our prior tutorial we created a simple application that showed the basics of event handling in SFML.  In this tutorial we are going to expand upon that concept a bit further and show how to read the keyboard, both using events and direct polling.  Although not strictly SFML related (anymore anyways), we are also going to be covering random number generators as part of our demonstration, a very common task in game development.

 

As always, there is an HD video version of this tutorial.

 

Earlier we used the RenderWindow method pollEvent() to check for and respond to the Closed event.  Now let’s look at the process of responding to keyboard events.

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
      }
    }
  }

 

As we can see, responding to a KeyPressed event is nearly identical to a Closed event.  The key difference is, once you have identified that the event is a keyboard event you have additional information available in the event’s key structure.  The value of key.code is a sf::Keyboard::Key enum for each available key.  In addition to the KeyPressed event, there is also a KeyReleased event that is sent when the key is, well, released.  By default KeyPressed will be fired again and again if held down.  If you would prefer to have only a single event fired for a key press you can change this behaviour using the RenderWindow setKeyRepeatEnabled().

 

This is an example of Event driven programming in which your program responds to events as they occurred.  Sometimes however you would rather poll for input then wait for it to occur.  For example you might want to ask “hey computer, what keys are pressed?” or “is the control key down?”.  Fortunately SFML supports this out of the box, like so:

  if (sf::Keyboard::isKeyPressed(sf::Keyboard::R))
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl))
      // Do something

 

This code snippet first checks if the ‘R’ key is currently pressed.  It then checks to see if the Control key is pressed, as you can see in the example above the left and right control keys are separate events.  You can mix and match event driven and polled input handling however you want, but be careful not to get in a situation where you are accidentally handling the same input multiple times.

 

We are going to create a full example in a second that puts all of these processes together by randomly changing the screen color based on keys being pressed.  First, lets cover that process of creating a random number, a very common process in game development.  In fact, random number generation used to be part of SFML, but as of SFML 2.0 it was removed, partly because it didn’t make sense and partly because it’s now well entrenched in the language in a cross platform manner.

 

There are two ways we can generate random numbers, using the older C style, or the more modern C++ 11 style.  The new way is more appropriate for generating truly random numbers for security reasons and has no global state, but the old C style way should be more than sufficient for game development, so pick the way you like best.  The process is very similar… you generate a sequence of random numbers using some form of seed value, then massage the results into the numeric range you want.  Let’s look first at the C way of generating randoms:

  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.

The first call sends the random number generator using the current system time.  Then we call rand() to get a random number.  One important thing to be wary off, the seeding process is fairly slow, while the rand() call is slightly lighter weight.  Next we look at the C++ way:

  std::uniform_int_distribution<int> randomColorRange(0, 255);
  std::random_device rd;
  std::mt19937 randomNumbers(rd());
  auto randomVal = randomColorRange(randomNumbers);

This example does basically the same thing as the earlier version.  The uniform_int_distribution object is a lightweight object that can be used to massage the data generated by mt19937 algorithm into actual int values within the defined range.  The mt19937 is an implementation of Mersenne Twister algorithm for generating random numbers.  The random_device is a built in object for creating a seed for your random number generator instead of using the current time ( although it’s quite possible that random_device uses the time behind the scenes ).  Using random_device is heavy weight, as is mt19937, so be careful where you generate random numbers.  However using those generated numbers with a uniform_int_distribution is light weight.

 

Ok, so we now know how to respond to keyboard events, poll the keyboard directly and generate random numbers, let’s tie it all together in a single example.

// 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();
  }

}

 

All we are doing in this example is setting the clear color of the RenderWindow based on keyboard input.  On pressing the spacebar we handle this using event driven input and set the Red channel of our clear color to a random value between 0-255.  A value of 0 means no red, while 255 is fully red.  We then directly poll the keyboard for input look to see if the R/G/B keys are pressed.  If they are, we assign that color channel to a random value.  If we also have the control key pressed, we instead set that channel to 0.  Therefore pressing CTRL+R, CTRL+G, CTRL+B will result in a black window.

 

As you can see, SFML makes it simple to handle keyboard input, both using an event driven model or by polling.  One thing it does not provide however is any sense of history.  We can check if a key was pressed or released during a given frame but we can’t really track the history a very common requirement.  That said, rolling your own solution is very simple, as shown below:

// 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();
  }

}


This approach is pretty typical of SFML.  The basic tools are there for you, but you may have to build a layer on top to fit your game.  That said, there is a library called Thor that covers many of these common scenarios, essentially a utility library built over top of SFML providing additional functionality.  For example, in addition to providing the kind of functionality we just implemented above, there is also a utility for turning SFML Key enum values into a printable format.  We will probably cover using at least parts of Thor at a later point in this series, but for now I want to focus on core SFML.

 

The Video


Programming , , ,

blog comments powered by Disqus

Month List

Popular Comments