Case Study - Simple Game Engine (SGE)
Published June 27, 2025
Written by
Dmytro Kharchenko
Introduction
A game engine is a tool, a set of abstractions, that gives the developer structure and foundation for creating games. In a similar way, web frameworks and libraries, give developers tools for creating websites and web applications.
More specifically, game engines can provide 2D and 3D graphics abstractions, tools for working with sound, physics engine, collision logic, memory management, and, in some cases network management tools.
To create a good game, developer has to solve a massive array of problems: graphic design of assets, characters, animations, scenes, sound design, level design, story, and, of course, programming.
The purpose of SGE is to help simplify the process of game development by providing a set of ideas and tools developers can use to program 2D games. Thus, because programming will be simpler, developers will have the ability to focus more time and resources on other aspects of game development.
SGE also has a cool logo:

The shape on the left side has 5 edges, which symbolize 5 core logical sections of the game engine.
Structure & Tech
SGE is written in C++ using SFML library as foundation.
C++ is famous for its performance, efficiency and flexibility. This programming language builds on top of C, which means it inherits most features of C, yet is a bit more efficient and productive to use for general purpose programming thanks to its extra abstractions.
SFML library conveniently gives access to multimedia functionality like graphics, audio, input, and network. Having this access, has allowed to focus most on game engine structure and ideas, while still having the ablity to use core features of C++, with its benefits, to build abstractions, and, as a result, giving developers same freedom and flexibility.
The whole structure of the engine can be seen on the following image:

