Book Image

Mastering SFML Game Development

By : Raimondas Pupius
Book Image

Mastering SFML Game Development

By: Raimondas Pupius

Overview of this book

SFML is a cross-platform software development library written in C++ with bindings available for many programming languages. It provides a simple interface to the various components of your PC, to ease the development of games and multimedia applications. This book will help you become an expert of SFML by using all of its features to its full potential. It begins by going over some of the foundational code necessary in order to make our RPG project run. By the end of chapter 3, we will have successfully picked up and deployed a fast and efficient particle system that makes the game look much more ‘alive’. Throughout the next couple of chapters, you will be successfully editing the game maps with ease, all thanks to the custom tools we’re going to be building. From this point on, it’s all about making the game look good. After being introduced to the use of shaders and raw OpenGL, you will be guided through implementing dynamic scene lighting, the use of normal and specular maps, and dynamic soft shadows. However, no project is complete without being optimized first. The very last chapter will wrap up our project by making it lightning fast and efficient.
Table of Contents (17 chapters)
Mastering SFML Game Development
Credits
About the Author
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface

Managing application events


Event management is one of the cornerstones that provide us with fluid control experience. Any key presses, window changes, or even custom events created by the GUI system we'll be covering later are going to be processed and handled by this system. In order to effectively unify event information coming from different sources, we first must unify their types by enumerating them correctly:

enum class EventType { 
  KeyDown = sf::Event::KeyPressed, 
  KeyUp = sf::Event::KeyReleased, 
  MButtonDown = sf::Event::MouseButtonPressed, 
  MButtonUp = sf::Event::MouseButtonReleased, 
  MouseWheel = sf::Event::MouseWheelMoved, 
  WindowResized = sf::Event::Resized, 
  GainedFocus = sf::Event::GainedFocus, 
  LostFocus = sf::Event::LostFocus, 
  MouseEntered = sf::Event::MouseEntered, 
  MouseLeft = sf::Event::MouseLeft, 
  Closed = sf::Event::Closed, 
  TextEntered = sf::Event::TextEntered, 
  Keyboard = sf::Event::Count + 1, Mouse, Joystick, 
  GUI_Click, GUI_Release, GUI_Hover, GUI_Leave 
}; 
 
enum class EventInfoType { Normal, GUI }; 

SFML events come first, since they are the only ones following a strict enumeration scheme. They are then followed by the live SFML input types and four GUI events. We also enumerate event information types, which are going to be used inside this structure:

struct EventInfo { 
  EventInfo() : m_type(EventInfoType::Normal), m_code(0) {} 
  EventInfo(int l_event) : m_type(EventInfoType::Normal), 
    m_code(l_event) {} 
  EventInfo(const GUI_Event& l_guiEvent): 
    m_type(EventInfoType::GUI), m_gui(l_guiEvent) {} 
  EventInfo(const EventInfoType& l_type) { 
    if (m_type == EventInfoType::GUI) { DestroyGUIStrings(); } 
    m_type = l_type; 
    if (m_type == EventInfoType::GUI){ CreateGUIStrings("", ""); } 
  } 
 
  EventInfo(const EventInfo& l_rhs) { Move(l_rhs); } 
 
  EventInfo& operator=(const EventInfo& l_rhs) { 
    if (&l_rhs != this) { Move(l_rhs); } 
    return *this; 
  } 
 
  ~EventInfo() { 
    if (m_type == EventInfoType::GUI) { DestroyGUIStrings(); } 
  } 
  union { 
    int m_code; 
    GUI_Event m_gui; 
  }; 
   
  EventInfoType m_type; 
private: 
  void Move(const EventInfo& l_rhs) { 
    if (m_type == EventInfoType::GUI) { DestroyGUIStrings(); } 
    m_type = l_rhs.m_type; 
    if (m_type == EventInfoType::Normal){ m_code = l_rhs.m_code; } 
    else { 
      CreateGUIStrings(l_rhs.m_gui.m_interface, 
        l_rhs.m_gui.m_element); 
      m_gui = l_rhs.m_gui; 
    } 
  } 
 
  void DestroyGUIStrings() { 
    m_gui.m_interface.~basic_string(); 
    m_gui.m_element.~basic_string(); 
  } 
 
  void CreateGUIStrings(const std::string& l_interface, 
    const std::string& l_element) 
  { 
    new (&m_gui.m_interface) std::string(l_interface); 
    new (&m_gui.m_element) std::string(l_element); 
  } 
}; 

