kengine icon indicating copy to clipboard operation
kengine copied to clipboard

Game engine with an Entity-Component-System (ECS) architecture. Focus on ease-of-use, runtime extensibility and compile-time type safety.

Kengine

tests

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.

koala

Table of contents

  • Members
    • Classes
    • API
  • Reflection
  • Components
    • Data components
    • Function components
    • Meta components
  • Samples
    • Components
      • Data components
        • General purpose gamedev
        • Behaviors
        • Debug tools
        • Graphics
        • 3D Graphics
        • Skeletal animation
        • Physics
      • Function components
      • Meta components
    • Systems
      • Behaviors
      • Debug tools
      • 3D Graphics
      • 2D Graphics
      • Physics
    • Helpers
      • Meta component helpers
  • Example
  • Scripts

Example

An example of source code is shown at the bottom of this page.

Installation

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 has been tested on Windows with MSVC and MinGW. MinGW is a bit finnicky, as some build configurations simply don't work. This is mostly due to external dependencies pulled from conan, which could be built from source in order to fix the errors. At the time of writing I don't have a personal need for it, so I haven't gone out of my way to do so.

Linux compilation works with GCC. At the time of writing, clang doesn't support C++ 20's constexpr std::string and std::vector.

C++ version

The engine requires a C++20 compiler.

Members

Classes

  • Entity: can be used to represent anything (generally an in-game entity). Is simply a container of Components
  • Entities: lets users create, remove, access and iterate over entities.

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.

API

init

void init(size_t threads = 0) noexcept;

Initializes the engine and creates a ThreadPool with the given number of worker threads.

entities

Entities entities;

Global Entities object that allows for creation, removal, access and iteration over entities.

threadPool

putils::ThreadPool & threadPool() noexcept;

Returns the engine's ThreadPool to allow for controlled asynchronous tasks.

isRunning, stopRunning

bool isRunning() noexcept;
void stopRunning() noexcept;

Lets code query and control whether the engine should keep running or not.

terminate

void terminate() noexcept;

Cleans up the engine. All OnTerminate function Components will be called, the ThreadPool's workers will be joined and the internal state will be deleted.

This function should typically only be called right before exiting main.

getState

void * getState() noexcept;

Returns a pointer to the engine's internal state. This state is required when initializing a plugin (as it is not shared across DLL boundaries).

initPlugin

void initPlugin(void * state) noexcept;

Initializes the current plugin with the provided internal state pointer.

cleanupArchetypes

void cleanupArchetypes() noexcept;

Removes any unused Archetypes to speed up future iteration over Entities. This is called at the end of each frame by the mainLoop helper.

Reflection

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.

Components

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

Data 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.

Function components

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!" << std::endl; }
};
const auto & execute = e.get<functions::Execute>(); // Get the function
execute(0.f); // Call it with its parameters
execute.call(42.f); // Alternatively

Meta components

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.

auto type = getTypeEntity<TransformComponent>(); // 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 = entities.create([](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] : entities.with<NameComponent, meta::Has>())
    if (has(e)) // if `e` has the component represented by `type`
        std::cout << e.id << " has a " << name.name << std::endl;

Samples

These are pre-built, extensible and pluggable elements that can be used to bootstrap a project or as inspiration for your own implementations.

Components

Data components

General purpose gamedev
  • 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
  • TimeModulatorComponent: lets an Entity affect the passing of time
  • CommandLineComponent: holds the command-line arguments
Behaviors
  • LuaComponent: defines the lua scripts to be run by the LuaSystem for an Entity
  • LuaTableComponent: holds a sol::table that lua scripts can use to hold any information related to an Entity
  • PythonComponent: defines the Python scripts to be run by the PythonSystem for an Entity
  • CollisionComponent: defines a function to be called when an Entity collides with another
Debug tools
  • AdjustableComponent: lets users modify variables through a GUI (such as the ImGuiAdjustableSystem)
  • ImGuiToolComponent: indicates that an Entity's ImGuiComponent is a tool that can be enabled or disabled by the ImGuiToolSystem
  • ImGuiScaleComponent: custom scale to be applied to all ImGui elements
  • DebugGraphicsComponent: lets an Entity be used to draw debug information (such as lines, rectangles or spheres)
Graphics
  • 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)
  • InstanceComponent: specifies an Entity's model
  • 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
3D Graphics
  • 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's GraphicsComponent describes a 2D or 3D sprite
  • TextComponent: indicates that an Entity's GraphicsComponent describes a 2D or 3D text element