It may seem like a lot to unpack, but it's actually very simple.
Everything is an Entity.
That is the core idea of this game engine. Everything that can ever be present on the screen is built by developer as an Entity object.
An Entity can have different properties and calculations added to it based on the specific needs of an object described. This functionality is added to Entities through Components.
A Component in SGE is a distinct class that describes a specific piece of functionality. For example Sprite component describes object's texture, CollisionShape component describes the dimensions of the shape that is used for collision calculations between objects, MotionUnit is responsible for object's movement calculations and position updates, etc.
This kind of separation of concerns simplifies understanding and thus makes programming more straightforward.
Initially Entity is just a class with null pointers:
...
class Entity : public sge::StatefulComponent{
public:
sge::Sprite* sprite = nullptr;
sge::MotionUnit* motionUnit = nullptr;
std::unordered_map<std::string, sge::CollisionShape*> collisionShapes;
sge::ClickableShape* clickableShape = nullptr;
sge::SpriteText* spriteText = nullptr;
sge::AnimationCluster* animationCluster = nullptr;
sge::StateCluster* stateCluster = nullptr;
void activateEntityParts();
void pauseEntityParts();
void hideEntityParts();
private:
// Hidden because extended using 'activateEntityParts', 'pauseEntityParts' and 'hideEntityParts'
using sge::StatefulComponent::activate;
using sge::StatefulComponent::pause;
using sge::StatefulComponent::hide;
//
};
...
When developer for example aims to build a player object, he would create a custom Entity class and, for instance, describe its animation sequence, collision shapes, and possible states (e.g. for walking and jumping). For instance:
...
// ComplexMobileEntity adds predefined physics calculations
// See project code for more details
class GravityEntity : public sge::ComplexMobileEntity{
public :
GravityEntity(sf::Texture* texture, sf::IntRect textureRect, sf::Vector2f position, std::vector<std::string> collisionGroups)
: sge::ComplexMobileEntity(texture, textureRect, position, collisionGroups){
motionUnit->fieldForces["gravity"] = sf::Vector2f(0, 1000);
}
};
class PlayerEntity : public GravityEntity{
public:
PlayerEntity(sf::Texture* texture, sf::IntRect textureRect, sf::Vector2f position, std::vector<std::string> collisionGroups, sge::TextureSheet* animationTextureSheet)
: GravityEntity(texture, textureRect, position, collisionGroups){
animationCluster = new sge::AnimationCluster(sprite);
animationCluster->animationDelayMilliseconds = 80;
animationCluster->addTextureSequence("idle", new sge::TextureSequence(std::vector<int>{260}, animationTextureSheet));
animationCluster->addTextureSequence("in_air", new sge::TextureSequence(std::vector<int>{265}, animationTextureSheet));
animationCluster->addTextureSequence("walking_right", new sge::TextureSequence(std::vector<int>{262, 263, 264}, animationTextureSheet));
animationCluster->addTextureSequence("walking_left", new sge::TextureSequence(std::vector<int>{262, 263, 264}, animationTextureSheet, true));
animationCluster->setCurrentTextureSequence("idle");
collisionShapes["global_bounds"]->setSize(sf::Vector2f(16, 8));
collisionShapes["global_bounds"]->offset = sf::Vector2f(0, 8);
stateCluster = new sge::StateCluster();
stateCluster->states["on_ground"] = new PlayerOnGroundState(this);
stateCluster->states["jump"] = new PlayerJumpState(this);
stateCluster->states["moving_right"] = new PlayerMovingRightState(this);
stateCluster->states["moving_left"] = new PlayerMovingLeftState(this);
stateCluster->activateState("jump"); // jump because player is initially falling
}
};
...
The ground tile, on which the player object would stand, would be described differently. It could just be a StaticEntity, which is 2 layers of abstraction above the Entity. For example:
...
class PlainEntity : public sge::Entity{
public:
PlainEntity(sf::Texture* texture, sf::IntRect textureRect, sf::Vector2f position){
sprite = new sge::Sprite(*texture, textureRect);
sprite->setPosition(position);
}
};
...
class StaticEntity : public sge::PlainEntity{
public:
StaticEntity(sf::Texture* texture, sf::IntRect textureRect, sf::Vector2f position, std::vector<std::string> collisionGroups)
: sge::PlainEntity(texture, textureRect, position){
collisionShapes["global_bounds"] = new sge::CollisionShape(this);
collisionShapes["global_bounds"]->collisionGroups = collisionGroups;
}
};
...
In this way, we have built a player Entity and a tile Entity with different sets of properties and calculations. Player must have the abliity to move, for that MotionUnit and StateCluster is used, but tile must stay static, thus only Sprite and CollisionShapes are added to it.
Most Components in SGE have a corresponding Manager that is responsible for them. This creates the structure that I call Component-Manager (CM) structure. It came about as a result of refactoring during development, and it basically means that there exists the Component, that describes a particular engine feature, and there exists a Manager that manages this component, for example registers, draws and updates it and its properties.
Managers can be illustrated like this:

SGE, like most game development engines do, constantly runs the main loop. In this loop Managers perform their functions in a specific order:

Engine is logically divided into 5 parts:
Controller: manages input devices.
Assets: takes care of game assets like fonts, textures and sounds.
View: allows the creation of camera views and sections.
Logic: main elements and logic of the engine.
Debug: abstractions that aid game debugging.
At the core of it all is the Universe, the main engine manager. In the Universe all logic layers are put together and executed in order, in a loop.
Now that you understand basic structure, lets get into the features.
Features & Usage
The whole game engine is packed into the SGE.hpp
file. The most basic usage example would look like this:
#include <SFML/Graphics.hpp>
#include "SGE.hpp"
int main(){
sf:RenderWindow* window = new sf::RenderWindow(sf::VideoMode(1000, 600), "Test");
sge::Universe* universe = new sge::Universe(window);
// Optional debug functionality initialization
universe->setupDebugEntityManager();
// Your game code
universe->loop();
return 0;
}
We create a simple SFML window, initializing the Universe object, setting up debug functionality and starting the main engine loop.
Ensure you have SFML
library installed on your system.
Lets explore a couple of the most interesting things this game engine lets you create.
Smooth camera movement
Beauty is in the details.
To improve the look and feel of the game, developers often write code to "smooth out" camera movement, when camera follows the player. This smoothing can be seen clearly in this example:

