
Entity-Component-System Design Pattern - Implementation and Example Game
- Transfer

In this article I want to discuss the Entity-Component-System ( ECS ) design pattern . You can find a lot of information on this topic on the Internet, so I will not go deep into the explanations, but will talk more about my own implementation.
Part 1: Implementing the Entity-Component-System Template in C ++
Start over. The full source code for my ECS implementation can be found in the github repository .
Entity-Component-System, mainly used in games, is a design pattern that provides tremendous flexibility in designing a common software architecture [1] . Big companies like Unity, Epic or Crytek use this template in their frameworks to provide developers with a very feature-rich tool with which they can develop their own software. You can read a broader discussion on this topic in these posts [2,3,4,5] .
If you study these articles, you will notice that they all have a common goal: the distribution of various problems and tasks between entities (Entities), components (Components) and systems (Systems). These are the three basic concepts of this template, and they are connected quite freely. Entities are usually used to create a unique identifier, to provide the environment with information about the existence of a single element and function as a kind of root object that combines many components. Components- This is nothing more than container objects that do not have any complex logic. Ideally, they are objects with a simple old data objects (POD). Each type of component can be attached to an entity to give it something like a property. Suppose you can attach a “Health-Component” to an entity, which will make it mortal, giving it health , which is a normal integer or fractional value in memory.
Most of the articles that I have come across are consistent with each other regarding the use of entities and components, but opinions about systems vary. Some people believe that only components are known to systems. Moreover, some say that for each type of component there should be its own system, for example, for “Collision-Components” should be “Collision-System”, for “Health-Components” - “Health-System”, etc. This approach is quite strict and does not take into account the interaction of various components. A less rigorous approach is that different systems deal with components that they should be aware of. For example, Physics-Systems should know about Collision-Components and Rigidbody-Components, because both of them most likely contain the information necessary for simulating physics. In my humble opinion,systems are “enclosed environments”. That is, they do not own either entities or components. They have access to them through independent dispatcher objects, which in turn control the life cycle of entities and components.
This poses an interesting question for us: how do entities, components and systems exchange information if they are more or less independent of each other? The answer may be different and depends on the implementation. In my implementation, the answer is event sourcing [6]. Events are distributed through the "Event Manager" ("Event-Manager") and anyone who is interested in events can receive information transmitted by the dispatcher. If an entity, system, or even a component has an important change in state that needs to be reported, for example, “the position has changed” or “the player has died”, they can transmit information to the “event manager”. He will pass the event and all those subscribed to this event will learn about it. Thus, everything can be connected with each other.
It seems that the introduction was longer than I planned, well, what can you do. Before we delve into the study of code , which, incidentally, is written in C ++ 11, I will outline the basic characteristics of my architecture:
- memory efficiency - to quickly create and delete objects of entities, components and systems, as well as events, I cannot rely on standard dynamic memory management. The solution in this case, of course, is its own memory allocator.
- logging - to track what was happening, I used log4cplus [7] for logging .
- scalability - it is possible to conveniently implement new types of entities, components, systems and events without any specified upper limits, with the exception of system memory
- flexibility - there are no dependencies between entities, components and systems (of course, entities and components have a kind of dependence, but they do not contain logic of pointers to each other)
- simple search for objects / access to them - simple obtaining of entity objects and their components through EntityId or an iterator of components for iterating all components of a certain type
- execution control - systems have priorities and can depend on each other, so you can establish a topological order from execution
- ease of use - the library can easily be used with other software, just include is enough.
The image below shows the general architecture of my Entity-Component-System implementation:

Figure 01: ECS Architecture Diagram ( ECS.dll ).
As you can see, there are four areas of different colors in this diagram. Each area represents a modular part of the architecture. At the very bottom - in fact, on the diagram at the very top, it is turned upside down - we have memory management and journaling (yellow area). These first level modules work with very low level tasks. They use second-level modules in the Entity-Component-System (blue area) and event sourcing (red area). These modules deal mainly with object management tasks. On top of them is the third-level module ECS_Engine (green area). This global object of the top-level engine controls all modules of the second level and is engaged in initialization and destruction. Well, this was a brief and very abstract overview, now let's look at the architecture in more detail.
Memory manager
Let's start with the memory manager . Its implementation is based on an article [8] that I found on gamedev.net . The idea is to minimize the allocation and freeing of dynamic memory to an absolute minimum. Therefore, only when starting the application using malloc a large area of memory is allocated. This memory will now be controlled by one or more allocators. There are many types of dispensers [9] (linear, stack, free list) and each of them has its pros and cons (I will not consider them here). But despite the fact that they work differently inside, they all have a common open interface:
class Allocator
{
public:
virtual void* allocate(size_t size) = 0;
virtual void free(void* p) = 0;
};
The above code snippet is incomplete, but there are two main common methods that each distributor should provide:
- allocate - allocates a certain number of bytes and returns the memory address of this area
- free - frees the previously allocated memory area with the specified address.
With that said, we can do interesting things, for example, combine several distributors into chains, like this:

