Project Overview
This advanced capstone project challenges you to build a simple yet powerful game engine from scratch. You will work with the Predict Online Gaming Behavior Dataset from Kaggle containing player engagement metrics, session performance data, and gaming behavior patterns for understanding game performance optimization. The engine must demonstrate proficiency in game loop architecture, entity-component system design, input handling, and game state management using modern C++17/20 features.
ECS Core
Entity, Component, System architecture with cache-friendly design
Physics
Rigid body dynamics, collision detection, spatial partitioning
Rendering
Sprite batching, texture atlases, animation state machines
Audio
Sound effects, music playback, 3D positional audio
Learning Objectives
Technical Skills
- Master Entity Component System architecture patterns
- Implement real-time 2D physics with collision response
- Build efficient sprite rendering with GPU batching
- Design thread-safe resource loading systems
- Create flexible event-driven input handling
Game Dev Skills
- Understand game loop timing and frame rate management
- Implement scene management and state machines
- Optimize for consistent 60 FPS performance
- Handle multi-platform input devices
- Design extensible and modular engine architecture
Company Scenario
PixelForge Studios
You have been hired as a Game Engine Developer at PixelForge Studios, an indie game development company specializing in 2D pixel art games. The studio has been using a third-party engine but wants to build their own custom engine for better control over performance and features. They need an engine that can handle their flagship title: a fast-paced action platformer with hundreds of entities on screen.
"We need a lightweight but powerful 2D engine that gives us full control. It must handle at least 1,000 active entities at 60 FPS, support complex collision detection for our platformer mechanics, and have a clean API that our game programmers can easily use. Can you build an engine that matches the performance of established frameworks while being tailored to our specific needs?"
Technical Requirements
- Maintain 60 FPS with 1,000+ active entities
- Physics step at fixed 50Hz (20ms intervals)
- Sprite batch rendering (minimize draw calls)
- Memory budget: < 100MB for 10,000 entities
- Entity Component System with archetypes
- AABB and Circle collision detection
- Sprite sheets and animation playback
- Input mapping for keyboard/gamepad
- Data-oriented design for cache efficiency
- Component pools with contiguous memory
- System execution order management
- Scene serialization to JSON/binary
- Plugin system for custom components
- Scripting hooks (Lua optional bonus)
- Debug visualization overlays
- Profiling and performance metrics
Test Data & Benchmarks
Download the test suite and benchmark data to validate your game engine implementation. The data includes performance targets, test scenarios, and expected outputs:
Test Suite Download
Download the game engine test data files containing benchmark scenarios, collision test cases, and performance baselines for validation.
Original Data Source
This project uses benchmark data inspired by the Predict Online Gaming Behavior Dataset from Kaggle - containing player engagement metrics, session performance data, and gaming behavior patterns ideal for understanding game performance optimization and player experience analysis.
Test Data Schema
| Column | Type | Description |
|---|---|---|
test_id | String | Unique test identifier (e.g., ECS_001, PHY_015) |
category | String | ecs, physics, rendering, audio, input, resource |
test_name | String | Descriptive test name |
entity_count | Integer | Number of entities in test (100-10000) |
component_count | Integer | Components per entity (1-10) |
expected_frame_time_ms | Float | Maximum allowed frame time |
memory_budget_mb | Float | Maximum memory usage allowed |
test_duration_frames | Integer | Number of frames to run test |
pass_criteria | String | Success condition description |
priority | String | critical, high, medium, low |
| Column | Type | Description |
|---|---|---|
collision_id | String | Unique collision test ID |
shape_a_type | String | aabb, circle, polygon, point |
shape_a_params | String | JSON parameters for shape A |
shape_b_type | String | aabb, circle, polygon, point |
shape_b_params | String | JSON parameters for shape B |
expected_collision | Boolean | true if collision expected |
expected_normal | String | Expected collision normal vector |
expected_penetration | Float | Expected penetration depth |
| Column | Type | Description |
|---|---|---|
render_test_id | String | Unique rendering test ID |
sprite_count | Integer | Number of sprites to render |
texture_count | Integer | Unique textures used |
batch_expected | Integer | Expected draw call count |
resolution | String | Screen resolution (1920x1080, etc.) |
target_fps | Integer | Minimum FPS requirement |
features | String | alpha_blend, rotation, scaling |
| Column | Type | Description |
|---|---|---|
ecs_test_id | String | Unique ECS test ID |
operation | String | create_entity, add_component, query, iterate |
entity_count | Integer | Entities involved in operation |
component_types | Integer | Number of component types |
target_time_us | Float | Maximum operation time (microseconds) |
memory_per_entity | Integer | Expected bytes per entity |
cache_friendly | Boolean | Whether test measures cache efficiency |
Project Requirements
Your game engine must include all of the following systems. Structure your code with clean separation between engine core and game-specific code.
Entity Component System
Core ECS Implementation:
- Entity manager with unique ID generation
- Component pools with contiguous memory allocation
- System base class with update() and fixed_update()
- Component queries with type-safe iteration
Built-in Components:
TransformComponent: position, rotation, scaleSpriteComponent: texture, source rect, colorRigidbodyComponent: velocity, mass, dragColliderComponent: shape, layer, trigger flagAnimationComponent: current frame, speed, loop
Physics System
Collision Detection:
- AABB vs AABB collision detection
- Circle vs Circle collision detection
- AABB vs Circle collision detection
- Collision layers and masks for filtering
- Trigger zones (collision without physics response)
Physics Simulation:
- Fixed timestep physics loop (50Hz recommended)
- Velocity and acceleration integration
- Basic collision response (bounce, slide)
- Gravity and drag forces
- Spatial partitioning (grid or quadtree) for optimization
Rendering System
Sprite Rendering:
- Texture loading with caching (PNG, JPG support)
- Sprite batching by texture to minimize draw calls
- Sprite sheets with source rectangle support
- Color tinting and alpha blending
- Rotation and scaling transformations
Animation System:
- Frame-based animation playback
- Animation state machine (idle, walk, jump, etc.)
- Animation events and callbacks
- Sprite flip for direction changes
Camera System:
- 2D camera with position and zoom
- Camera follow with smoothing/lerp
- Screen shake effects
Input & Audio Systems
Input System:
- Keyboard input (pressed, held, released states)
- Mouse input with position and button states
- Gamepad support (optional but recommended)
- Input action mapping (rebindable controls)
- Event-based input callbacks
Audio System:
- Sound effect playback (WAV, OGG)
- Background music with looping
- Volume control per channel
- Audio resource caching
ECS Architecture
The Entity Component System is the heart of your game engine. Design it for cache efficiency and flexibility. Components should be stored in contiguous memory pools, and systems should iterate over components efficiently.
ECS Core Implementation
// Entity - just an ID
using Entity = uint32_t;
constexpr Entity NULL_ENTITY = 0;
// Component base - uses CRTP for type identification
template<typename T>
class Component {
public:
static size_t GetTypeId() {
static size_t type_id = next_type_id++;
return type_id;
}
private:
static inline size_t next_type_id = 0;
};
// Example components
struct TransformComponent : Component<TransformComponent> {
float x = 0.0f, y = 0.0f;
float rotation = 0.0f;
float scale_x = 1.0f, scale_y = 1.0f;
};
struct SpriteComponent : Component<SpriteComponent> {
std::string texture_id;
int src_x = 0, src_y = 0, src_w = 0, src_h = 0;
uint8_t r = 255, g = 255, b = 255, a = 255;
int z_order = 0;
};
struct RigidbodyComponent : Component<RigidbodyComponent> {
float velocity_x = 0.0f, velocity_y = 0.0f;
float mass = 1.0f;
float drag = 0.0f;
bool is_kinematic = false;
};
// Component Pool - stores components contiguously
template<typename T>
class ComponentPool {
public:
T& Add(Entity entity) {
size_t index = components_.size();
components_.emplace_back();
entity_to_index_[entity] = index;
index_to_entity_.push_back(entity);
return components_.back();
}
void Remove(Entity entity) {
auto it = entity_to_index_.find(entity);
if (it == entity_to_index_.end()) return;
size_t removed_index = it->second;
size_t last_index = components_.size() - 1;
if (removed_index != last_index) {
// Swap with last element
components_[removed_index] = std::move(components_[last_index]);
Entity moved_entity = index_to_entity_[last_index];
entity_to_index_[moved_entity] = removed_index;
index_to_entity_[removed_index] = moved_entity;
}
components_.pop_back();
index_to_entity_.pop_back();
entity_to_index_.erase(entity);
}
T* Get(Entity entity) {
auto it = entity_to_index_.find(entity);
return it != entity_to_index_.end() ? &components_[it->second] : nullptr;
}
bool Has(Entity entity) const {
return entity_to_index_.count(entity) > 0;
}
// Iteration support
auto begin() { return components_.begin(); }
auto end() { return components_.end(); }
size_t size() const { return components_.size(); }
private:
std::vector<T> components_;
std::unordered_map<Entity, size_t> entity_to_index_;
std::vector<Entity> index_to_entity_;
};
World Manager
class World {
public:
Entity CreateEntity() {
return next_entity_++;
}
void DestroyEntity(Entity entity) {
// Remove from all component pools
for (auto& [type_id, pool] : pools_) {
pool->RemoveIfExists(entity);
}
destroyed_entities_.push_back(entity);
}
template<typename T, typename... Args>
T& AddComponent(Entity entity, Args&&... args) {
auto& pool = GetOrCreatePool<T>();
T& component = pool.Add(entity);
((component.*args.first = args.second), ...); // Optional initializer
return component;
}
template<typename T>
T* GetComponent(Entity entity) {
auto pool = GetPool<T>();
return pool ? pool->Get(entity) : nullptr;
}
template<typename T>
bool HasComponent(Entity entity) {
auto pool = GetPool<T>();
return pool && pool->Has(entity);
}
template<typename... Components>
auto View() {
// Returns iterator over entities with all specified components
return EntityView<Components...>(*this);
}
void Update(float delta_time) {
for (auto& system : systems_) {
system->Update(*this, delta_time);
}
}
void FixedUpdate(float fixed_delta_time) {
for (auto& system : systems_) {
system->FixedUpdate(*this, fixed_delta_time);
}
}
private:
Entity next_entity_ = 1;
std::vector<Entity> destroyed_entities_;
std::unordered_map<size_t, std::unique_ptr<IComponentPool>> pools_;
std::vector<std::unique_ptr<System>> systems_;
};
View<Transform, Sprite>()
to efficiently iterate only over entities that have both components. This avoids checking every entity individually.
Physics System
The physics system handles collision detection and response. Use a fixed timestep for deterministic physics simulation and implement spatial partitioning for efficient broad-phase collision detection.
Collision Detection Algorithms
struct AABB {
float x, y, width, height;
float Left() const { return x; }
float Right() const { return x + width; }
float Top() const { return y; }
float Bottom() const { return y + height; }
bool Contains(float px, float py) const {
return px >= Left() && px <= Right() &&
py >= Top() && py <= Bottom();
}
};
struct Circle {
float x, y, radius;
};
// AABB vs AABB
bool CheckCollision(const AABB& a, const AABB& b) {
return a.Left() < b.Right() && a.Right() > b.Left() &&
a.Top() < b.Bottom() && a.Bottom() > b.Top();
}
// Circle vs Circle
bool CheckCollision(const Circle& a, const Circle& b) {
float dx = b.x - a.x;
float dy = b.y - a.y;
float distance_sq = dx * dx + dy * dy;
float radius_sum = a.radius + b.radius;
return distance_sq < radius_sum * radius_sum;
}
// AABB vs Circle
bool CheckCollision(const AABB& aabb, const Circle& circle) {
// Find closest point on AABB to circle center
float closest_x = std::clamp(circle.x, aabb.Left(), aabb.Right());
float closest_y = std::clamp(circle.y, aabb.Top(), aabb.Bottom());
float dx = circle.x - closest_x;
float dy = circle.y - closest_y;
return (dx * dx + dy * dy) < (circle.radius * circle.radius);
}
// Collision manifold for response
struct CollisionManifold {
bool collided = false;
float normal_x = 0.0f, normal_y = 0.0f; // Collision normal
float penetration = 0.0f; // Penetration depth
};
CollisionManifold GetManifold(const AABB& a, const AABB& b) {
CollisionManifold m;
float overlap_x = std::min(a.Right(), b.Right()) - std::max(a.Left(), b.Left());
float overlap_y = std::min(a.Bottom(), b.Bottom()) - std::max(a.Top(), b.Top());
if (overlap_x <= 0 || overlap_y <= 0) return m;
m.collided = true;
// Use smallest overlap as separation axis
if (overlap_x < overlap_y) {
m.penetration = overlap_x;
m.normal_x = (a.x < b.x) ? -1.0f : 1.0f;
m.normal_y = 0.0f;
} else {
m.penetration = overlap_y;
m.normal_x = 0.0f;
m.normal_y = (a.y < b.y) ? -1.0f : 1.0f;
}
return m;
}
Collision Response
void ResolveCollision(RigidbodyComponent& rb_a, RigidbodyComponent& rb_b,
TransformComponent& tf_a, TransformComponent& tf_b,
const CollisionManifold& manifold) {
if (!manifold.collided) return;
// Skip if both are kinematic
if (rb_a.is_kinematic && rb_b.is_kinematic) return;
// Calculate relative velocity
float rel_vel_x = rb_b.velocity_x - rb_a.velocity_x;
float rel_vel_y = rb_b.velocity_y - rb_a.velocity_y;
// Relative velocity along collision normal
float vel_along_normal = rel_vel_x * manifold.normal_x +
rel_vel_y * manifold.normal_y;
// Don't resolve if velocities are separating
if (vel_along_normal > 0) return;
// Restitution (bounciness) - use minimum of both
float restitution = 0.3f; // Could be stored in RigidbodyComponent
// Calculate impulse scalar
float inv_mass_a = rb_a.is_kinematic ? 0.0f : 1.0f / rb_a.mass;
float inv_mass_b = rb_b.is_kinematic ? 0.0f : 1.0f / rb_b.mass;
float impulse_scalar = -(1.0f + restitution) * vel_along_normal;
impulse_scalar /= inv_mass_a + inv_mass_b;
// Apply impulse
float impulse_x = impulse_scalar * manifold.normal_x;
float impulse_y = impulse_scalar * manifold.normal_y;
if (!rb_a.is_kinematic) {
rb_a.velocity_x -= inv_mass_a * impulse_x;
rb_a.velocity_y -= inv_mass_a * impulse_y;
}
if (!rb_b.is_kinematic) {
rb_b.velocity_x += inv_mass_b * impulse_x;
rb_b.velocity_y += inv_mass_b * impulse_y;
}
// Positional correction (prevent sinking)
const float percent = 0.8f; // Penetration percentage to correct
const float slop = 0.01f; // Penetration allowance
float correction = std::max(manifold.penetration - slop, 0.0f) /
(inv_mass_a + inv_mass_b) * percent;
float correction_x = correction * manifold.normal_x;
float correction_y = correction * manifold.normal_y;
if (!rb_a.is_kinematic) {
tf_a.x -= inv_mass_a * correction_x;
tf_a.y -= inv_mass_a * correction_y;
}
if (!rb_b.is_kinematic) {
tf_b.x += inv_mass_b * correction_x;
tf_b.y += inv_mass_b * correction_y;
}
}
Spatial Hash Grid
class SpatialHashGrid {
public:
SpatialHashGrid(float cell_size = 64.0f) : cell_size_(cell_size) {}
void Clear() {
cells_.clear();
}
void Insert(Entity entity, const AABB& bounds) {
int min_x = static_cast<int>(std::floor(bounds.Left() / cell_size_));
int max_x = static_cast<int>(std::floor(bounds.Right() / cell_size_));
int min_y = static_cast<int>(std::floor(bounds.Top() / cell_size_));
int max_y = static_cast<int>(std::floor(bounds.Bottom() / cell_size_));
for (int y = min_y; y <= max_y; ++y) {
for (int x = min_x; x <= max_x; ++x) {
cells_[HashCell(x, y)].push_back(entity);
}
}
}
std::vector<Entity> Query(const AABB& bounds) const {
std::unordered_set<Entity> found;
int min_x = static_cast<int>(std::floor(bounds.Left() / cell_size_));
int max_x = static_cast<int>(std::floor(bounds.Right() / cell_size_));
int min_y = static_cast<int>(std::floor(bounds.Top() / cell_size_));
int max_y = static_cast<int>(std::floor(bounds.Bottom() / cell_size_));
for (int y = min_y; y <= max_y; ++y) {
for (int x = min_x; x <= max_x; ++x) {
auto it = cells_.find(HashCell(x, y));
if (it != cells_.end()) {
for (Entity e : it->second) {
found.insert(e);
}
}
}
}
return std::vector<Entity>(found.begin(), found.end());
}
// Get potential collision pairs (broad phase)
std::vector<std::pair<Entity, Entity>> GetPotentialPairs() const {
std::set<std::pair<Entity, Entity>> pairs;
for (const auto& [hash, entities] : cells_) {
for (size_t i = 0; i < entities.size(); ++i) {
for (size_t j = i + 1; j < entities.size(); ++j) {
Entity a = std::min(entities[i], entities[j]);
Entity b = std::max(entities[i], entities[j]);
pairs.insert({a, b});
}
}
}
return std::vector<std::pair<Entity, Entity>>(pairs.begin(), pairs.end());
}
private:
size_t HashCell(int x, int y) const {
// Simple hash combining x and y
return std::hash<int>{}(x) ^ (std::hash<int>{}(y) << 16);
}
float cell_size_;
std::unordered_map<size_t, std::vector<Entity>> cells_;
};
Fixed Timestep Game Loop
class Engine {
public:
void Run() {
const float FIXED_TIMESTEP = 1.0f / 50.0f; // 50 Hz physics
const float MAX_FRAME_TIME = 0.25f; // Cap to prevent spiral
auto previous_time = std::chrono::high_resolution_clock::now();
float accumulator = 0.0f;
while (is_running_) {
auto current_time = std::chrono::high_resolution_clock::now();
float frame_time = std::chrono::duration<float>(
current_time - previous_time).count();
previous_time = current_time;
// Cap frame time to avoid spiral of death
if (frame_time > MAX_FRAME_TIME) {
frame_time = MAX_FRAME_TIME;
}
accumulator += frame_time;
// Process input (once per frame)
ProcessInput();
// Fixed timestep updates (physics)
while (accumulator >= FIXED_TIMESTEP) {
world_.FixedUpdate(FIXED_TIMESTEP);
accumulator -= FIXED_TIMESTEP;
}
// Variable timestep updates (rendering, animations)
float alpha = accumulator / FIXED_TIMESTEP; // For interpolation
world_.Update(frame_time);
// Render with interpolation
Render(alpha);
// Frame limiting (optional, depends on vsync)
LimitFrameRate(60);
}
}
private:
void ProcessInput() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
is_running_ = false;
}
input_system_->ProcessEvent(event);
}
}
void Render(float interpolation_alpha) {
renderer_->Clear();
render_system_->Render(world_, interpolation_alpha);
renderer_->Present();
}
void LimitFrameRate(int target_fps) {
static auto frame_start = std::chrono::high_resolution_clock::now();
auto frame_end = std::chrono::high_resolution_clock::now();
float target_frame_time = 1.0f / target_fps;
float elapsed = std::chrono::duration<float>(
frame_end - frame_start).count();
if (elapsed < target_frame_time) {
int sleep_ms = static_cast<int>(
(target_frame_time - elapsed) * 1000);
SDL_Delay(sleep_ms);
}
frame_start = std::chrono::high_resolution_clock::now();
}
bool is_running_ = true;
World world_;
std::unique_ptr<InputSystem> input_system_;
std::unique_ptr<RenderSystem> render_system_;
std::unique_ptr<Renderer> renderer_;
};
Rendering Pipeline
Efficient rendering is crucial for game performance. Implement sprite batching to minimize draw calls and texture atlases to reduce texture switches.
Sprite Batch Renderer
struct Vertex {
float x, y; // Position
float u, v; // Texture coordinates
uint8_t r, g, b, a; // Color
};
class SpriteBatch {
public:
static constexpr size_t MAX_SPRITES = 10000;
static constexpr size_t VERTICES_PER_SPRITE = 4;
static constexpr size_t INDICES_PER_SPRITE = 6;
SpriteBatch() {
vertices_.reserve(MAX_SPRITES * VERTICES_PER_SPRITE);
indices_.reserve(MAX_SPRITES * INDICES_PER_SPRITE);
}
void Begin() {
vertices_.clear();
indices_.clear();
current_texture_ = nullptr;
draw_calls_ = 0;
}
void Draw(Texture* texture, float x, float y, float w, float h,
float src_x, float src_y, float src_w, float src_h,
uint8_t r = 255, uint8_t g = 255, uint8_t b = 255, uint8_t a = 255,
float rotation = 0.0f, float origin_x = 0.0f, float origin_y = 0.0f) {
// Flush if texture changes or batch is full
if (texture != current_texture_ ||
vertices_.size() >= MAX_SPRITES * VERTICES_PER_SPRITE) {
Flush();
current_texture_ = texture;
}
// Calculate UV coordinates
float tex_w = static_cast<float>(texture->GetWidth());
float tex_h = static_cast<float>(texture->GetHeight());
float u0 = src_x / tex_w, v0 = src_y / tex_h;
float u1 = (src_x + src_w) / tex_w, v1 = (src_y + src_h) / tex_h;
// Apply rotation if needed
std::array<float, 8> positions;
if (std::abs(rotation) > 0.001f) {
float cos_r = std::cos(rotation);
float sin_r = std::sin(rotation);
// Rotate around origin
auto rotate = [&](float px, float py) -> std::pair<float, float> {
float rx = px - origin_x;
float ry = py - origin_y;
return {
x + origin_x + rx * cos_r - ry * sin_r,
y + origin_y + rx * sin_r + ry * cos_r
};
};
auto [x0, y0] = rotate(0, 0);
auto [x1, y1] = rotate(w, 0);
auto [x2, y2] = rotate(w, h);
auto [x3, y3] = rotate(0, h);
positions = {x0, y0, x1, y1, x2, y2, x3, y3};
} else {
positions = {x, y, x + w, y, x + w, y + h, x, y + h};
}
// Add vertices
uint32_t base_index = static_cast<uint32_t>(vertices_.size());
vertices_.push_back({positions[0], positions[1], u0, v0, r, g, b, a});
vertices_.push_back({positions[2], positions[3], u1, v0, r, g, b, a});
vertices_.push_back({positions[4], positions[5], u1, v1, r, g, b, a});
vertices_.push_back({positions[6], positions[7], u0, v1, r, g, b, a});
// Add indices (two triangles per quad)
indices_.push_back(base_index);
indices_.push_back(base_index + 1);
indices_.push_back(base_index + 2);
indices_.push_back(base_index);
indices_.push_back(base_index + 2);
indices_.push_back(base_index + 3);
}
void End() {
Flush();
}
int GetDrawCallCount() const { return draw_calls_; }
private:
void Flush() {
if (vertices_.empty() || !current_texture_) return;
// Bind texture and upload vertex data
current_texture_->Bind();
// Upload to GPU and draw (implementation depends on graphics API)
// For SDL2: SDL_RenderGeometry()
// For OpenGL: glBufferData() + glDrawElements()
draw_calls_++;
vertices_.clear();
indices_.clear();
}
std::vector<Vertex> vertices_;
std::vector<uint32_t> indices_;
Texture* current_texture_ = nullptr;
int draw_calls_ = 0;
};
Animation System
struct AnimationFrame {
int src_x, src_y, src_w, src_h; // Source rectangle in sprite sheet
float duration; // Frame duration in seconds
};
struct Animation {
std::string name;
std::vector<AnimationFrame> frames;
bool loop = true;
};
class AnimationController {
public:
void AddAnimation(const std::string& name, Animation animation) {
animations_[name] = std::move(animation);
}
void Play(const std::string& name, bool force_restart = false) {
if (current_animation_ != name || force_restart) {
current_animation_ = name;
current_frame_ = 0;
frame_time_ = 0.0f;
}
}
void Update(float delta_time) {
auto it = animations_.find(current_animation_);
if (it == animations_.end()) return;
const Animation& anim = it->second;
if (anim.frames.empty()) return;
frame_time_ += delta_time;
while (frame_time_ >= anim.frames[current_frame_].duration) {
frame_time_ -= anim.frames[current_frame_].duration;
current_frame_++;
if (current_frame_ >= anim.frames.size()) {
if (anim.loop) {
current_frame_ = 0;
} else {
current_frame_ = anim.frames.size() - 1;
is_finished_ = true;
break;
}
}
}
}
const AnimationFrame& GetCurrentFrame() const {
return animations_.at(current_animation_).frames[current_frame_];
}
bool IsFinished() const { return is_finished_; }
private:
std::unordered_map<std::string, Animation> animations_;
std::string current_animation_;
size_t current_frame_ = 0;
float frame_time_ = 0.0f;
bool is_finished_ = false;
};
Submission Requirements
Create a public GitHub repository with the exact name shown below:
Required Repository Name
cpp-simple-game-engine
Required Project Structure
cpp-simple-game-engine/
├── include/
│ └── engine/
│ ├── core/
│ │ ├── engine.hpp
│ │ ├── world.hpp
│ │ └── entity.hpp
│ ├── ecs/
│ │ ├── component.hpp
│ │ ├── component_pool.hpp
│ │ └── system.hpp
│ ├── components/
│ │ ├── transform.hpp
│ │ ├── sprite.hpp
│ │ ├── rigidbody.hpp
│ │ ├── collider.hpp
│ │ └── animation.hpp
│ ├── systems/
│ │ ├── physics_system.hpp
│ │ ├── render_system.hpp
│ │ ├── animation_system.hpp
│ │ └── input_system.hpp
│ ├── physics/
│ │ ├── collision.hpp
│ │ └── spatial_hash.hpp
│ ├── rendering/
│ │ ├── renderer.hpp
│ │ ├── sprite_batch.hpp
│ │ └── texture.hpp
│ ├── audio/
│ │ └── audio_manager.hpp
│ └── input/
│ └── input_manager.hpp
├── src/
│ └── ... (implementation files)
├── examples/
│ └── platformer/
│ ├── main.cpp
│ └── assets/
├── tests/
│ ├── test_ecs.cpp
│ ├── test_collision.cpp
│ └── test_rendering.cpp
├── benchmarks/
│ └── benchmark_ecs.cpp
├── docs/
│ └── API.md
├── CMakeLists.txt
└── README.md
Do Include
- Complete engine source code with headers
- Working example game (simple platformer)
- Unit tests for ECS and collision
- Performance benchmarks
- API documentation (Doxygen or markdown)
- CMakeLists.txt for cross-platform build
Do Not Include
- Pre-built binaries or object files
- IDE-specific project files (.vs, .idea)
- Large asset files (>10MB images/audio)
- Third-party library source (use submodules)
- Build directories (build/, bin/, obj/)
Enter your GitHub username - we will verify your repository automatically
Grading Rubric
Your project will be graded on the following criteria. Total: 750 points.
| Criteria | Points | Description |
|---|---|---|
| ECS Architecture | 150 | Entity/Component/System design, component pools, queries, iteration efficiency |
| Physics System | 125 | Collision detection (AABB, Circle), response, spatial partitioning, fixed timestep |
| Rendering System | 125 | Sprite batching, texture management, animations, camera system |
| Input & Audio | 75 | Keyboard/mouse input, action mapping, sound effects, music playback |
| Performance | 100 | 60 FPS with 1000+ entities, efficient memory usage, minimal draw calls |
| Example Game | 75 | Working demo showcasing engine features (movement, collision, sprites) |
| Code Quality | 50 | Modern C++17/20, RAII, const-correctness, naming conventions |
| Documentation | 50 | README, API docs, code comments, architecture overview |
| Total | 750 |
Grading Levels
Excellent
Exceeds all requirements with exceptional quality
Good
Meets all requirements with good quality
Satisfactory
Meets minimum requirements
Needs Work
Missing key requirements
Ready to Submit?
Make sure you have completed all requirements and reviewed the grading rubric above.
Submit Your Project