Can you notice how the camera lags behind the character movement and smoothly catching up to its position? Its a subtle effect, but it makes the resulting game feel much more polished.
In SGE this effect can be achieved through ScriptedView and ScriptedViewManager:
#include "../../SGE.hpp"
class CameraView : public sge::ScriptedView{
public:
CameraView(sge::Entity* playerEntity) : m_playerEntityPtr(playerEntity){
this->setCenter(sf::Vector2f(100, 100));
this->setSize(sf::Vector2f(500, 300));
};
void script(){
sf::Vector2f center = m_playerEntityPtr->sprite->getPosition();
center.x += 4;
center.y += 4;
m_scroll.x = center.x - this->getCenter().x - m_scroll.x / 100;
m_scroll.y = center.y - this->getCenter().y - m_scroll.y / 100;
this->setCenter(center - m_scroll);
}
private:
sf::Vector2f m_scroll = sf::Vector2f(0, 0);
sge::Entity* m_playerEntityPtr;
};
Then, creating and registering the camera view:
...
CameraView* cameraView = new CameraView(playerEntity);
universe->scriptedViewManager->registerComponent(cameraView);
...
After the Universe loop begins, the script above will be executed on every iteration, and camera will be centered relatively to the player entity with smoothing calculation applied to it.
Collision management
Platformer games, at the core, are about some character exploring the world, collecting things, discovering secrets, and in some cases fighting enemies. One of the problems programmers have to solve to setup game worlds for players to explore is Collision management.
A Collision is the situation that happens when two (or more) object's textures intersect with each other. When objects collide in this way, different types of responses can be setup to create different effects. For example an effect of character standing on the platform, or one object pushing another in some direction.
An effect of character standing on a platform is achieved through calculating collisions of character object and platform objects and running a function that stops character movement when it collides with the platform.
Visually it would look like this:

"Standing", "Walking", and "Stopping" are not the only interactions that can be created.
For example:

In above illustrations you can see objects and their collision shapes highlighted with a border. You can observe the character and the box which are "standing" firmly on the platforms and character has the ability to push the box. In SGE these interactions are written based on the concept of Collision Phase.
Collision Phase is a way to programmatically reason about two object's past, present, and future actions based on their interactions with each other through time. Internally SGE holds arrays of all collisions to perform calculations on them. If we have arrays of past collisions and present collisions (updated each loop iteration), we can determine in the phase in which the collision is currently. In set theory the illustration would look like this:

When the phase of collision is known, we can run developer defined functions based on the phase that the collision is in. For example, as in the example above, when objects touch (start & then continuous phase), the box border becomes red, character movement slows down, and box movement is performed, when objects stop touching (end phase), the border becomes green, and the box object stops. This is written using Collision Phases.
In similar way other object interactions can be written. Lets explore one of them.
Physics simulation
Similarly to the examples above, you can write interactions and responses to Collisions that resemble different physics phenomena, like gravity or friction force.
For example:

This illustration shows the interaction between character and the box, as well as Debug variables that reflect affected values.
For objects to fall on the "ground" the force called gravity
is constantly applied to them (which is a 2D vector that points straight down). It's values are included in object's position calculations and, you can notice, that it gets counteracted in full, when objects touch ground tiles, thus objects don't fall through the platforms.
You can see that when a box gets moved, kinetic_friction
force gets applied to it (from the platforms on which the box is currently standing in the direction opposite to its movement) and it affects further interaction calculations. All calculations (gravity, kinetic_friction, character's push, ground's counteraction), through time, achieve an effect of the box "sliding" on the tiles and gradually stopping. Just like friction in the real world.
This approach to Collisions and interactions between objects gives developers the flexibility to create what is necessary for their game to be interesting and engaging to play.
Conclusions
SGE, by virtue of its architecture, the way it works, and the technologies that it is built with, gives developers the structure that simplifies a major chunk of game development process, allowing them to redirect their resources on other aspects of game creation.
Resources:
Project: SGE
Math: