Entity hierarchy, behaviour, collision, camera and phase management.
The abstract Entity base provides a shared interface via pure virtual methods.
NPC derives from Entity and owns its behaviour and animation as components, not base classes.
SpecialEntity inherits from NPC - justified because a SpecialEntity is genuinely a type of NPC,
using the same animation system, NPCBehaviour, and movement pipeline.
m_isInCameraView, m_hasBeenPhotographed, m_isHighlighted, m_isWearingHat, m_isFleeing, m_isScreaming, m_isIdle. Each flag drives both visual state and behaviour assignment decisions.
NPCBehaviour is an abstract class with one virtual method:
calculateMovement(float deltaTime, float moveSpeed).
Each frame the NPC calls this on its current behaviour and applies the returned velocity.
The NPC class contains no movement logic. Behaviour is swapped at runtime via
setBehaviour(unique_ptr).
| Behaviour | Internal State | Description |
|---|---|---|
| WanderBehavior | wanderAngle, wanderTimer | Default NPC state. Adjusts direction by a random angle offset every 1.5 seconds. Kinematic wander from Millington (2019). |
| FleeBehavior | fleeDirection, FLEE_SPEED_MULTIPLIER | Moves at 2× speed away from a threat. Direction is the normalised vector away from the hat-wearing NPC in range. |
| FlockBehavior | steeringDirection, currentVelocity | Interpolates velocity toward the designated leader's direction using a turn speed factor to prevent snapping. One leader per faction (hat / no hat). |
| AngryNPCBehavior | State enum, stateTimer, wanderAngle | Three-state machine: Chasing → Screaming → Wandering. Chase speed is 1.3× normal. Screaming locks movement for 2 seconds. Timer-driven transitions. |
| JournalistBehavior | State enum, stateTimer | Two-state machine: Wandering / Idle. Swaps sprites when changing state. Used exclusively by the Journalist SpecialEntity. |
Enum-based state machine in the Game class. States: Menu, Playing, Capturing, ShowingPhoto, NPCReaction, About, Paused, GameOver. Game::update() and Game::render() both switch on m_currentState. A separate GamePhase enum tracks narrative progression and is managed exclusively by PhaseManager.
Two collision systems. AABB via SFML's findIntersection() for the viewfinder-to-NPC check - accurate because both the viewfinder and NPC sprites are rectangles. Statue obstacle uses a sf::FloatRect boundary: NPCs that intersect are pushed to the edge. World wrapping repositions NPCs to the opposite edge when they leave the play area.
PhaseManager tracks capture counts, sets spawn thresholds, and creates phase-specific entities using createSpecialEntity(). The Game class only calls onCaptureOccurred() and queries the current phase. All transition logic, thresholds, and world changes are contained in PhaseManager - the game loop never touches them directly.
Two SFML Views: m_gameView follows the game world with NPCs, SpecialEntities, and backgrounds; m_defaultView is fixed to window resolution for the viewfinder, captions, and UI. This separation prevents HUD elements from drifting with the view. The photo zoom sequence uses sf::View to zoom toward the captured position and return, without affecting NPC logic.
Player::startCapture() triggers on left-click: flash overlay to white, shutter sound, game state transitions to Capturing. checkNPCsInCamera() tests every NPC bounds against the viewfinder area via AABB. Intersecting NPCs get triggerHighlight(). checkSpecialEntityInCamera() marks a SpecialEntity for removal if it was in frame, advancing the phase.
The Animation class manages sprite sheet playback. Each NPC has a 3-frame horizontal sprite sheet. Frame advances when a timer exceeds 0.1s. getCurrentFrame() returns the sf::IntRect for the current sprite. Visual states (default, hat, scared, angry, screaming) are applied by reloading the texture and reinitialising the Animation object.