Skeletal animation
  • AnimationComponent: provides skeletal animation information for Entities.
  • AnimationFilesComponent: provides a list of animation files to load for a model Entity
  • SkeletonComponent: provides bone information for an Entity's skeletal animation
Physics
  • ModelColliderComponent: attached to an Entity with a ModelComponent. Describes the colliders associated with a given model.

Function components

  • Execute: called each frame
  • Log: logs messages
  • OnClick: called when the parent Entity is clicked
  • OnEntityCreated: called for each new Entity
  • OnEntityRemoved: called whenever an Entity is removed
  • OnTerminate: called when terminating the engine
  • GetEntityInPixel: returns the Entity seen in a given pixel
  • GetPositionInPixel: returns the position seen in a given pixel
  • 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
  • AppearsInViewport: returns whether or not the Entity should appear in a given viewport

Meta components

In all following descriptions, the "parent" Component refers to the Component type represented by the type entity which has the meta component.

  • AttachTo: attaches the parent Component to an Entity
  • Attributes: recursively lists the parent Component's attributes
  • Copy: copies the parent Component from one Entity to another
  • Count: counts the number of Entities with the parent Component
  • DetachFrom: detaches the parent Component from an Entity
  • DisplayImGui: displays the parent Component attached to an Entity in ImGui with read-only attributes
  • EditImGui: displays the parent Component attached to an Entity in ImGui and lets users edit attributes
  • ForEachEntity: iterates on all entities with the parent Component
  • ForEachEntityWithout: iterates on all entities without the parent Component
  • Get: returns a pointer to the parent Component attached to an Entity
  • Has: returns whether an Entity has the parent Component
  • LoadFromJSON: initializes the parent Component attached to an Entity from a nlohmann::json object
  • MatchString: returns whether the parent Component attached to an Entity matches a given string
  • SaveToJSON: serializes the parent Component into a JSON object
  • Size: contains the size of the parent Component

Systems

Behaviors

  • LuaSystem: executes lua scripts attached to Entities
  • PythonSystem: 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

Debug tools

  • ImGuiAdjustableSystem: displays an ImGui window to edit AdjustableComponents
  • ImGuiEngineStats: displays an ImGui window with engine stats
  • ImGuiEntityEditorSystem: displays ImGui windows to edit Entities with a SelectedComponent
  • ImGuiEntitySelectorSystem: displays an ImGui window that lets users search for and select Entities
  • ImGuiPromptSystem: displays an ImGui window that lets users run arbitrary code in Lua and Python
  • ImGuiToolSystem: manages ImGui tool windows through ImGui's MainMenuBar

Logging

  • LogFileSystem: outputs logs to a file
  • LogImGuiSystem: outputs logs to an ImGui window
  • LogStdoutSystem: outputs logs to stdout
  • LogVisualStudioSystem: outputs logs to Visual Studio's output window

2D Graphics

  • SFMLSystem: displays entities in an SFML render window

    ⬆ ⚠ Doesn't compile on MinGW in Release ⚠ ⬆

3D Graphics

  • 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

    ⬆ ⚠ Doesn't compile on MinGW in Debug ⚠ ⬆

  • 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

  • GLFWSystem: creates GLFW windows and handles their input

Physics

  • BulletSystem: simulates physics using Bullet Physics
  • KinematicSystem: moves kinematic Entities

General

  • RecastSystem: generates navmeshes and performs pathfinding
  • ModelCreatorSystem: handles model Entities

These systems must be enabled by setting the corresponding CMake variable to true in your CMakeLists.txt. Alternatively, you can set KENGINE_ALL_SYSTEMS to build them all.

System Variable
AssimpSystem KENGINE_ASSIMP
BulletSystem KENGINE_BULLET
CollisionSytem KENGINE_COLLISION
ImGuiAdjustableSystem KENGINE_IMGUI_ADJUSTABLE
ImGuiEngineStatsSystem KENGINE_IMGUI_ENGINE_STATS
ImGuiEntityEditorSystem KENGINE_IMGUI_ENTITY_EDITOR
ImGuiEntitySelectorSystem KENGINE_IMGUI_ENTITY_SELECTOR
ImGuiPromptSystem KENGINE_IMGUI_PROMPT
ImGuiToolSystem KENGINE_IMGUI_TOOL
InputSystem KENGINE_INPUT
KinematicSystem KENGINE_KINEMATIC
LuaSystem KENGINE_LUA
LogImGuiSystem KENGINE_LOG_IMGUI
LogStdoutSystem KENGINE_LOG_STDOUT
LogVisualStudioSystem KENGINE_LOG_VISUAL_STUDIO
OnClickSystem KENGINE_ONCLICK
OpenGLSystem KENGINE_OPENGL
OpenGLSpritesSystem KENGINE_OPENGL_SPRITES
PolyVoxSystem KENGINE_POLYVOX
MagicaVoxelSystem KENGINE_POLYVOX
ModelCreatorSystem KENGINE_MODEL_CREATOR
PythonSystem KENGINE_PYTHON
RecastSystem KENGINE_RECAST
SFMLSystem KENGINE_SFML

It is possible to test for the existence of these systems during compilation thanks to C++ define macros. These have the same name as the CMake variables, e.g.:

#ifdef KENGINE_LUA
// The LuaSystem exists, and we can safely use the lua library
#endif

Some of 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

Helpers

These are helper functions to factorize typical manipulations of Components.

  • assertHelper: higher-level assertions
  • cameraHelper
  • imguiHelper: provides helpers to display and edit Entities in ImGui
  • imguiLuaHelper
  • instanceHelper
  • jsonHelper: provides helpers to serialize and de-serialize Entities from json
  • lightHelper
  • logHelper
  • luaHelper
  • mainLoop
  • matrixHelper
  • pluginHelper: provides an initPlugin function to be called from DLLs
  • pythonHelper
  • registerTypeHelper
  • resourceHelper
  • scriptLanguageHelper: helpers to easily implement new scripting languages
  • skeletonHelper
  • sortHelper: provides functions to sort Entities
  • typeHelper: provides a getTypeEntity<T> function to get a "singleton" entity representing a given type
Meta component helpers

These provide helper functions to register standard implementations for the respective meta Components.

  • registerAttachTo
  • registerCount
  • registerDetachFrom
  • registerDisplayImGui
  • registerEditImGui
  • registerForEachAttribute
  • registerForEachEntity
  • registerHas
  • registerLoadFromJSON
  • registerMatchString
  • registerSaveToJSON

Example

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.

main.cpp

#include <iostream>

#include "go_to_bin_dir.hpp"

#include "kengine.hpp"

#include "systems/lua/LuaSystem.hpp"

#include "data/GraphicsComponent.hpp"
#include "data/TransformComponent.hpp"
#include "functions/Execute.hpp"

#include "helpers/mainLoop.hpp"
#include "helpers/luaHelper.hpp"

// Simple system that outputs the transform and lua components of each entity that has them
//- Forward declaration
static float execute(float deltaTime);
//-
auto DebugSystem() {
    return [&](kengine::Entity & e) {
        // Attach an Execute component that will be called each frame
        e += kengine::functions::Execute{ execute };
    };
}

// This could be defined as a lambda in DebugSystem but is moved out here for readability
static float execute(float deltaTime) {
    for (const auto & [e, transform, lua] : kengine::entities.with<kengine::TransformComponent, kengine::LuaComponent>()) {
        std::cout << "Entity " << e.id << std::endl;
        std::cout << "\tTransform: "
            << transform.boundingBox.position.x << ' '
            << transform.boundingBox.position.y << ' '
            << transform.boundingBox.position.z << std::endl;

        std::cout << "\tScripts:" << std::endl;
        for (const auto & script : lua.scripts)
            std::cout << "\t\t[" << script << "]" << std::endl;

        std::cout << std::endl;
    }
}

int main(int, char **av) {
    // Go to the executable's directory to be next to resources and scripts
    putils::goToBinDir(av[0]);

    kengine::init(); // Optionally, pass a number of threads as parameter

    kengine::entities += DebugSystem();
    kengine::entities += kengine::LuaSystem();

    // Create an Entity and attach Components to it
    kengine::entities += [](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::luaHelper::registerTypes<
        kengine::TransformComponent, putils::Point3f, putils::Rect3f,
        kengine::LuaComponent
    >();

    // Start game
    kengine::MainLoop::run();

    return 0;
}

scripts/unit.lua

-- Simply modify component

local transform = self:getTransformComponent()
local pos = transform.boundingBox.position
pos.x = pos.x + 1

Scripts

A generateTypeRegistration Python script is provided, which can be used to generate C++ files containing functions that will register a set of given types with the engine.

This is absolutely not mandatory.