Figure 02: memory controlled by the allocator.
As you can see, one allocator can get its memory area, which it will control, from another (parent) allocator, which, in turn, can get its memory from another allocator, and so on. In this way, various memory management strategies can be built.
To implement my ECS, I created a root stack dispenser that gets the initial allocated area of 1 GB of system memory. Second-level modules allocate the required amount of memory from the root allocator, and release it only after the application is completed.

Figure 03: Possible global memory allocation.
Figure 03 shows how memory can be distributed between the second-level modules: “Global-Memory-User A” can be an entity manager, “Global-Memory-User B” can be a component manager, and “Global-Memory-User C” can be a manager systems.
Logging
I won’t talk much about logging because I just used log4cplus [7] , which does all the work for me. All I did was define the base class Logger , which defines the log4cplus :: Logger object and several wrapper methods that pass simple logging calls like “LogInfo ()”, “LogWarning ()”, etc.
Entity-Manager, IEntity, Entity
Ok, now let's talk about the “meat” of architecture itself: the blue area in Figure 01. You might notice a similar structure for all the dispatch objects and their corresponding classes. For example, take a look at the EntityManager , IEntity, and Entity classes.
class IEntity
{
// код неполон!
EntityId m_Id;
public:
IEntity();
virtual ~IEntity();
virtual const EntityTypeId GetStaticEntityTypeID() const = 0;
inline const EntityId GetEntityID() const { return this->m_Id; }
};
A type identifier is an integer value that varies for each particular entity class. This allows you to check the type of an IEntity object at runtime. Last but not least is the Entity template class
template
class Entity : public IEntity
{
// код неполон!
void operator delete(void*) = delete;
void operator delete[](void*) = delete;
public:
static const EntityTypeId STATIC_ENTITY_TYPE_ID;
Entity() {}
virtual ~Entity() {}
virtual const EntityTypeId GetStaticEntityTypeID() const override { return STATIC_ENTITY_TYPE_ID; }
};
// инициализация константы идентификатора типа сущностей
template
const EntityTypeId Entity::STATIC_ENTITY_TYPE_ID = util::Internal::FamilyTypeID::Get();
The main purpose of this class is to initialize a unique identifier for the type of a particular entity class. Here I took advantage of two facts: first, the initialization of the constant [10] of static variables; secondly, by the nature of the work of template classes. Each version of the Entity Template Class
Let's look at the EntityManager class first. Figure 04 shows the general structure for storing items.

Figure 04: Abstract diagram of the EntityManager class and storage of its objects.
When creating a new entity object, you must use the EntityManager :: CreateEntity < T > method (arguments ...) . This general method first receives a template parameter, which is the type of the specific entity being created. Then, this method takes optional parameters (they may not be), which are passed to the constructor T . The transfer of these parameters is performed through a template with a variable number of arguments [11] . During creation, the following actions occur inside:
- It turns out ObjectPool [12] for entity objects of type T ; if this pool does not exist, a new one is created
- Memory is allocated from this pool; exactly as much as is needed to store the object T
- Before calling the T constructor from the dispatcher, a new EntityId is obtained . This identifier with previously allocated memory will be saved in the lookup table; this way we can search for an entity instance with the desired identifier
- Then the C ++ new operator [13] is called with the arguments passed as input to create a new instance of T
- finally, the method returns the identifier of the entity.
After creating a new instance of the entity object, you can access it using its unique object identifier ( EntityId ) and EntityManager :: GetEntity (EntityId id) . To destroy an instance of an entity object, you must call the EntityManager :: DestroyEntity (EntityId id) method .
The ComponentManager class works in the same way, plus another extension. In addition to pools of objects for storing all kinds of objects, it should provide an additional mechanism for binding components to entity objects that own them. This restriction leads to the second stage of the search: first we check whether the entity with the given EntityId exists, and if there is, then we check whether a certain type of component is attached to this entity by searching the list of its components.

