Introduction

Warning: The engine is still in its early stages of development, and many features are still being worked on.

If you came here is because you are curious about the engine, right? So let's start with the basics.

What is Project-E?

Project-E aims to:

  • Provide a simple and easy-to-use API for development
  • Facilitate the creation of games by providing a set of pre-made modules
  • Fast enough to run games with a decent amount of entities
  • Be cross-platform (Windows, Linux)
  • Data Focused

Project-E is a game engine written in C++ for educational purposes during the R-Type project at EPITECH. It is designed to be modular, easy to use, and easy to understand. The engine is based on the Entity-Component-System (ECS) architecture, which allows for a high level of modularity and flexibility.

Lexicon

this page lists all relevant terminologies regarding the project.

ECS

TermDefinition
ECSEntity-Component-System, design pattern used to manage modular objects
EntityAn object within the application. Can be a player, enemy, ui element etc...
ComponentData that can be attached to an entity to give it specific properties.
SystemAn operation that perform actions on an entity that has a specific set of components.
BlobA ressource that can be used by multiple entities (i.e. meshes for collision shapes)

Networking

TermDefinition
NetworkingThe process of transmitting data over the a network connection
ProtocolA set of rules that define how data is transmitted
TCPTransmission Control Protocol, slower but reliable
UDPUser Datagram Protocol, faster but unreliable
LatencyThe time it takes for a packet to travel to its destination

Game Engine

TermDefinition
ModuleA collection of components, systems, blobs, and premade entities
SceneA collection of entities that are loaded into the game

Getting Started

This section will guide you through the process of setting up the project and building it.

Prerequisites

1. Setup the project

git init

2. Add Project-E as a submodule

git submodule add https://mathematisse/Project-E engine

3. Add the engine to your Project

basic CMakeLists.txt:

cmake_minimum_required(VERSION 3.15)
project(MyProject)

set(CMAKE_CXX_STANDARD 20)

set(RENDER_ENABLED ON)
set(PHYSICS_ENABLED ON)
set(ECS_ENABLED OFF)
set(NETWORK_ENABLED ON)

# Compile the project

add_subdirectory(engine)
target_link_libraries(MyProject PRIVATE core spatial2d render network)

ECS

All logic in Project-E is based on the Entity-Component-System (ECS) architecture. This design pattern is used to manage modular objects in the engine. The ECS architecture is composed of three main components: Entities, Components, and Systems.

For example, an entity can be a player, an enemy, a UI element, etc. A component is data that can be attached to an entity to give it specific properties. A system is an operation that performs actions on an entity that has a specific set of components.

To make use of the ECS architecture, it is encouraged to use break down the logic of your game into small, reusable components. This will allow you to create complex behaviors by combining simple components.

Project-E's Implementation of ECS

Component

A component is a simple data structure that holds data relevant to a specific aspect of an entity. For example, a Position component might hold the x and y coordinates of an entity.

DECLARE_COMPONENT(
    Position,
    float, // x
    float  // y
);

System

class ApplyVelocitySystem : public S::AMonoSystem<C::Position::Pool, C::Velocity::Pool>,
                            public S::ADeltaTimeSystem {
public:
    ~ApplyVelocitySystem() override = default;

protected:
    void _innerOperate(
        typename C::Position::Pool::Types &cposition, typename C::Velocity::Pool::Types &cvelocity
    ) override
    {
        auto [x, y] = cposition;
        auto [vX, vY] = cvelocity;
        x += vX * _deltaTime;
        y += vY * _deltaTime;
    }
};

Here, ApplyVelocitySystem is a system that operates on entities with both Position and Velocity components.

_innerOperate is a method that is called for each entity that has the required components. The method receives the data of the components as arguments.

S::AMonoSystem is a base class that defines the system as a system that operates on entities with a single component. S::ADeltaTimeSystem is a base class that provides the system with the delta time of the frame.

Each Component has a Pool that stores the data of all entities that have that component. The Pool is a collection of Types that represent the data of each entity.

