Build Your Own 2D Game Engine
"Every shipped game is a study in compromises. Building an engine is where you discover which compromises matter."
A 2D game engine is one of the most fun and visible portfolio projects. By the end you have an engine that can run a small game — Tetris, Snake, a platformer, a roguelike — and you understand the game loop, scene graphs, sprite rendering, input handling, audio, and entity-component systems.
1. Overview & motivation
A game engine has a small set of core systems:
- Game loop — fixed-timestep update + variable-rate render.
- Window / input / audio — usually via SDL2, raylib, or a similar library.
- Renderer — sprite drawing, layers, camera transforms.
- Scene graph or ECS — how game objects are organized and updated.
- Physics — collision detection at minimum; a physics engine at more.
- Asset pipeline — loading sprites, sounds, levels.
What you can only learn by building one:
- Why fixed-timestep updates with variable rendering is the universal pattern (Glenn Fiedler's "Fix Your Timestep!").
- Why entity-component-systems dominate modern engine design (cache locality, composition over inheritance).
- Why scene graphs are simple in concept but quickly accumulate edge cases.
- Why game development is half engineering, half art direction — your engine choices constrain what games you can make on it.
2. Where this fits in the degree
- Phase: Production
- Semester: Capstone elective
- Modules deepened: Sem 8 Module 4 (scale, reliability, performance) — frame budgets are a strict performance discipline.
Cross-phase relevance:
- Pairs naturally with the Physics Engine tutorial.
- Builds on the Memory Allocator tutorial — arena allocators are common in game engines.
3. Prerequisites
- A language: C++, Rust, C#, JavaScript, or Lua. C++ and Rust are the production defaults; the others are excellent for tutorials.
- Basic linear algebra: vectors, matrices, 2D transforms.
- A graphics library: SDL2 (recommended), raylib, or MonoGame (C#).
4. Theory & research
Required reading
- Robert Nystrom, Game Programming Patterns (gameprogrammingpatterns.com) — free online. Patterns specific to game development: game loop, update method, component, observer, state, double buffer, object pool, dirty flag, spatial partition, command. ⭐ start here.
- Glenn Fiedler, "Fix Your Timestep!" (gafferongames.com/post/fix_your_timestep/) — the canonical game-loop article.
Strongly recommended
- Jason Gregory, Game Engine Architecture (3rd ed.) — the game-engine textbook. Industrial-strength.
- Bruno Yamaguchi, "Game Programming in C++" — alternative book.
- handmadehero.org — Casey Muratori's 1,000+ episode "from scratch" game programming series. Spectacular for systems-level engagement.
For ECS specifically
- Unity DOTS documentation — modern ECS in production.
- Bevy ECS (bevyengine.org) — Rust, clean modern ECS.
5. Curated tutorial list (from BYO-X)
The BYO-X "Game" section is huge. Selected highlights:
- C: Handmade Hero — Casey Muratori, handmadehero.org ⭐ the deepest path
- C++: Breakout, Beginning Game Programming v2.0 — LazyFoo's SDL tutorials, lazyfoo.net ⭐ classic SDL series
- C++: Space Invaders from Scratch
- JavaScript: 2D breakout game using Phaser — MDN tutorial
- JavaScript: How to Make Flappy Bird in HTML5 With Phaser
- JavaScript: Developing Games with React, Redux, and SVG
- JavaScript: Build your own 8-Ball Pool game from scratch [video]
- JavaScript: How to Make Your First Roguelike
- JavaScript: Think like a programmer: How to build Snake using only JavaScript, HTML & CSS
- Lua: BYTEPATH — a16bitnes's blog
- Python: Developing Games With PyGame
- Python: Making Games with Python & Pygame [pdf] — Al Sweigart's free book
- Python: Roguelike Tutorial Revised — Roguelike Dev Reddit's libtcod tutorial ⭐ recommended primary for roguelikes
- Ruby: Developing Games With Ruby
- Ruby: Ruby Snake
- Rust: Adventures in Rust: A Basic 2D Game
- Rust: Roguelike Tutorial in Rust + tcod — bfnightly.bracketproductions.com
- Java: Code a 2D Game Engine using Java - Full Course for Beginners [video] — long, well-paced
- Java: 3D Game Development with LWJGL 3 — bonus for 3D
- C#: Learn C# by Building a Simple RPG, Creating a Roguelike Game in C#, Build a C#/WPF RPG
- C: Chess Engine In C [video], Let's Make: Dangerous Dave [video]
- Go: Games With Go [video]
6. Recommended primary path
Pick a target game first; the engine emerges from the game. Three recommended starting projects, by ambition:
Path A: Pong / Breakout (1 weekend)
LazyFoo's SDL tutorials in C++. By tutorial 12 you have a working Breakout. Excellent for first contact.
Path B: Snake or Tetris (1 week)
Implement classic mechanics. Fixed playfield, simple physics, simple input.
Path C: Roguelike (3–6 weeks)
The recommended primary path for this degree. Roguelike Dev's "Complete Roguelike Tutorial Revised" (Python + libtcod) or bracket-lib's "Roguelike Tutorial in Rust". Both walk through:
- Map generation (BSP, cellular automata, random rooms).
- Field of view (FoV).
- Entity-component system.
- Combat, items, magic.
- Saving / loading.
Roguelikes are a sweet spot: deeply systemic without requiring sprite art (ASCII or simple tiles work).
Path D: Handmade Hero (years)
Casey Muratori's full series. Build everything — even the file I/O — from scratch. Not for the impatient. Unparalleled depth.
For this degree: Path C (roguelike) is the default for portfolio. Path A or B is fine if shorter scope is needed.
7. Implementation milestones
(Generic 2D engine, distilled from Roguelike tutorials and Nystrom's Game Programming Patterns.)
Milestone 1: Window + game loop
Open a window. Implement a fixed-timestep loop with variable rendering (Fiedler's "the gist").
double t = 0.0;
const double dt = 1.0 / 60.0; // 60 Hz logic
double currentTime = getTime();
double accumulator = 0.0;
while (!quit) {
double newTime = getTime();
double frameTime = newTime - currentTime;
currentTime = newTime;
accumulator += frameTime;
while (accumulator >= dt) {
update(dt);
accumulator -= dt;
t += dt;
}
render(accumulator / dt); // interpolation factor
}
Evidence: Window stays open. FPS displays. Frame budget visible.
Milestone 2: Render a moving sprite
Load a PNG, render at varying positions.
Evidence: Sprite moves across the screen at constant speed regardless of frame rate.
Milestone 3: Input handling
Map keyboard / mouse / gamepad events to game actions.
Evidence: Player sprite moves in response to WASD.
Milestone 4: Entity-component-system (ECS)
Replace ad-hoc object hierarchies with components.
struct Position { x: f32, y: f32 }
struct Velocity { dx: f32, dy: f32 }
struct Sprite { texture_id: u32 }
// systems:
fn physics_system(entities: &mut [Entity], dt: f32) {
for e in entities {
if let (Some(p), Some(v)) = (e.position_mut(), e.velocity()) {
p.x += v.dx * dt;
p.y += v.dy * dt;
}
}
}
Evidence: Adding a new component (e.g., Health) requires no changes to existing system code.
Milestone 5: Collision detection (AABB)
Axis-aligned bounding boxes. Check overlap for each pair.
fn aabb_collide(a: &AABB, b: &AABB) -> bool {
a.x < b.x + b.w && a.x + a.w > b.x &&
a.y < b.y + b.h && a.y + a.h > b.y
}
For many objects: spatial partitioning (grid or quadtree). Read Nystrom's "Spatial Partition" chapter.
Evidence: Player can pick up items by colliding with them.
Milestone 6: Camera and viewport
The world is larger than the screen. The camera follows the player.
let camera = Camera { x: player.x - SCREEN_WIDTH / 2.0, y: player.y - SCREEN_HEIGHT / 2.0 };
// when rendering:
draw_at(world_x - camera.x, world_y - camera.y, sprite);
Evidence: Player can walk across a world larger than the screen.
Milestone 7: Audio
Load sounds; play them on game events.
Evidence: Footsteps, item pickup, damage all play correct sounds.
Milestone 8: Scene management
Title screen, gameplay screen, pause menu. A scene stack: push, pop, top scene gets update/render.
Evidence: ESC pauses; tap again to resume. Game-over screen shows score.
Milestone 9: Save / load
Serialize the entire ECS to disk. Reload restores state exactly.
Milestone 10 (game-specific): Procedural map generation
For roguelikes: BSP, cellular automata, drunkard's walk.
For platformers: hand-designed levels loaded from TMX (Tiled) files.
8. Tests & evidence
| Test | How |
|---|---|
| Frame rate | Stable 60 FPS on the target hardware |
| Determinism | Same input sequence → same game state (essential for tests, replays) |
| Collision | Hand-traced collision scenarios |
| ECS performance | 10,000 entities at 60 FPS |
| Save/load | Save mid-game, load, identical state |
| A real game | Tetris, Snake, Pong, or a roguelike level playable |
The strongest evidence: a video or GIF of someone playing your game.
9. Common pitfalls
- Variable timestep physics. Causes non-determinism. Use a fixed timestep.
- No FPS cap. Burns CPU/GPU for no benefit. Cap at 60.
- Allocation in the inner loop. Pre-allocate; reuse. Object pools are the canonical pattern (Nystrom chapter 19).
- Tight coupling between game logic and rendering. Logic should be testable without a window.
- Naive O(n²) collision. Slow with 100+ entities. Use spatial partition.
- Mixing screen and world coordinates. Be explicit:
world_pos,screen_pos. Convert via camera. - No ECS. Object hierarchies become unwieldy quickly. Even a simple ECS is worth the upfront cost.
- Input read from keyboard polling AND from event queue. Pick one model.
10. Extensions
- Particle systems. Sparks, smoke, explosions.
- Tilemap rendering. Standard 2D map representation.
- Animation system. Sprite sheets, state machine for animations.
- Lighting. 2D dynamic lighting (e.g., flashlight effect).
- Multiplayer (local). Two players on one keyboard.
- Multiplayer (networked). Hard. See "client prediction" and "lag compensation."
- Modding API. Expose Lua or Python; let users write scripts.
- Asset packaging. Bundle all sprites, sounds, levels into one file.
A game engine is one of those projects with no obvious ceiling.
11. Module integration
| Module | What the game engine deepens |
|---|---|
| Sem 4 Module 1 — C / Rust fundamentals | Substantial project with strict performance requirements. |
| Sem 4 Module 3 — Computer organization | Cache-friendly data layout (ECS). Frame budgets. |
| Sem 8 Module 4 — Scale & performance | Frame budget is the performance budget. |
| Memory Allocator tutorial | Arena allocators are common in engines. |
| Physics Engine tutorial | Natural extension. |
| 3D Renderer tutorial | 2D → 3D is a real conceptual leap; revisit linear algebra. |
12. Portfolio framing
What to publish:
- Engine source (
engine/{core, render, input, audio, ecs, physics}). - One complete playable game built on top.
- A README with:
- Video of gameplay.
- Architecture diagram.
- Performance numbers.
- Free downloads of compiled binaries for at least one platform (Windows or macOS or Linux).
Reviewer entry points:
engine/core/loop.cpp— the game loop.engine/ecs/world.cpp— the ECS.game/main.cpp— the actual game.- README with playable build link or web demo.
A working 2D engine + game is one of the most engaging portfolio pieces a programmer can publish. Everyone can immediately see what it does; everyone respects the work.
Source
This tutorial draws from the BYO-X catalog "Game" section. Nystrom's Game Programming Patterns and Gregory's Game Engine Architecture are the canonical references. Roguelike tutorials are the recommended starting project shape.