Figure 05: Storage diagram of the Component-Manager object. ComponentManager :: CreateComponent
Method
The SystemManager class does not have any special extras for storing systems and accessing them. A simple memory allocation with a type identifier as a key is used to store the system.
The EventManager class uses a linear allocator that manages the memory area. This memory is used as an event buffer. Events are stored in this buffer and later transmitted. When transmitting an event, the buffer is cleared and new events can be saved in it. This happens at least once per frame.

Figure 06: Revised ECS Architecture Diagram
Hopefully now you have some idea of how my ECS framework works. If not, don’t worry, look at Figure 06 and repeat it briefly. As you can see, EntityId is quite important, because we will use it to access a specific instance of an entity object and all its components. All components know their owner, that is, having a component object, you can easily get an entity by querying the EntityManager class with the specified owner identifier for this component. To pass an entity, we never pass a pointer directly, but use events in conjunction with EntityId . You can create a specific event, say EntityDied , and this event (which should be an object with a simple data structure) has an element of typeEntityId . Now, to notify all event receivers ( IEventListener ), which can be entities, components or systems, we use EventManager :: SendEvent
Engine Object
To make things a little more convenient, I created an engine object. The engine object provides easy integration and use of client software. On the client side, just add the header file “ECS / ECS.h” and call the ECS :: Initialize () method . Now the static global object of the engine will be initialized ( ECS :: ECS_Engine ) and can be used on the client side to gain access to dispatcher classes. Moreover, it provides a SendEvent method
#include
int main(int argc,char* argv[])
{
// инициализация глобального объекта 'ECS_Engine'
ECS::Initialize();
const float DELTA_TIME_STEP = 1.0f / 60.0f; // 60 Гц
bool bQuit = false;
// выполняем основной цикл до выхода
while(bQuit == false)
{
// Обновление всех систем, передача всех событий из буфера,
// удаление разрушенных компонентов и сущностей ...
ECS::ECS_Engine->(DELTA_TIME_STEP);
/*
ECS::ECS_Engine->GetEntityManager()->...;
ECS::ECS_Engine->GetComponentManager()->...;
ECS::ECS_Engine->GetSystemManager()->...;
ECS::ECS_Engine->SendEvent(...);
*/
// другая логика ...
}
// разрушение глобального объекта 'ECS_Engine'
ECS::Terminate();
return 0;
}
Conclusion
The “entity-component-system" architecture described in this part of the article is fully functional and ready for use. But as usual, there are always thoughts for improvement. Here are just a few of the ideas that came to my mind:
- Make the architecture thread safe
- Perform each system or group of systems in threads, taking into account their topological order,
- Refactor event-sourcing and memory management to use them as modules,
- Serialization
- Profiling
- ...
I prepared a demo so you can see my ECS in action:
The BountyHunter demo makes extensive use of ECS and demonstrates the power of this template. In the second part of the post I will talk about him.
Reference materials
[1] https://en.wikipedia.org/wiki/Entity-component-system
[2] http://gameprogrammingpatterns.com/component.html
[3] https://www.gamedev.net/articles/programming / general-and-gameplay-programming / understanding-component-entity-systems-r3013 /
[4] https://github.com/junkdog/artemis-odb/wiki/Introduction-to-Entity-Systems
[5] http: //scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf
[6] https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing
[7] https: // sourceforge. net / p / log4cplus / wiki / Home /
[8] https://www.gamedev.net/articles/programming/general-and-gameplay-programming/c-custom-memory-allocation-r3010/
[9]https://github.com/mtrebi/memory-allocatorshttps://www.gamedev.net/articles/programming/general-and-gameplay-programming/c-custom-memory-allocation-r3010/
[10]http://en.cppreference.com/w/cpp/language/constant_initialization
[11]https://en.wikipedia.org/wiki/Variadic_template
[12]http://gameprogrammingpatterns.com/object-pool.html
[13]http://en.cppreference.com/w/cpp/language/new
Часть 2: Игра BountyHunter
Now I want to show you how to actually use architecture to create games on it. I admit, my game does not look very complicated, but if you decide to use my implementation to create your own game instead of a large and complex game engine such as Unity or Unreal , you can mention my authorship. To demonstrate the capabilities of my ECS, I had enough of such a game. If you do not understand its rules, then the following picture will help you:

