This library aims to provide a reasonable basic level of Entity, Component, and Systems (ECS) functionality.
ECS refers to an architecture where specific Systems operate on a collection of Entities, filtered to match only the Component types the System cares about.
In a high-level sense, this transforms a potentially narly-looking inheritance structure down into a Database-like structure.
In this library, entities and components are stored in a collection referred to as an ecsWorld, allowing run-time manipulation and access.
Requires C++17 and CMake.
Compiles on GCC 8/9, Clang 7/8/9, MSVC 14/19 (VS 2017/2019).
Across this library and other of my "Mini" libraries, the namespace "mini" must be used.
This library exposes the following structures to the user:
- ecsComponent
- ecsEntity
- ecsHandle
- ecsSystem
- ecsWorld
An ecsComponent is what users should sub-class when creating their own components.
Using a database analogy, these would be presented as columns.
- Components can contain ONLY data
- Components cannot contain ANY functions
Example:
struct TransformComponent final : public ecsComponent<TransformComponent> {
mat4 transformMatrix;
quat orientation
vec3 position;
vec3 scale;
vec3 rotation;
};
An ecsEntity represents a single entity, but requires no further subclassing or modification from the user under an ECS architecture.
All behaviour is expressed in systems responding to the presence or absence of component combinations within an entity.
Using a database analogy, these would be presented as rows.
- An entity can contain no components
- An entity cannot contain two components of the same class type
Example:
An entity can be made with a set of components representing a player, such as:
- Player component
- Character component
- Health component
- Armor component In a game, this entity could be modified and can add useful gameplay components like:
- Invulnerable component or
- Poisoned component
An ecsHandle is a structure used to uniquely identify entities or components using a 32 character UUID.
The ecsWorld takes care to provide sensible values on behalf othe user when generating entities and components.
However, this class on its own should never have to be used by a user.
Instead, when a entity or component is created, its EntityHandle or ComponentHandle will be returned - subclasses of ecsHandle.
Example:
// Make an entity
EntityHandle entHandle = world.makeEntity(...);
// Delete an entity
bool removedSuccesfully = world.removeEntity(entHandle);
An ecsSystem provides the logic appropriate to entity-component combinations.
These are set to filter certain combinations of components per-entity, and can also optionally accept components.
In a database analogy, these would be represented as queries.
Example:
class GravitySystem final : public ecsBaseSystem {
public:
GravitySystem() {
addComponentType(
ParticleComponent::Runtime_ID, RequirementsFlag::FLAG_REQUIRED);
addComponentType(
MassComponent::Runtime_ID, RequirementsFlag::FLAG_REQUIRED);
}
// Interface Implementation
// Receives a vector of required components per-entity, nested in another vector
void updateComponents(
const double& deltaTime,
const std::vector<std::vector<ecsBaseComponent*>>& entityComponents)
final {
for (auto& components : entityComponents) {
auto& particleComponent =
*static_cast<ParticleComponent*>(components[0]);
auto& massComponent = *static_cast<MassComponent*>(components[1]);
const float force = massComponent.mass * -9.81F;
particleComponent.particle.m_velocity.y() +=
force * static_cast<float>(deltaTime);
particleComponent.particle.m_pos +=
particleComponent.particle.m_velocity *
vec2(static_cast<float>(deltaTime));
}
}
};
An ecsWorld stores, organizes, and provides means of accessing and manipulating entities and components.
In addition, it facilitates creation of entities and components. It encapsulates the state of a the game-world.
It also provides means for systems to interact with the data within.
In a database analogy, this would be a DBMS (database management system).
Example:
int main()
{
ecsWorld world;
// Create Empty Entity
EntityHandle entity = world.makeEntity(nullptr, 0);
// Create components
PositionComponent pos{ vec3(0.0F) };
ScaleComponent scl{ vec3(1.0F) };
QuatComponent quat{ quat(0.0F) };
ModelComponent mdl{ Model("//Models//Penguin.mdl") };
world.makeComponent(entity, &pos);
world.makeComponent(entity, &scl);
ComponentHandle quatHandle = world.makeComponent(entity, &quat);
ComponentHandle modelHandle = world.makeComponent(entity, &mdl);
// Delete component using handles
world.removeEntityComponent(entity, modelHandle);
// Delete component using 1 handle (slow)
world.removeComponent(quatHandle);
// Delete component using type
world.removeEntityComponent(entity, ScaleComponent::Runtime_ID);
// Delete entity and remaining components
world.removeEntity(entity);
PhysicsSystem system;
while (shouldRun())
{
world.updateSystem(&system, 0.01);
}
}
};