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

Entity component system core


Let's get to the essence of how our game entities are going to be represented. In order to achieve highest maintainability and code compartmentalization, it's best to use composition. The entity component system allows just that. For the sake of keeping this short and sweet, we're not going to be delving too deep into the implementation. This is simply a quick overview for the sake of being familiar with the code that will be used down the line.

The ECS pattern consists of three cornerstones that make it possible: entities, components, and systems. An entity, ideally, is simply an identifier, as basic as an integer. Components are containers of data that have next to no logic inside them. There would be multiple types of components, such as position, movable, drawable, and so on, that don't really mean much by themselves, but when composed, will form complex entities. Such composition would make it incredibly easy to save the state of any entity at any given time.

There are many ways to implement components. One of them is simply having a base component class, and inheriting from it:

class C_Base{ 
public: 
  C_Base(const Component& l_type): m_type(l_type){} 
  virtual ~C_Base(){} 
 
  Component GetType() const { return m_type; } 
 
  friend std::stringstream& operator >>( 
    std::stringstream& l_stream, C_Base& b) 
    { 
      b.ReadIn(l_stream); 
      return l_stream; 
    } 
 
  virtual void ReadIn(std::stringstream& l_stream) = 0; 
protected: 
  Component m_type; 
}; 

The Component type is simply an enum class that lists different types of components we can have in a project. In addition to that, this base class also offers a means of filling in component data from a string stream, in order to load them more easily when files are being read.

In order to properly manage sets of components that belong to entities, we would need some sort of manager class:

class EntityManager{ 
public: 
  EntityManager(SystemManager* l_sysMgr, 
    TextureManager* l_textureMgr); 
  ~EntityManager(); 
 
  int AddEntity(const Bitmask& l_mask); 
  int AddEntity(const std::string& l_entityFile); 
  bool RemoveEntity(const EntityId& l_id); 
 
  bool AddComponent(const EntityId& l_entity, 
    const Component& l_component); 
 
  template<class T> 
  void AddComponentType(const Component& l_id) { ... } 
 
  template<class T> 
  T* GetComponent(const EntityId& l_entity, 
    const Component& l_component){ ... } 
 
  bool RemoveComponent(const EntityId& l_entity, 
    const Component& l_component); 
  bool HasComponent(const EntityId& l_entity, 
    const Component& l_component) const; 
  void Purge(); 
private: 
  ... 
}; 

As you can see, this is a fairly basic approach at managing these sets of data we call entities. The EntityId data type is simply a type definition for an unsigned integer. Creation of components happens by utilizing a factory pattern, lambdas and templates. This class is also responsible for loading entities from files that may look a little like this:

Name Player 
Attributes 255 
|Component|ID|Individual attributes| 
Component 0 0 0 1 
Component 1 Player 
Component 2 0 
Component 3 128.0 1024.0 1024.0 1 
Component 4 
Component 5 20.0 20.0 0.0 0.0 2 
Component 6 footstep:1,4 
Component 7 

The Attributes field is a bit mask, the value of which is used to figure out which component types an entity has. The actual component data is stored in this file as well, and later loaded through the ReadIn method of our component base class.

The last piece of the puzzle in ECS design is systems. This is where all of the logic happens. Just like components, there can be many types of systems responsible for collisions, rendering, movement, and so on. Each system must inherit from the system's base class and implement all of the pure virtual methods:

class S_Base : public Observer{ 
public: 
  S_Base(const System& l_id, SystemManager* l_systemMgr); 
  virtual ~S_Base(); 
 
  bool AddEntity(const EntityId& l_entity); 
  bool HasEntity(const EntityId& l_entity) const; 
  bool RemoveEntity(const EntityId& l_entity); 
 
  System GetId() const; 
 
  bool FitsRequirements(const Bitmask& l_bits) const; 
  void Purge(); 
 
  virtual void Update(float l_dT) = 0; 
  virtual void HandleEvent(const EntityId& l_entity, 
    const EntityEvent& l_event) = 0; 
protected: 
  ... 
}; 

Systems have signatures of components they use, as well as a list of entities that meet the requirements of said signatures. When an entity is being modified by the addition or removal of a component, every system runs a check on it in order to add it to or remove it from itself. Note the inheritance from the Observer class. This is another pattern that aids in communication between entities and systems.

An Observer class by itself is simply an interface with one purely virtual method that must be implemented by all derivatives:

class Observer{ 
public: 
  virtual ~Observer(){} 
  virtual void Notify(const Message& l_message) = 0; 
}; 

It utilizes messages that get sent to all observers of a specific target. How the derivative of this class reacts to the message is completely dependent on what it is.

Systems, which come in all shapes and sizes, need to be managed just as entities do. For that, we have another manager class:

class SystemManager{ 
public: 
  ... 
  template<class T> 
  void AddSystem(const System& l_system) { ... } 
 
  template<class T> 
  T* GetSystem(const System& l_system){ ... } 
  void AddEvent(const EntityId& l_entity, const EventID& l_event); 
 
  void Update(float l_dT); 
  void HandleEvents(); 
  void Draw(Window* l_wind, unsigned int l_elevation); 
 
  void EntityModified(const EntityId& l_entity, 
    const Bitmask& l_bits); 
  void RemoveEntity(const EntityId& l_entity); 
   
  void PurgeEntities(); 
  void PurgeSystems(); 
private: 
  ... 
  MessageHandler m_messages; 
}; 

This too utilizes the factory pattern, in that types of different classes are registered by using templates and lambdas, so that they can be constructed later, simply by using a System data type, which is an enum class. Starting to see the pattern?

The system manager owns a data member of type MessageHandler. This is another part of the observer pattern. Let us take a look at what it does:

class MessageHandler{ 
public: 
  bool Subscribe(const EntityMessage& l_type, 
    Observer* l_observer){ ... } 
  bool Unsubscribe(const EntityMessage& l_type, 
    Observer* l_observer){ ... } 
  void Dispatch(const Message& l_msg){ ... } 
private: 
  Subscribtions m_communicators; 
}; 

Message handlers are simply collections of Communicator objects, as shown here:

using Subscribtions = 
  std::unordered_map<EntityMessage,Communicator>; 

Each possible type of EntityMessage, which is just another enum class, is tied to a communicator that is responsible for sending out a message to all of its observers. Observers can subscribe to or unsubscribe from a specific message type. If they are subscribed to said type, they will receive the message when the Dispatch method is invoked.

The Communicator class itself is fairly simple:

class Communicator{ 
public: 
  virtual ~Communicator(){ m_observers.clear(); } 
  bool AddObserver(Observer* l_observer){ ... } 
  bool RemoveObserver(Observer* l_observer){ ... } 
  bool HasObserver(const Observer* l_observer) const { ... } 
  void Broadcast(const Message& l_msg){ ... } 
private: 
  ObserverContainer m_observers; 
}; 

As you can gather, it supports the addition and removal of observers, and offers a way to broadcast a message to all of them. The actual container of observers is simply a vector of pointers:

// Not memory-owning pointers. 
using ObserverContainer = std::vector<Observer*>;