Figure 01: Purpose and rules of the game BountyHunter. Goal: each player seeks to replenish his own supplies as quickly as possible. Rules: 1. The game has a time limit. After the time has passed, the game ends. 2. Regarding the prey, the collector picks it up. 3. Pickers may carry a limited amount of loot. If the limit is reached, the collector can no longer collect the prey, and when it touches the prey, it is destroyed. 4. If the collector touches his or enemy supplies, then all the collected prey is dumped into the supplies and the collector can again collect prey. 5. In the collision of the pickers, they are destroyed and after some time they are reborn at the point of their supplies. Collected production is lost. 6. Prey appears at a random point in the central part of the world and exists during its lifetime, or until it is collected. 7.
The image on the left looks familiar because it is a more abstract outline of the game that you saw in the video. The rules are clear enough and speak for themselves. As you can see, we have many types of entities living in the game world. You are probably wondering what they actually consist of? Of the components, of course. Some types of components are common to all entities, while others are unique to others. Take a look at the picture below.

Figure 02: An entity and its components.
By looking at this figure, you can easily see the relationship between entities and their components (not everything is shown in the figure!). All game entities have a Transform-Component . Since game entities must be located somewhere in the world, they have a transform that describes the position, rotation, and scale of the entities. This may be the only component attached to the entity. For example, a camera object requires more components, but it does not need a Material-Component , because it will never be visible to the player (which may not be true if you use post-effects). On the other hand, entity objects Bounty (production) and Collector (collector) must be visible, so they need to displayMaterial-Component . They can also collide with other objects of the game, and therefore a Collision-Component is attached to them , describing their physical form. Another component is attached to the Bounty entity - Lifetime-Component . This component determines the remaining life of the Bounty object , when its life ends, production is destroyed.
So what's next? Having created all these different entities with their own sets of components, we did not complete the game itself. We also need someone who knows how to manage each of them. Of course, I'm talking about systems. Systems are a great thing, they can be used to split the entire game logic into smaller elements. Each element works with its own aspect of the game. We may or may even have an Input-System that processes all the data entered by the player. Render-System , which transfers all shapes and colors to the screen. Respawn-System for the revival of dead objects of the game. Well, I think you get the idea. The figure below shows the complete class diagram of all the specific types of entities, components, and systems in BountyHunter .

