Back to projects

Gameplay project

Vampire Survivors Like

A C++17 survival-action game built around interface-driven gameplay systems, pooled entity lifecycles, deterministic infinite-map sampling, and versioned snapshot persistence.

Infinite-map gameplay showing the player, a heavy enemy, automatic fire, a power-up, and coordinate-hashed terrain.
Live Release x64 capture: seeded world-coordinate sampling, camera-relative rendering, enemy pressure, and nearest-target automatic fire in infinite mode.

Project overview

Role
Gameplay / Systems Programmer
Language
C++17
Framework
GamesEngineeringBase (Direct3D 11)
Platform
Windows PC
Context
MSc Games Engineering coursework

System architecture

I designed the runtime around explicit ownership and a clear orchestration path, separating process-level composition, application states, per-run coordination, and domain logic.

  • Main is the composition root: it initializes the content path and resources once, constructs each concrete gameplay service, and injects them into GEGameSession and GameManager.
  • GameManager owns the application state machine and save-list flow. GEGameSession owns one run and defines the frame order: player, camera and map, enemies, projectiles, power-ups, then map-to-HUD rendering.
  • MapProvider, PlayerProvider, EnemyProvider, ProjectileProvider, and PowerUpProvider expose narrow capabilities; serializable providers also implement GECodable<State>, so orchestration depends on contracts rather than concrete managers.
  • Concrete managers retain domain ownership for spawning, collisions, drops, and cleanup. GEObjectPool owns reusable slots, while GEEnemyManager keeps a compact active set for the hot update and draw paths.

World, camera, and collision

The same data-driven tile source supports a bounded authored level and an unbounded procedural traversal mode.

  • The level loader parses a 70 × 70 map of 32-pixel tiles from a human-readable text format, including tile dimensions and layer data.
  • Fixed mode clamps player and camera movement to the authored 2,240 × 2,240 world; infinite mode samples tiles from world coordinates through modulo repetition or seed-based hashing.
  • A shared collision query handles circle-circle interactions for characters and projectiles and circle-AABB tests for solid or hazardous terrain.
  • The virtual camera follows the player, respects fixed-map bounds, and participates in snapshots so visual position matches restored world state.

Combat and entity lifecycle

The combat loop combines escalating pressure with bounded, allocation-conscious runtime work.

  • Built a complete 120-second survival loop with four enemy archetypes, time-based spawn escalation, contact and projectile damage, drops, victory, and defeat.
  • Automatic fire finds the nearest live target, while the player-triggered AOE selects the highest-HP enemies within range through a fixed-capacity candidate set.
  • Preallocated pools support up to 2,000 enemy, 1,000 projectile, and 100 power-up slots; inactive objects are reused instead of recreated during combat.
  • Projectiles outside the camera plus a safety margin are deactivated for reuse, preventing off-screen entities from permanently consuming pool capacity.

State persistence

Saving is treated as restoration of gameplay causality, not just player position and health.

  • The versioned binary snapshot records map mode and seed, active chunk and camera, session time, player combat and buff timers, spawn progression, active entities, and subsystem random states.
  • Enemy activity order, contact-damage cooldowns, fire state, and random-generator state are restored so loading does not silently change subsequent combat behavior.
  • The serializer validates a magic header, exact format version, bounded strings, and entity counts before accepting a save.
  • Save slots are written to a temporary file and replaced with a write-through atomic move, reducing the chance of leaving a partially written save.

Evaluation and iteration

The original coursework report documented both the result and the limits of the first implementation; the current develop branch reflects a later engineering pass.

  • The report observed roughly 700 FPS on AC power and 400 FPS on battery on the development machine. These are local observations rather than a controlled cross-hardware benchmark.
  • Frame-rate degradation became visible with several hundred active enemies, identifying broad-phase spatial partitioning as a more valuable next optimization than further micro-tuning.
  • The report also exposed incomplete cooldown restoration and tightly coupled responsibilities. Later revisions expanded the save schema, preserved random and activity state, and narrowed subsystem interfaces.
  • The main project lesson was to balance architectural ambition with delivery: establish clear ownership and testable boundaries, but prioritize a complete playable result before deeper abstraction.
View all projects