Six systems. Each with a dedicated class or component and a clear responsibility.
Player doesn't do anything itself, it owns components that do things. The Player class is an owner and coordinator. Game logic accesses behaviour through the thin interfaces each component implements.
Every game object derives from one or more of four pure-virtual interfaces.
IGameEntity bundles all four for common game objects. Lighter components
pick only what they need.
| Interface | Contract | Used by |
|---|---|---|
| IRenderable | render(sf::RenderTarget&): any drawable object satisfies this. The game loop never knows what it's drawing. |
Player, WorldObject, WorldItem, PointOfInterest, HUDComponent... |
| IUpdatable | update(sf::Time): fixed-timestep loop calls this on all registered entities. Each entity is responsible for its own state. |
Player, HealthComponent, StatComponent, Enemy types... |
| IPositionable | getPosition() / setPosition(): enables generic position queries without knowing the concrete type. |
Player, WorldObject, WorldItem, PointOfInterest... |
| ICollidable | getBounds() returns world-space sf::FloatRect for broad-phase collision pre-checks. |
Player, WorldObject, WorldItem, PointOfInterest... |
A Pushdown Automaton in GameStateManager handles MainMenu, Playing, Paused, Settings, and GameOver. The key behaviour: pushing a state saves the current one on a stack. Popping returns to it. This is what lets Settings be opened from both main menu and pause menu and return correctly to the right prior state each time.
Two phases. Broad-phase AABB check first, if the bounding boxes don't overlap, skip everything else. Narrow-phase SAT for convex polygons loaded from .tmx files. Minimum Translation Vector resolution pushes entities cleanly out of geometry. Flyweight-shared collision shapes mean thousands of world objects don't each own a copy of their polygon data.
Abstracts keyboard and gamepad behind InputAction enums. wasJustPressed and wasJustReleased are O(1) array lookups using memcpy state snapshots, current frame vs previous frame. Runtime key rebinding stored in std::unordered_map for O(1) lookup.
Two enemy types with state machines. SavageEnemy: Idle -> Chase -> Lost using line-of-sight raycasting. ChomperEnemy adds a Leap state, a velocity-locked lunge with its own cooldown that aborts on wall contact via applyCollisionCorrection(). Both managed by EnemyManager using fixed-size object pools.
A 2x5 slot grid with drag-and-drop, right-click context menus (Use / Drop), and a Flyweight ItemTypeRegistry that stores one data block per item type. Items in the world use WorldItemPool, 64 pre-allocated slots, activated and deactivated rather than heap-allocated per spawn.
Dual SFML views: m_gameView follows the player with map-bounds clamping; m_uiView is fixed to screen space. All UI renders under the fixed view, this is what fixed the HUD drift bug where bars were moving with the camera. Frustum culling on tiles, POIs, world objects, and items.