Figure 03: ECS BountyHunter class diagram (click on the image to enlarge it).
Now we have entities, components, and a system (ECS), but we are missing ... events! So that systems and entities can exchange data, I created a collection of 38 events:
GameInitializedEvent GameRestartedEvent GameStartedEvent GamePausedEvent GameResumedEvent GameoverEvent GameQuitEvent PauseGameEvent ResumeGameEvent RestartGameEvent QuitGameEvent LeftButtonDownEvent LeftButtonUpEvent LeftButtonPressedEvent RightButtonDownEvent RightButtonUpEvent RightButtonPressedEvent KeyDownEvent KeyUpEvent KeyPressedEvent ToggleFullscreenEvent EnterFullscreenModeEvent StashFull EnterWindowModeEvent GameObjectCreated GameObjectDestroyed PlayerLeft GameObjectSpawned GameObjectKilled CameraCreated, CameraDestroyed ToggleDebugDrawEvent WindowMinimizedEvent WindowRestoredEvent WindowResizedEvent PlayerJoined CollisionBeginEvent CollisionEndEvent
And there is still much that I needed to implement BountyHunter :
- general application framework - SDL2 for receiving player input and setting the main application window.
- graphics - I used my own OpenGL renderer so that I could render to this application window.
- math - for linear algebra I used glm .
- collision recognition - for her I used box2d physics .
- State machine - used for simple AI and game states.
Of course, I will not consider all of these mechanics, because they deserve my own article, which I may write later. But if you still want to know about them, then I will not bother you and give this link . Having studied all the characteristics I mentioned, you can decide that this can be a good start for your own game engine. There are a few more items on my list that I have not actually implemented, simply because I wanted to complete the prototype.
- Editor for managing entities, components, systems and others
- Saving a game - saving entities and their components to a database using some ORM library (for example, codesynthesis )
- Play replays - record events at runtime and play them later
- GUI - using a GUI framework (e.g. librocket ) to create an interactive game menu
- Resource Manager - synchronous and asynchronous loading of resources (textures, fonts, models, etc.) using your own resource manager
- Networking - sending events over the network and setting up multi-user mode
I will leave these points as a task for you so that you can prove how cool you are.
I also need to show you code demonstrating the use of my ECS. Remember the game entity of Bounty ? Prey is small yellow, large red and intermediate squares randomly created in the center of the world. The following is a snippet of the Bounty entity class definition code .
// Bounty.h
class Bounty : public GameObject
{
private:
// кэшируем компоненты
TransformComponent* m_ThisTransform;
RigidbodyComponent* m_ThisRigidbody;
CollisionComponent2D* m_ThisCollision;
MaterialComponent* m_ThisMaterial;
LifetimeComponent* m_ThisLifetime;
// свойство класса bounty
float m_Value;
public:
Bounty(GameObjectId spawnId);
virtual ~Bounty();
virtual void OnEnable() override;
virtual void OnDisable() override;
inline float GetBounty() const { return this->m_Value; }
// вызывает OnEnable, задаёт случайно выбираемое значение добычи
void ShuffleBounty();
};
The code is pretty straightforward. I created a new gaming entity resulting from GameObject < T > (which is derived from the ECS the Entity :: < T >) to the class itself ( Bounty ) as T . ECS is now aware of this particular entity type and a unique identifier for the (static) type will be created. We also get access to convenient methods AddComponent < U > , GetComponent < U > , RemoveComponent < U >. In addition to the components, which I will soon show, there is another property - the value of production. I don’t remember exactly why I did not separate this property into a separate component, for example, in BountyComponent , because that would be correct. Instead, I made the booty property a member of the Bounty class , sprinkling ashes on my head. But actually it only shows the enormous flexibility of this template, right? More precisely, its components ...
// Bounty.cpp
Bounty::Bounty(GameObjectId spawnId)
{
Shape shape = ShapeGenerator::CreateShape();
AddComponent(shape);
AddComponent(BOUNTY_RESPAWNTIME, spawnId, true);
// кэшируем эти компоненты
this->m_ThisTransform = GetComponent();
this->m_ThisMaterial = AddComponent(MaterialGenerator::CreateMaterial());
this->m_ThisRigidbody = AddComponent(0.0f, 0.0f, 0.0f, 0.0f, 0.0001f);
this->m_ThisCollision = AddComponent(shape, this->m_ThisTransform->AsTransform()->GetScale(), CollisionCategory::Bounty_Category, CollisionMask::Bounty_Collision);
this->m_ThisLifetime = AddComponent(BOUNTY_MIN_LIFETIME, BOUNTY_MAX_LIFETIME);
}
// другие реализации ...
I used the constructor to attach all the components needed by the Bounty entities . Note that this approach creates an object procurement and is not flexible, that is, you always get a Bounty object with the same components attached to it. Although this is a good enough solution for my game, for a more complex one it may turn out to be a mistake. In this case, you need to implement the "factory" template, which creates mutable entity objects.
As you can see from the code above, quite a few components are attached to the Bounty entity . We have ShapeComponent and MaterialComponent for visual display. RigidbodyComponent and CollisionComponent2Dfor physical behavior and reaction to collisions. RespawnComponent so that Bounty has the opportunity to be reborn after death. Last, but not least, is the LifetimeComponent , which ties the existence of an entity to a specific period of time. TransformComponent is automatically bound to any entity obtained from GameObject < T > . That's all. We just added a new entity to the game.
Now you might want to learn how to use all of these components. Let me show you two examples. The first is the RigidbodyComponent. This component contains information about physical characteristics, such as friction, density and linear attenuation. Moreover, it is used as an adapter class used to embed box2d physics into the game. RigidbodyComponent is quite important because it is used to synchronize the transform of the physically simulated body (owned by box2d) and the TransformComponent of the entity (owned by the game). This synchronization process is performed by PhysicsSystem .
// PhysicsEngine.h
class PhysicsSystem : public ECS::System, public b2ContactListener
{
public:
PhysicsSystem();
virtual ~PhysicsSystem();
virtual void PreUpdate(float dt) override;
virtual void Update(float dt) override;
virtual void PostUpdate(float dt) override;
// Подключение механизма обработки событий физики box2d для сообщения о коллизиях
virtual void BeginContact(b2Contact* contact) override;
virtual void EndContact(b2Contact* contact) override;
}; // class PhysicsSystem
// PhysicsEngine.cpp
void PhysicsSystem::PreUpdate(float dt)
{
// Синхронизация преобразований физического твёрдого тела и TransformComponent
for (auto RB = ECS::ECS_Engine->GetComponentManager()->begin(); RB != ECS::ECS_Engine->GetComponentManager()->end(); ++RB)
{
if ((RB->m_Box2DBody->IsAwake() == true) && (RB->m_Box2DBody->IsActive() == true))
{
TransformComponent* TFC = ECS::ECS_Engine->GetComponentManager()->GetComponent(RB->GetOwner());
const b2Vec2& pos = RB->m_Box2DBody->GetPosition();
const float rot = RB->m_Box2DBody->GetAngle();
TFC->SetTransform(glm::translate(glm::mat4(1.0f), Position(pos.x, pos.y, 0.0f)) * glm::yawPitchRoll(0.0f, 0.0f, rot) * glm::scale(TFC->AsTransform()->GetScale()));
}
}
}
// другие реализации ...
From the above implementation, you can notice three different update functions. When updating systems, all PreUpdate methods of all systems are called first , then Update , and then PostUpdate methods . Since the PhysicsSystem is called before all TransformComponent related to the system, the code above provides transform synchronization. You can also see ComponentIterator in action here . Instead of asking every entity in the world whether it has a RigidbodyComponent , we ask the ComponentManager to provide us with a ComponentIterator for the type RigidbodyComponent. Having received the RigidbodyComponent , we can easily get the entity identifier and once again ask the ComponentManager to give us the TransformComponent for this identifier.
As I promised, let's look at a second example. RespawnComponent is used for entities that must be reborn after death. This component provides five properties that you can use to customize the behavior of entity regeneration. You can choose the automatic revival of the entity after death, set the time after which it will be regenerated, as well as the position and orientation of the revival. The revival logic itself is implemented in the RespawnSystem .
// RespawnSystem.h
class RespawnSystem : public ECS::System, protected ECS::Event::IEventListener
{
private:
// ... всё остальное
Spawns m_Spawns;
RespawnQueue m_RespawnQueue;
// Механизм обработки событий
void OnGameObjectKilled(const GameObjectKilled* event);
public:
RespawnSystem();
virtual ~RespawnSystem();
virtual void Update(float dt) override;
// другое ...
}; // class RespawnSystem
// RespawnSystem.cpp
// примечание: это только псевдокод!
voidRespawnSystem::OnGameObjectKilled(const GameObjectKilled * event)
{
// проверяем, имеет ли сущность возможность возрождения
RespawnComponent* entityRespawnComponent = ECS::ECS_Engine->GetComponentManager()->GetComponent(event->m_EntityID);
if(entityRespawnComponent == nullptr || (entityRespawnComponent->IsActive() == false) || (entityRespawnComponent->m_AutoRespawn == false))
return;
AddToRespawnQeueue(event->m_EntityID, entityRespawnComponent);
}
void RespawnSystem::Update(float dt)
{
foreach(spawnable in this->m_RespawnQueue)
{
spawnable.m_RemainingDeathTime -= dt;
if(spawnable.m_RemainingDeathTime <= 0.0f)
{
DoSpawn(spawnable);
RemoveFromSpawnQueue(spawnable);
}
}
}
The code above is incomplete, but gives an idea of the important lines of code. The RespawnSystem maintains and updates the EntityId queue along with their RespawnComponent . New entities are queued when systems receive the GameObjectKilled event . The system checks if the killed entity has the ability to regenerate, that is, whether a RespawnComponent is attached to it . If so, then the entity is queued for rebirth, otherwise it is ignored. In the RespawnSystem update method , which is called every frame, the system reduces the initial RespawnComponent respawn timequeued entities. If the spawn time is reduced to zero, then the entity is reborn and removed from the spawn queue.
I know the article was pretty short. but I hope she gave you a rough idea of how everything works in the ECS world. Before finishing the post, I want to share my own experience with you. Working with ECS is a great pleasure. It’s surprisingly simple to add new features to the game even with the help of third-party libraries. I just added new components and systems that connect new features to the game, and I never had the feeling that I was at a standstill. Dividing the entire game logic into several systems is intuitive and does not cause any difficulties when working with ECS. The code looks much more understandable and becomes more convenient to support, because we got rid of all the "spaghetti dependencies" of pointers. Event sourcing is a very powerful and useful technique for exchanging data between systems / entities, but it is a double-edged blade, and may cause problems over time. I am talking about the conditions for triggering events. If you have ever worked with a Unity or Unreal Engine editor, you will be pleased with the implementation of such an editor. These editors greatly increase productivity because it takes much less time to create new ECS objects than to write all these lines of code manually. But when you create a powerful foundation of objects of entities, components, systems and events, then combining them and creating something qualitative on their basis will be a simple task. than manually writing all these lines of code. But when you create a powerful foundation of objects of entities, components, systems and events, then combining them and creating something qualitative on their basis will be a simple task. than manually writing all these lines of code. But when you create a powerful foundation of objects of entities, components, systems and events, then combining them and creating something qualitative on their basis will be a simple task.