The Koala engine is a type-safe and self-documenting implementation of an Entity-Component-System (ECS), with a focus on runtime extensibility and compile-time type safety and clarity.
The latest version is available on the develop branch.
An example of source code is shown at the bottom of this page.
The engine uses Git submodules, and should therefore be cloned recursively with
git clone https://github.com/phisko/kengine --recursive
Alternatively, the entire source code can be found in ZIP form in the latest release.
The engine requires a C++17 compiler.
- Entity: can be used to represent anything (generally an in-game entity). Is simply a container of
Components
- EntityManager: manages
Entities
andComponents
Note that there is no Component
class. Any type can be used as a Component
, and dynamically attached/detached to Entities
.
Similarly, there is no System
class to hold game logic. Systems
are simply Entities
with an Execute
component. This lets users introspect Systems
or add behavior to them (such as profiling) just like they would with any other Entity
.
Many parts of the engine (such as the scripting systems or the OpenGL system) make use of putils
' reflection API. Most of the components in the following samples are thus defined as reflectible.
Any type can be used as a Component
. This essentially means that you can use the engine solely as a generic container, letting you attach anything to anything.
However, the engine comes with a (fairly large) number of pre-built components that can be used to bootstrap a game, or simply as examples that you can base your own implementations upon.
These components fit into three categories:
- Data components
- Function components
- Meta components
These are typical, data-holding components, such as a TransformComponent or a NameComponent. They provide information about an Entity
.
Data components can sometimes hold functions: the InputComponent lets an Entity
hold callbacks to be called whenever an input event occurs. The CollisionComponent lets an Entity
be notified when it collides with another.
These are simply holders for functors that can be attached as Components
to Entities
. This mechanic can be used:
- to attach behaviors to entities (for instance, the Execute function gets called by the main loop each frame)
- to register callbacks for system-wide events (for instance, the OnEntityCreated function gets called whenever a new
Entity
is created) - to provide new functionality that is implemented in a specific system (for instance, the QueryPosition function can only be implemented in a physics system)
Function components are types that inherit from BaseFunction, giving it the function signature as a template parameter.
To call a function component, one can use its operator()
or its call
function.
// have an Entity e
e += functions::Execute{ // Attach a function
[](float deltaTime) { std::cout << "Yay!\n"; }
};
const auto & execute = e.get<functions::Execute>(); // Get the function
execute(0.f); // Call it with its parameters
execute.call(42.f); // Alternatively
These provide a type-specific implementation of a generic function for a given Component
type. They are attached to "type entities", i.e. Entities
used to represent a Component
type. These entities can be obtained by calling the getTypeEntity<T>()
function from TypeHelper.
At their core, meta components are function components: they also inherit from BaseFunction and are used the same way.
As an example, the Has meta component, attached to the type entity for T
, takes an Entity
as parameter and returns whether it has a T
component.
EntityManager em;
auto type = getTypeEntity<TransformComponent>(em); // Get the type entity
type += NameComponent{ "TransformComponent" }; // You'll typically want to provide the type name as information
type += meta::Has{ // Provide the implementation for `Has`
[](const Entity & e) { return e.has<TransformComponent>(); }
};
auto e = em.createEntity([](Entity & e) { // Create an entity with a TransformComponent
e += TransformComponent{};
});
// For each entity with a NameComponent and a Has meta component
for (const auto & [type, name, has] : em.getEntities<NameComponent, meta::Has>())
if (has(e)) // if `e` has the component represented by `type`
std::cout << e.id << " has a " << name.name << '\n';
These are pre-built, extensible and pluggable elements that can be used to bootstrap a project or as inspiration for your own implementations.
- TransformComponent: defines an
Entity
's position, size and rotation - PhysicsComponent: defines an
Entity
's movement - KinematicComponent: marks an
Entity
as kinematic, i.e. "hand-moved" and not managed by physics systems - InputComponent: lets
Entities
receive keyboard and mouse events - SelectedComponent: indicates that an
Entity
has been selected - NameComponent: provides an
Entity
's name
- LuaComponent: defines the lua scripts to be run by the
LuaSystem
for anEntity
- LuaTableComponent: holds a sol::table that lua scripts can use to hold any information related to an
Entity
- PyComponent: defines the Python scripts to be run by the
PySystem
for anEntity
- CollisionComponent: defines a function to be called when an
Entity
collides with another - OnClickComponent: defines a function to be called when an
Entity
is clicked
- AdjustableComponent: lets users modify variables through a GUI (such as the ImGuiAdjustableSystem)
- ImGuiComponent: lets
Entities
render debug elements using ImGui - ImGuiToolComponent: indicates that an
Entity
'sImGuiComponent
is a tool that can be enabled or disabled by the ImGuiToolSystem - DebugGraphicsComponent: lets an
Entity
be used to draw debug information (such as lines, rectangles or spheres)
- GraphicsComponent: specifies the appearance of an
Entity
- ModelComponent: describes a model file (be it a 3D model, a 2D sprite or any other graphical asset)
- CameraComponent: lets
Entities
be used as in-game cameras, to define a frustum - ViewportComponent: specifies the screen area for a "camera entity"
- WindowComponent: lets
Entities
be used as windows
- HighlightComponent: indicates that an
Entity
should be highlighted - LightComponent: lets
Entities
be used as in-game light sources (directional lights, point lights or spot lights) - GodRaysComponent: indicates that a "light entity" should generate volumetric lighting (also known as "Almighty God Rays")
- ShaderComponent: lets
Entities
be used to introduce new OpenGL shaders - PolyVoxComponent: lets
Entities
be used to generate voxel-based models, drawn by the PolyVoxSystem - SkyBoxComponent: lets
Entities
be used to draw a skybox - SpriteComponent: indicates that an
Entity
'sGraphicsComponent
describes a 2D or 3D sprite - TextComponent: indicates that an
Entity
'sGraphicsComponent
describes a 2D or 3D text element
- AnimationComponent: provides skeletal animation information for
Entities
. - SkeletonComponent: provides bone information for an
Entity
's skeletal animation
- ModelColliderComponent: attached to an
Entity
with a ModelComponent. Describes the colliders associated with a given model.
- Execute: called each frame
- OnEntityCreated: called for each new
Entity
- OnEntityRemoved: called whenever an
Entity
is removed - OnTerminate: called during
EntityManager
destruction - GetEntityInPixel: returns the
Entity
seen in a given pixel - GetImGuiScale: returns the scale to apply to ImGui widgets
- OnCollision: called whenever two
Entities
collide - OnMouseCaptured: indicates whether the mouse should be captured by the window
- QueryPosition: returns a list of
Entities
found within a certain distance of a position
In all following descriptions, the "parent" Component
refers to the Component
type represented by the type entity which has the meta component.
- Has: returns whether an
Entity
has the parentComponent
- AttachTo: attaches the parent
Component
to anEntity
- DetachFrom: detaches the parent
Component
from anEntity
- ForEachEntity: iterates on all entities with the parent
Component
- ForEachEntityWithout: iterates on all entities without the parent
Component
- EditImGui: displays the parent
Component
attached to anEntity
in ImGui and lets users edit attributes - DisplayImGui: displays the parent
Component
attached to anEntity
in ImGui with read-only attributes - LoadFromJSON: initializes the parent
Component
attached to anEntity
from a putils::json object - MatchString: returns whether the parent
Component
attached to anEntity
matches a given string
- LuaSystem: executes lua scripts attached to
Entities
- PySystem: executes Python scripts attached to
Entities
- CollisionSystem: forwards collision notifications to
Entities
- OnClickSystem: forwards click notifications to
Entities
- InputSystem: forwards input events buffered by graphics systems to
Entities
- ImGuiAdjustableSystem: displays an ImGui window to edit
AdjustableComponents
- ImGuiEntityEditorSystem: displays ImGui windows to edit
Entities
with aSelectedComponent
- ImGuiEntitySelectorSystem: displays an ImGui window that lets users search for and select
Entities
- ImGuiToolSystem: manages ImGui tool windows through ImGui's MainMenuBar
- OpenGLSystem: displays entities in an OpenGL render window
- OpenGLSpritesSystem: loads sprites and provides shaders to render them
- AssimpSystem: loads 3D models using the assimp library, animates them and provides shaders to render them
- PolyVoxSystem: generates 3D models based on
PolyVoxComponents
and provides shaders to render them - MagicaVoxelSystem: loads 3D models in the MagicaVoxel ".vox" format, which can then be drawn by the
PolyVoxSystem
's shader
This system hasn't been updated in a while and won't currently compile. Let me know if you have an urgent need for it, but you're probably better off writing your own simplistic graphics system
- SfSystem: displays entities in an SFML render window
- BulletSystem: simulates physics using Bullet Physics
- KinematicSystem: moves kinematic
Entities
Some of these systems make use of external libraries which you may not want to depend upon, and are therefore disabled by default. To enable them, set the corresponding CMake variable to true
in your CMakeLists.txt
:
System | Variable |
---|---|
AssimpSystem | KENGINE_ASSIMP |
BulletSystem | KENGINE_BULLET |
MagicaVoxelSystem | KENGINE_POLYVOX |
OpenGLSystem | KENGINE_OPENGL |
PolyVoxSystem | KENGINE_POLYVOX |
SfSystem | KENGINE_SFML |
lua library | KENGINE_LUA |
python library | KENGINE_PYTHON |
These systems make use of Conan for dependency management. The necessary packages will be automatically downloaded when you run CMake, but Conan must be installed separately by running:
pip install conan
These are helper functions to factorize typical manipulations of Components
.
- CameraHelper
- ImGuiHelper: provides helpers to display and edit
Entities
in ImGui - MainLoop
- MatrixHelper
- PluginHelper: provides an
initPlugin
function to be called from DLLs - ShaderHelper
- SkeletonHelper
- SortHelper: provides functions to sort
Entities
- TypeHelper: provides a
getTypeEntity<T>
function to get a "singleton" entity representing a given type
- RegisterComponentFunctions: provides implementations for the Has, AttachTo and DetachFrom meta components
- RegisterComponentEntityIterators: provides implementations for the ForEachEntity and ForEachEntityWithout meta components
- RegisterComponentJSONLoader: provides an implementation for the LoadFromJSON
- RegisterComponentEditor: provides implementations for the EditImGui and DisplayImGui meta components
- RegisterComponentMatcher: provides an implementation for the MatchString meta component
Below is a commented main function that creates an entity and attaches some components to it, as well as a lua script. This should let you get an idea of what is possible using the kengine's support for reflection and runtime extensibility, as well as the compile-time clarity and type-safety that were the two motivations behind the project.
#include <iostream>
#include "go_to_bin_dir.hpp"
#include "EntityManager.hpp"
#include "Entity.hpp"
#include "systems/LuaSystem.hpp"
#include "data/GraphicsComponent.hpp"
#include "data/TransformComponent.hpp"
#include "functions/Execute.hpp"
#include "helpers/MainLoop.hpp"
// Simple system that outputs the transform and lua components of each entity that has them
//- Forward declaration
static float execute(kengine::EntityManager & em, float deltaTime);
//-
auto DebugSystem(kengine::EntityManager & em) {
return [&](kengine::Entity & e) {
// Attach an Execute component that will be called each frame
e += kengine::functions::Execute{
[&](float deltaTime) { execute(em, deltaTime); }
};
};
}
// This could be defined as a lambda in DebugSystem but is moved out here for readability
static float execute(kengine::EntityManager & em, float deltaTime) {
for (const auto & [e, transform, lua] : em.getEntities<kengine::TransformComponent, kengine::LuaComponent>()) {
std::cout << "Entity " << e.id << '\n';
std::cout << "\tTransform: "
<< transform.boundingBox.position.x << ' '
<< transform.boundingBox.position.y << ' '
<< transform.boundingBox.position.z << '\n';
std::cout << "\tScripts:" << '\n';
for (const auto & script : lua.scripts)
std::cout << "\t\t[" << script << "]\n";
std::cout << '\n';
}
}
int main(int, char **av) {
// Go to the executable's directory to be next to resources and scripts
putils::goToBinDir(av[0]);
// Create an EntityManager
kengine::EntityManager em; // Optionally, pass a number of threads as parameter -> kengine::EntityManager em(4);
em += DebugSystem(em);
em += kengine::LuaSystem(em);
// Create an Entity and attach Components to it
em += [](kengine::Entity e) {
e += kengine::TransformComponent({ 42.f, 0.f, 42.f }); // Parameter is a Point3f for position
e += kengine::LuaComponent({ "scripts/unit.lua" }); // Parameter is a vector of scripts
};
// Register types to be used in lua
kengine::lua::registerTypes<
kengine::TransformComponent, putils::Point3f, putils::Rect3f,
kengine::LuaComponent
>();
// Start game
kengine::MainLoop::run(em);
return 0;
}
-- Simply modify component
local transform = self:getTransformComponent()
local pos = transform.boundingBox.position
pos.x = pos.x + 1