for more information on ECS inner workings, you can refer to the ECS documentation

Basic Simulation Loop


DECLARE_COMPONENT(
    Position,
    float, // x
    float  // y
);

DECLARE_COMPONENT(
    Velocity,
    float, // x
    float  // y
);

using SomeEntity = Aspect<C::Position, C::Velocity>;

auto main() -> int
{
    ECS::EntityManager em;
    engine::module::Core core(em);
    ApplyVelocitySystem applyVelocitySystem;

    ECS::E::SomeEntity ePool(64);
    ECS::S::SystemTreeNode rootNode(
        "Root", {&applyVelocitySystem}
    );
    if (em.registerFixedSystemNode(rootNode) == false) {
        return 1;
    }

    auto start = std::chrono::high_resolution_clock::now();

    while (true) {
        auto end = std::chrono::high_resolution_clock::now();
        auto deltaTime = std::chrono::duration<float>(end - start).count();
        start = end;

        em.addTime(deltaTime);
    }
    em.deleteEverything();
}

Component

System

Modules

Overview

Modules are extensions of the ECS that implement features of the game engine. They provide a collection of defined Components and Systems related to commonly used concepts in game development to speed the process of making games.

Using the build system, you may selectively enable modules to be built along-side your game.

Sections

How to add a module to the engine

1. Create a new module

Create a new directory in the modules directory. The name of the directory should be the name of the module.

2. Create a CMakeLists.txt file

create a in modules/<module-name> directory. The content of the file should be similar to the following:

cmake_minimum_required(VERSION 3.15)
project(<module-name>) # Swap <module-name> with the name of your module

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(spatial2d STATIC Systems.cpp) # Change the source file to the source file of the module
target_link_libraries(spatial2d lib_ecs) # link the needed libraries (ex: raylib, etc...)

Don't forget to change <module-name> to the name of the module and the source files.

3. Make the module available in the engine

Edit the CMakeLists.txt file in engine/modules/ directory. Add the following lines to the top of the file:

add_module(<module-name>)

4. Notes

  • The module doesn't need to specify which other modules it depends on. This is the user's responsibility.
  • The module should be documented using Doxygen comments.
  • The module should have its own markdown file documenting it. The file should be listed to the registry using a similar format with a hyperlink to the file.

Creating a module

Before creating a module, make sure you have read the how to add a module guide.

Once you have created a new module, you can start adding systems and components to it.

Base Module class

Each module should have a base class that inherits from engine::module::IModule. This class should have a constructor that takes an ECS::EntityManager as an argument. The constructor should register all the systems and components of the module.

namespace engine::module {

class PrintHello : public IModule {
    ECS::S::PrintHelloSystem printhelloSystem;

    ECS::S::PrintHelloNode PrintHelloNode;
public:
    PrintHello():
        PrintHelloNode("PrintHello", {&printhelloSystem})
    {
    }

    void load(ECS::EntityManager &entityManager) override
    {
        LOG_INFO("Loading PrintHello module");
        if (!entityManager.registerFixedSystemNode(PrintHelloNode, FIXED_ROOT_SYS_GROUP)) {
            LOG_ERROR("Failed to register PrintHello system node");
        }
    }
};

}

Registering the module

To make the module available in the game, you need to add it mannually.


#include "modules/PrintHello/PrintHello.hpp"


auto main() -> int
{
    ECS::EntityManager em;
    engine::module::PrintHello printHello(em);

    printHello.load(em);
}

Module Registry

  • Render: Sprites drawing & sprite sheet iteration for animated sprites.
  • Spatial 2D: 2D spatial representation with basic movement logic.
  • Network: TCP/UDP networking tools for Server/client netcode infrastructure.

Spatial 2D

The Spatial2D module provides a 2D spatial representation with basic movement logic. It is used to manage the position, rotation, and scale of 2D objects in the game world.

