Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering SFML Game Development

You're reading from   Mastering SFML Game Development Inject new life and light into your old SFML projects by advancing to the next level.

Arrow left icon
Product type Paperback
Published in Jan 2017
Publisher Packt
ISBN-13 9781786469885
Length 442 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Raimondas Pupius Raimondas Pupius
Author Profile Icon Raimondas Pupius
Raimondas Pupius
Arrow right icon
View More author details
Toc

Table of Contents (11) Chapters Close

Preface 1. Under the Hood - Setting up the Backend FREE CHAPTER 2. Its Game Time! - Designing the Project 3. Make It Rain! - Building a Particle System 4. Have Thy Gear Ready - Building Game Tools 5. Filling the Tool Belt - a few More Gadgets 6. Adding Some Finishing Touches - Using Shaders 7. One Step Forward, One Level Down - OpenGL Basics 8. Let There Be Light - An Introduction to Advanced Lighting 9. The Speed of Dark - Lighting and Shadows 10. A Chapter You Shouldnt Skip - Final Optimizations

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*>; 
You have been reading a chapter from
Mastering SFML Game Development
Published in: Jan 2017
Publisher: Packt
ISBN-13: 9781786469885
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime