Events Tutorial
thor::SfmlEventSystem
which is treated in this tutorial has been replaced by the far more powerful thor::ActionMap
. This tutorial is outdated, consider the Actions tutorial instead.
Welcome to the tutorial about the Events module. Here, you will learn how to build up an object-oriented event system. From SFML, you know the procedural way of event handling, namely polling:
while (window.PollEvent(event)) { // switch on event type }
However, this approach has a few drawbacks. You have to differentiate the types explicitly, and your code reacting to the events is gathered in one place. The Events module of the Thor Library offers you an alternative, where the event generation is separated from the reactions to events. Events act as triggers that are connected with function calls. Let’s call the functions associated to the different event types “listeners” (in principle the same as “callbacks” or “slots”).
Event-listener connections
Using the class thor::SfmlEventSystem
, you can associate different event types with different functions. Let’s start with an example of a function that is called upon key presses and outputs the ASCII character of the pressed key:
void WriteKey(sf::Event event) { std::cout << "Text entered: "; std::cout << static_cast<char>(event.Text.Unicode) << std::endl; }
Now the part that actually registers this function and couples it with the TextEntered
event type:
thor::SfmlEventSystem system(window); // window is a sf::Window (or derived)
system.Connect(sf::Event::TextEntered, &WriteKey);
Simple, isn’t it? But nevertheless, the mechanism is quite powerful. The second parameter can not only be a pointer to function like in the above example, rather any callable is allowed. The only limitations are the return type (which must be void
) and the signature (a single parameter of sf::Event
or compatible, like const sf::Event&
). That is, you can also pass function objects, especially instances of std::tr1::function
. Besides, you are able to connect any number of listeners to one event type (even the same listener twice, which leads to two calls). Have a look at a function that closes a window:
void CloseWindow(sf::Window& window)
{
window.Close();
}
Here, we can’t register CloseWindow()
directly at the event system, because the parameter list doesn’t match (sf::Window&
vs. sf::Event
). But we can build a function object:
struct WindowCloser { explicit WindowCloser(sf::Window& window) : window(window) { } void operator() (sf::Event) { window.Close(); } sf::Window& window; };
Then the registration looks like this:
system.Connect(sf::Event::Closed, WindowCloser(window));
Binders [advanced]
This is a chapter that requires rather advanced C++ knowledge, so depending on your skills, you might want to skip it. You can use the whole functionality of the event systems in Thor without binders, however they can simplify things.
You may think, the functor WindowCloser
needs quite a lot code to achieve a single-line functionality. You are right. Fortunately, C++ offers other ways, namely binders. The library Boost.Bind (which has made it into TR1) contains a bunch of tools to compose new functions on-the-fly. If you are unfamiliar to binders, you should have a look at the Boost documentation – you don’t need Boost though, the TR1 is enough. First, we write two lines to simplify the usage of the TR1 namespaces (be sure to avoid that in header files):
using namespace std::tr1::placeholders; namespace tr1 = std::tr1;Using the function
CloseWindow()
from above, the registration looks like the following. You need tr1::ref
to pass the window as reference.
system.Connect(sf::Event::Closed, tr1::bind(&CloseWindow, tr1::ref(window)));
We got rid of the class and all the constructor/member/initialization stuff. But honestly, that annoying function does nothing more than forwarding a call to a member. Since binders support member functions, why not use them to call sf::Window::Close()
directly?
system.Connect(sf::Event::Closed, tr1::bind(&sf::Window::Close, &window));
Now all the functionality is contained in a single line, and we are relieved from writing boilerplate code. A more complex use case involves moving a sf::Shape instance to the position where the mouse is clicked. This time we are using placeholders to forward arguments, and pointers instead of references. First, the function:
void ReadMousePosition(sf::Event event, sf::Shape* shape) { float x = static_cast<float>(event.MouseButton.X); float y = static_cast<float>(event.MouseButton.Y); shape->SetPosition(x, y); }And on the other side, we register the function:
sf::Shape shape = …; system.Connect(sf::Event::MouseButtonPressed, tr1::bind(&ReadMousePosition, _1, &shape));
Embedding the event system to the game loop
Up to now, you have only heard half the story. You have seen how to connect events with listeners, but you don’t know how these events are generated. If we have a simple game loop:
while (window.IsOpened())
{
window.Display();
}
The integration of the event system is very straightforward:
while (window.IsOpened())
{
system.PollEvents();
window.Display();
}
Removing connections
You now know how to add event-listener connections, but not how to remove them from the system as soon as they aren’t needed anymore. The member function Connect()
returns an object of type thor::Connection
which exists exactly for this purpose. You can store it and call its method Disconnect()
later to disconnect the listener from the event.
thor::Connection connection = system.Connect(sf::Event::KeyPressed, &OutputKey); connection.Disconnect();
A connection automatically becomes invalid when the referenced listener is disconnected, so you can’t disconnect a listener accidentally more than once. To disconnect all listeners associated with a given event type, call SfmlEventSystem::ClearConnections()
.
Event forwarding [advanced]
There are situations where you don’t want a single event system to take control over all your input handling. For example, you cannot call sf::Window::PollEvent()
anymore, when a thor::SfmlEventSystem
is taking the window’s events. That’s why SfmlEventSystem
provides a mechanism to inform other objects about the occurred events. A typical use case is some sort of GUI hierarchy, where every component has a method OnEvent()
. Let’s use a global function as a placeholder:
void OnEvent(sf::Event event) { // Do something with event }
To forward every event to this function, you have call ForwardEvents()
:
system.ForwardEvents(&OnEvent);The behavior is very similar to
Connect()
: The parameter is a std::tr1::function<void(const Event&)>
and the return type is a thor::Connection
. Use ClearEventForwarders()
to disconnect all forwarding functions at once.