Components

  • Position2D (x: float, y: float)
  • Size2D (width: float, height: float)
  • Rotation (angle: float)
  • Velocity2D (x: float, y: float)
  • Acceleration2D (x: float, y: float)

Systems

  • ApplyVelocity (Position2D, Velocity2D)
  • ApplyAcceleration (Velocity2D, Acceleration2D)

Aspects

  • StaticEntity (Position2D, Rotation)
  • DynamicEntity (+StaticEntity, Velocity2D, Acceleration2D)

Render

The Render module is responsible for drawing sprites and sprite sheets to the screen. It also provides tools for animating sprites.

Components

  • Sprite (textureId: size_t)
  • AnimatedSprite (textureId: size_t, nbFrame: uint8_t, animationSpeed: float)

Systems

  • DebugDrawSprite (Position2D, Color, Size2D)
  • DrawSprite (Position2D, Size2D, Rotation, Sprite)
  • DrawAnimatedSprite (Position2D, Size2D, Rotation, AnimatedSprite)
  • SpriteAnimation (AnimatedSprite, Timer)

Aspects

  • SpriteEntity (Sprite, Size)
  • AnimatedSpriteEntity (AnimatedSprite, Timer, Size)

Network Module

The network module provides a set of functions to interact with the network interfaces of the device.

Networking

How the build system works

Introduction

todo...

Dependencies

The build system is based on CMake, a cross-platform build system generator. CMake is used to generate the build files for the project, which can be used to build the project on different platforms and with different compilers.

On linux:

Ubuntu:

sudo apt install build-essential git
sudo apt install libasound2-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev libglu1-mesa-dev libxcursor-dev libxinerama-dev libwayland-dev libxkbcommon-dev

Fedora:

sudo dnf groupinstall "Development Tools" "Development Libraries"
sudo dnf install alsa-lib-devel mesa-libGL-devel libX11-devel libXrandr-devel libXi-devel libXcursor-devel libXinerama-devel libatomic

Arch Linux:

sudo pacman -S base-devel git
sudo pacman -S alsa-lib mesa libx11 libxrandr libxi libxcursor libxinerama

How to build on Linux

Using CMake:

Release build:

Configure the project:

cmake -B cmake-build-release -S . -DCMAKE_BUILD_TYPE=Release

Build the project:

cmake --build cmake-build-release --parallel --config Release

Debug build:

Configure the project:

cmake -B cmake-build-debug -S . -DCMAKE_BUILD_TYPE=Debug

Build the project:

cmake --build cmake-build-debug --parallel --config Debug

If you are using WSL2, you might have the following error:

c++ fatal error killed signal terminated program cc1plus

You can fix this by removing the --parallel flag from the build command. (you can enable it again after the first build)


You can also use the provided scripts to build the project:

./scripts/build.sh

How to build on Windows

Using CMake:

Release build:

Configure the project:

cmake -B cmake-build-release -S . -DCMAKE_BUILD_TYPE=Release

Build the project:

cmake --build cmake-build-release --config Release

Debug build:

Configure the project:

cmake -B cmake-build-debug -S . -DCMAKE_BUILD_TYPE=Debug

Build the project:

cmake --build cmake-build-debug --config Debug

You can also use the provided scripts to build the project:

.\scripts\build.ps1

How to run

On linux:

The engine's libraries are located in the cmake-build-release/engine directory. And are directly linked to the binary. So you need to set the LD_LIBRARY_PATH environment variable to the cmake-build-release/engine directory.

Your binary will be located in the cmake-build-release or cmake-build-debug directory depending on the build type you chose. The binary is standalone and can be run from anywhere.

./cmake-build-release/<project>/<binary-name>

On Windows:

On Windows, the engine's libraries are located in the build/engine directory. And are directly linked to the binary. So you need to set the PATH environment variable to the build/engine directory.

Your binary will be located in the build.

The binary is standalone and can be run from anywhere.

.\build\<project>\<binary-name>.exe

or double-click on the executable, found in the root of the project.