Sample programm in C++ using SDL2 and EnTT libraries.
This sample programm was created as a starting point for developing applications in C++ using SDL2 and EnTT header-only
libraries. Specifically it shows the most essential usage of the Entity Component System
provided by the EnTT library, using small data structs as position, velocity and color components, to
render and move 1000 colorful confettis on the screen. Although Entity and Component are used here, the System
is not implemented as separate functions, but included in the update() and render() functions of the Scene
class.
Most code repositories that I found implementing EnTT as their ECS base, actually had rather complex components, which I found to be overloaded with functionality and not matching the raw data I would have expected to see in a component, while functionality should be implemented by systems, in my humble opinion.
I hope this project might serve as a useful resource to others who are perhaps new to C++ and EnTT, and can not find simple implementation templates as a starting point for own projects.
You can just medidate and watch the confetti fly around, but if you want to be more interactive, use the arrow keys, or A, W, S, D keys to blow some wind in chosen direction and make the confetti change direction. And if it all gets too fast, just press the spacebar to freeze the confetti in position, and then gently press direction keys again, to give it some velocity again. Beware, you could be doing this all day ;)
The Game
class encapsulates all the SDL2 specifics to initialize SDL create a window, create the renderer, handle events and perform the actual rendering on screen.
The Scene
class then actually uses EnTT to create the confetti entities, with their Position, Velocity and Color components. Updates their position in the update()
function, and then renders them on screen in the render()
function.
The Position, Velocity and Color components are defined in the components.hpp
in the include
directory.
This shows the most basic usage of the entt::registry
and how to create entities, adding components to them, and how
to use views
to retreive the data components of interest for modification.
Entities are just unique identifiers of type entt::entity
which are used to group components for a single entity. You
retreive the entity identifier from the entt::registry
as so:
entt::registry m_registry;
const auto entity = m_registry.create();
Components are just pure data structures, that are grouped to specific usage, and then can be added to an entity. To define a component, just create a named struct with the desired elements, and ideally create constructors, that allow initializing your struct with data, i.e.:
// position component
struct Position
{
float x; // x position on screen
float y; // y position on screen
// default constructor
Position() = default;
// initialize component when adding it to entity
Position(float px, float py)
: x(px), y(py) {};
};
You can then add such a component to an existing entity:
m_registry.emplace<Position>(entity, 10.f, 10.f);
If your structs don't have a constructor to initialize their values, you may as well initialize them after adding them
to the entity, as the emplace
call returns the component itself.
auto& pos = m_registry.emplace<Position>(entity);
pos.x = 1.0f;
pos.y = 1.0f;
Components can be assigned to, or removed from entities at any time.
Systems then usually are just functions or classes, that work with the components of one or more entities, where as you usually do your modifications on all components, regardless of the entity. As the components are packed in memory optimized format, processing over a set of components is blazing fast.
There are several ways to access and group components, let me just hightlight the essential way of creating a view of components from all entities, iterate over them and modify them.
Iterating over a single component type...
auto view = m_registry.view<Position>();
for (auto [entity, pos]: view.each()) {
pos.x = 0.f;
pos.y = 0.f;
}
...or iterating over a set of components:
auto view = m_registry.view<Position, Velocity>();
for (auto [entity, pos, vel]: view.each()) {
pos.x += vel.dx;
pos.y += vel.dy;
}
For a more in-depth insight into the entity-component systems functionality from the EnTT header-only library, check out their Crash Course: entity-component system.
EnTT is included as a submodule in this repository, if you clone the repository with the --recursive flag:
git clone --recursive https://github.com/hexerei/entt-confetti.git
If you have already cloned the repository without the recursvie flag, you can still retreive the submodules. Within you project folder, run:
git submodule update --init --recursive
SDL2 can be installed easily with following commands depending on your platform:
- Linux using apt:
apt-get install libsdl2-dev
- Mac OS X using Homebrew:
brew install sdl2
- Windows using MSYS2 and Pacman:
pacman -S mingw-w64-i686-SDL2
This project uses the G++ compiler and GNU Make to build the desired target, but can also be buildt and debugged in Visual Studio Code.
You will find a simple makefile, that allows quick adaption of program name, compiler flags and included libraries. All the other magic hapens in the included common.mk makefile, which auto adds all your sources, provides multi-platform support, and allows building to separate debug and release folders, where debug is the default.
Calling make
without paramaters, or make debug
or make all
will build the debug version for your target
environment into the bin/debug
folder.
Calling make run
will build the debug version and execute the target program file for fast testing. The program file
is executed in the projects root directory, so no need to copy assets into the build folders.
Calling make release
will build the release version without debug information, and optimized for size.
Calling make clean
will remove the program, all object files and other output created from the bin/debug
and the
bin/release
folders.
For your convenience you will find a .vscode
folder in the repository, which includes the tasks.json
to build the
solution from within VSCode and launch.json
to execute or debug the solution using the Run menu.
Note: The target application is named main
in the launch and tasks JSON settings files, in contrary to the target name
confetti
defined in the makefile, which is easier to modify. You can still change this in the JSON files, if desired.
The only libraries used in the current code stack are SDL2 and EnTT. But makefiles include reference to SDL2_image, SDL2_mixer and SDL2_ttf as a good starting point to continue your own journey adding images, sounds and fonts.
EnTT is a header-only, tiny and easy to use library for game programming and much more written in modern C++. It provides an Enitity Component System which is the focus of this project.
SDL2 (Simple DirectMedia Layer) is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D. Latest release can be found at SDL Releases on libsdl-org's GitHub page.
SDL2 Image is a simple library to load images of various formats as SDL surfaces. It can load BMP, GIF, JPEG, LBM, PCX, PNG, PNM (PPM/PGM/PBM), QOI, TGA, XCF, XPM, and simple SVG format images. AVIF, JPEG-XL, TIFF, and WebP images can be loaded, depending on build options.
SDL2 TTF is a wrapper around the FreeType and Harfbuzz libraries, allowing you to use TrueType fonts to render text in SDL applications.
SDL2 Mixer is a simple multi-channel audio mixer. It supports 8 channels of 16 bit stereo audio, plus a single channel of music. It can load FLAC, MP3, Ogg, VOC, and WAV format audio. It can also load MIDI, MOD, and Opus audio, depending on build options.
Code and documentation Copyright (c) 2022 Daniel Vorhauer.
Code released under the MIT license.
Documentation released under CC BY 4.0.