Because we care about more than just the event type that took place, there needs to be a good way of storing additional data that comes with it. C++11's unrestricted union is a perfect candidate for that. The only downside is that now we have to worry about manually managing the data inside the union, which comes complete with data allocations and direct invocation of destructors.

As event call-backs are being invoked, it's a good idea to provide them with the actual event information. Because it's possible to construct more complex requirements for specific call-backs, we can't get away with unions this time. Any possible information that may be relevant needs to be stored, and that's precisely what is done here:

struct EventDetails { 
  EventDetails(const std::string& l_bindName): m_name(l_bindName){ 
    Clear(); 
  } 
   
  std::string m_name; 
  sf::Vector2i m_size; 
  sf::Uint32 m_textEntered; 
  sf::Vector2i m_mouse; 
  int m_mouseWheelDelta; 
  int m_keyCode; // Single key code. 
 
  std::string m_guiInterface; 
  std::string m_guiElement; 
  GUI_EventType m_guiEvent; 
 
  void Clear() { ... } 
}; 

This structure is filled with every single bit of information that is available as the events are processed, and then passed as an argument to the call-back that gets invoked. It also provides a Clear() method, because instead of being created only for the time during the call-back, it lives inside the binding structure:

using Events = std::vector<std::pair<EventType, EventInfo>>; 
 
struct Binding { 
  Binding(const std::string& l_name) : m_name(l_name), 
    m_details(l_name), c(0) {} 
  void BindEvent(EventType l_type, EventInfo l_info = EventInfo()) 
  { ... } 
 
  Events m_events; 
  std::string m_name; 
  int c; // Count of events that are "happening". 
 
  EventDetails m_details; 
}; 

A binding is what actually allows events to be grouped together in order to form more complex requirements. Think of it in terms of multiple keys needing to be pressed at once in order to perform an action, such as Ctrl + C for copying a piece of text. A binding for that type of situation would have two events it's waiting for: the Ctrl key and the C key.

Event manager interface

With all of the key pieces being covered, all that's left is actually managing everything properly. Let's start with some type definitions:

using Bindings = std::unordered_map<std::string, 
  std::unique_ptr<Binding>>; 
using CallbackContainer = std::unordered_map<std::string, 
  std::function<void(EventDetails*)>>; 
enum class StateType; 
using Callbacks = std::unordered_map<StateType, 
  CallbackContainer>; 

All bindings are attached to specific names that get loaded from a keys.cfg file when the application is started. It follows a basic format like this:

Window_close 0:0 
Fullscreen_toggle 5:89 
Intro_Continue 5:57 
Mouse_Left 9:0 

Of course these are very basic examples. More complex bindings would have multiple events separated by white spaces.

Call-backs are also stored in an unordered map, as well as tied to the name of a binding that they're watching. The actual call-back containers are then grouped by state, in order to avoid multiple functions/methods getting called when similar keys are pressed. As you can imagine, the event manager is going to be inheriting from a StateDependent class for this very reason:

class EventManager : public StateDependent{ 
public: 
  ... 
  bool AddBinding(std::unique_ptr<Binding> l_binding); 
  bool RemoveBinding(std::string l_name); 
  void ChangeState(const StateType& l_state); 
  void RemoveState(const StateType& l_state); 
  void SetFocus(bool l_focus); 
   
  template<class T> 
  bool AddCallback(const StateType& l_state, 
    const std::string& l_name,  
    void(T::*l_func)(EventDetails*), T* l_instance) 
  { ... } 
   
  template<class T> 
  bool AddCallback(const std::string& l_name, 
    void(T::*l_func)(EventDetails*), T* l_instance) 
  { ... } 
 
  bool RemoveCallback(const StateType& l_state, 
    const std::string& l_name){ ... } 
  void HandleEvent(sf::Event& l_event); 
  void HandleEvent(GUI_Event& l_event); 
  void Update(); 
  sf::Vector2i GetMousePos(sf::RenderWindow* l_wind = nullptr) 
    const { ... } 
private: 
  ... 
  Bindings m_bindings; 
  Callbacks m_callbacks; 
}; 

Once again, this is quite simple. Since this is a state-dependent class, it needs to implement the ChangeState() and RemoveState() methods. It also keeps track of when the window focus is obtained/lost, in order to avoid polling events of minimized/unfocused windows. Two versions of AddCallback are provided: one for a specified state, and one for the current state. Separate HandleEvent() methods are also available for every event type supported. So far, we only have two: SFML events, and GUI events. The latter is going to be used in the upcoming section.