C++17 · SFML 3.0 · SETU Carlow - Year IV · 2026

THORNS

A top-down 2D survival game.

Component Architecture Procedural Generation SAT Collision Flyweight Spatial Partition

Fourth Year Project Architecture

Thorns is a top-down 2D survival game built in C++17 with SFML 3.0. The research question was simple: can the Game Programming Patterns catalogue (Nystrom, 2014) be applied systematically to produce a demonstrably better-structured codebase.

Seven patterns were implemented, each chosen to solve a specific, real problem in the code, not just to tick boxes. The project also implements a multi-phase procedural generation pipeline and a SAT polygon collision system with profiled performance results.

What's in it.

Component Architecture

Player is composed from independent components: SpriteComponent, HealthComponent, StatComponent (x3), Inventory, HUDComponent, CursorComponent. Nothing inherits from Player. Adding a feature touches only the relevant component.

Procedural Generation

A two-phase pipeline. Phase 1 uses Voronoi regionalization with Bridson's Poisson disk sampling for site placement. Phase 2 applies octave Perlin noise for world object placement. Phases 3 and 4 (Cellular Automata, Dijkstra) were cut during development.

Polygon Collision

AABB broad-phase pre-check followed by SAT narrow-phase for convex polygons loaded from Tiled .tmx files. Collision shape data is Flyweight-shared, thousands of world objects reference one shape.

State Management

A Pushdown Automaton in GameStateManager handles five states. Push/pop semantics let Settings overlay correctly from both the main menu and the pause menu, returning to the right prior state each time.

Input Abstraction

InputController maps InputAction enums to physical keys. Game logic queries InputAction::Sprint, never sf::Keyboard::Key::LShift. Full runtime rebinding via the Settings menu. wasJustPressed/Released handled via memcpy state snapshots.

Enemy AI

Two enemy types, each with a state machine. SavageEnemy: Idle -> Chase -> Lost. ChomperEnemy adds a Leap state, a velocity locked lunge that aborts on wall contact. Both use line-of-sight raycasting. Incomplete but functional.

Games that shaped the direction.

Three games informed what Thorns is trying to be. Not in mechanics copied wholesale, but in the feeling each one creates, the atmosphere, the tension, the way the world feels like it belongs to something other than the player.

Visual and gameplay reference
Darkwood
Acid Wizard Studio - 2017
The primary visual and tonal reference. Darkwood's top-down perspective, muted palette, and oppressive forest atmosphere are the clearest influence on Thorns' art direction and camera approach. The way it uses daylight as safety and darkness as threat, something Thorns was intended to implement with its own day/night cycle.
Tone and world design
S.T.A.L.K.E.R.
GSC Game World — 2007
The tonal reference. Not the mechanics, but the way S.T.A.L.K.E.R. creates a world that feels lived-in and reactive, where the player is a small part of a larger ecosystem. The procedural generation was intended to support a similar sense of place not handcrafted setpieces, but emergent moments from the interaction of systems and player choice.
Procedural generation reference
Prey: Mooncrash
Arkane Studios — 2018
The generation reference. Mooncrash demonstrated that procedural variability can co-exist with authored, fixed-location structures, the station layout is fixed, but item placement, enemy positions, and objectives shift each run. Thorns' approach to generation follows the same logic: the hideout is always at the centre, POIs are always at Voronoi sites, but the exact positions, loot distribution, and enemy spawns vary with the seed.

What went right and what didn't.

What went right

The architectural foundation was established early and stayed stable throughout. The interface hierarchy was flexible enough to accommodate everything added during development without modification, which is the whole point of doing it that way.

The profiling workflow produced clear, actionable data. Identifying VoronoiDiagram::renderDebug as a 28.93% CPU hotspot wasn't obvious before running the profiler. The TMX-based collision template system made adjusting collision shapes straightforward without recompilation.

The view separation fix (m_gameView vs m_uiView) solved a whole class of HUD drift bugs cleanly. Identifying the root cause and applying a structural fix was more satisfying than hacking around it.

What went wrong

The enemy system took longer than expected and ended up incomplete. In hindsight, a simpler enemy, just directional movement and contact damage, should have been done earlier as a proof of the IGameEntity interface. The architectural groundwork was there, the time wasn't.

There's an anonymous struct in CollisionType.h that's a leftover from an abandoned circle collision type. A reminder that exploratory code should live on a branch, not main.

The first attempt at map rendering created 16,384 individual sprites for a 128x128 map, producing a 25,000ms generation time.

Sections.