Back
C++ FOR ENGINES
95% · First Class

What is C++ for Engines?

C++ for Engines gameplay // UE5 C++ — in-engine gameplay screenshot

C++ for Engines is a Level 5 solo module project (GDEV50010) at Staffordshire University, built entirely from a blank Unreal Engine 5 C++ project. No starter content, no framework to borrow from — just a blank slate and the task of constructing a fully working third-person action prototype, architectured around SOLID principles, Anonymous Modular Design (AMD), and a suite of industry-standard design patterns.

The prototype is a fast-paced, ability-driven action game inspired by X-Men Legends II: Rise of Apocalypse — mana-based spell casting, environmental destruction, and flight-style movement bursts — combined with a Giant Ball hazard enemy borrowed from Elden Ring's Silver Sphere. The player wields five distinct spells (Fire, Ice, Electric, Ice Spikes, Lightning Strike), engages Rotating Detection Turrets with a full state machine, and progresses through a world with Invisible Magic Path tiles, breakable objects, collectibles, and checkpoints.

Version control was maintained via a self-hosted Perforce depot (P4V, VPN over double-NAT on a personal PC) — a deliberate choice to mirror industry pipeline practice. All 146 C++ source files were written across 52 folders, covering audio, animation, components, enemies, gameplay, input, interactables, managers, Niagara VFX, player, save/load, UI, and a secondary TwinStick prototype mode.

  • Awarded 95% — First Class for GDEV50010 C++ for Engines
  • 146 C++ source files across 52 folders, built from a blank UE5 project
  • 18 major systems built: AMD GameplayEvents bus, AttributesComponent, Audio, Niagara VFX, Save/Load with checkpoints, HUD, Inventory, Spells, Turrets, and more
  • Five spell types: Fire, Ice, Electric, Ice Spikes, and Lightning Strike — all with mana economy and Niagara effects
  • Self-hosted Perforce P4V depot with VPN over double-NAT for source control
  • SOLID principles and six design patterns applied: Observer, Singleton, State, Facade, Template, and Flyweight
  • Anonymous Modular Design (AMD) throughout — no direct class dependencies, all communication via GameplayEvents bus
  • Fully event-driven HUD with shake feedback, chunk animations, and smooth bar interpolation — zero Tick dependency

Anonymous Modular Design

The entire project is built around Anonymous Modular Design (AMD) — a pattern where no class holds a direct dependency on another. All inter-system communication flows through a centralised UGameplayEvents bus (a UWorldSubsystem), which acts as the sole message router for the game. This eliminates spaghetti coupling and means any system can be added, removed, or replaced without touching anything else.

// Example: Spell fires mana event — HUD, AudioSystem, and RegenLogic // all respond WITHOUT the spell knowing they exist. Events->BroadcastSpellCastRequest(OwnerActor, ManaCost, bApproved); Events->OnManaUseComplete().AddUObject(this, &UAttributesComponent::OnManaUseCompleteRequest); // → UI updates mana bar → AudioSystem plays SFX → Regen restarts

This approach means a Spell does not know the UI exists. A Breakable does not know the AudioSystem exists. The GiantBall does not know the camera shake system exists. Everything communicates anonymously through events.

Key architectural decisions:

  • All gameplay state changes route through UGameplayEvents — no Tick polling, no hard casts
  • UAttributesComponent is the single authoritative source for Health, Stamina, and Mana — no stat can be modified except through it
  • Blueprint subclasses handle presentation only (Niagara, audio, UI polish) — all core logic stays in C++
  • Every system exposes UPROPERTY(EditAnywhere, BlueprintReadWrite) parameters so designers can tune without touching code
  • Contingency-driven development: each post has an explicit "what I'll do if inheritance fails" plan, documented in the forum thread

18 C++ Systems

UAttributesComponent
Authoritative stat store for Health, Stamina, Mana. Broadcasts OnHealthChanged, OnStaminaChanged, OnManaChanged delegates. Integrates damage pipeline, CombatText registration, and full save/load serialisation.
APlayerCharacter_Base
Main player class. Implements IA_Interface, PlayerCharacter_Interface, Anim_Interface. Handles movement, sprint, crouch, flight state machine (Grounded/Flying/Landing), spell casting, and component ownership.
UPlayerHUDWidget
Fully event-driven HUD. Three modular bar states (FHUDBarState), shake feedback (FHUDShakeState), designer-tunable config (FHUDEffectsConfig). Smooth lerp animations via Timers — zero Tick dependency.
ATPGameMode
Third-person match rules: countdown timer, respawn from checkpoint, game-over delegation. Listens to OnPlayerRespawn. Manages turret world managers and enemy tracking.
UAudioSystem
WorldSubsystem-level audio manager. Loads DA_AudioRegistryMaster on init, maps EAudioEvent enums to sound assets. Responds to OnPlayAudioEvent — zero hard references to any gameplay class.
UNiagaraSubsystem
WorldSubsystem VFX manager with an object-pooled Niagara component pool. Supports PlayVFXEvent (world-space) and PlayVFXEventAttached (socket-relative). Pool auto-expands on demand.
ASpellBase
Template-pattern spell base class. SpellCast_Implementation checks mana via AttributesComponent, broadcasts approval via GameplayEvents, consumes mana, plays cast FX, invokes OnSpellExecuted on child class. Child spells: Fire, Ice, Electric, IceSpikes, LightningStrike.
ATurretBase
State-pattern turret: Disabled → Tracking → Firing. Has BaseMesh, BarrelMesh, FirePoint, and AttributesComponent. Integrates TurretWorldManager, EnemyTrackerComponent, AudioSystem, and NiagaraSubsystem on fire events.
ARotatingDetectionTurret
Turret subtype with a rotating detection cone, proximity-based aggro threshold, and burst-fire timing. Inherits full TurretBase pipeline — overrides detection logic only.
AGiantBallHazard
Elden Ring-inspired Silver Sphere. Spline-following patrol, proximity-triggered charge mode, collision reset. State machine: Patrol → WindUp → Charge → Recover. Broadcasts OnPlayerDetected / OnChargeImpact.
Save / Load System
ISaveableInterface on any actor. WriteSaveData / ReadSaveData via FMemoryWriter/Reader (binary .sav). SaveSubsystem WorldSubsystem serialises full attribute state, inventory, and checkpoint position across sessions.
UInventoryComponent
Manages spell unlocks, collectibles (Coin, Gem, Health, Mana, Stamina pickups), and currency. All pickups inherit ACollectableBase with a UCollectableDataAsset for data-driven item definitions.
UCombatTextSubsystem
WorldSubsystem that spawns ACombatTextActor in 3D world-space on damage/heal events. Registers attribute components on BeginPlay, listens to stat change delegates to drive floating number animation.
UVoiceOverComponent
12,947-byte voice-over manager. Queues VO lines with cooldowns based on gameplay events (low mana, damage taken, kill, spell cast). Integrates with AudioSystem and GameplayEvents for fully decoupled voice feedback.
UDebugHelper
Static UBlueprintFunctionLibrary utility — Singleton-like access without global state. Prints actor name, event tag, message, and severity to screen and log. Stripped in non-editor builds via WITH_EDITOR guards.
MagicPath System
Invisible platform tiles (UMagicPathTile) revealed when the player steps near them. Implements IMagicPathInterface. Creates environmental puzzles where the path is hidden until discovered at runtime.
UBreakableObject_Base
Health-based environmental destruction. On death threshold, broadcasts OnObjectDestroyed → VFX (Niagara) + SFX (AudioSystem) + item drops (InventoryComponent). Shares AttributesComponent for health management.
TwinStick Prototype
Separate game mode — ATwinStickPawn, ATwinStickGameMode, ATwinStickController. The original Dead Ops Arcade-style concept before pivoting to third-person. Retained as a secondary prototype alongside the main TP framework.

Six Patterns Applied

Each pattern was documented in the forum thread with explicit C++ implementations, designer-friendly exposure, and contingency plans for when the pattern was not appropriate:

Observer
Multi-cast delegates on UAttributesComponent and UGameplayEvents. UI, audio, and gameplay systems all subscribe — no system polls another directly.
Singleton / Facade
WorldSubsystems (UAudioSystem, UNiagaraSubsystem, UCombatTextSubsystem) provide globally accessible services without global state or singleton pitfalls.
State
ATurretBase: Disabled → Tracking → Firing. AGiantBallHazard: Patrol → WindUp → Charge → Recover. APlayerCharacter_Base: EFlightState Grounded / Flying / Landing.
Template
ASpellBase defines the cast pipeline (mana check → approve → consume → VFX → execute). Child spells only override OnSpellExecuted — the sequence is fixed.
Flyweight
Audio assets reused across all turret types via shared DA_AudioRegistry. Niagara systems referenced once in UNiagaraSubsystem — every actor shares the same particle asset, only instance transforms differ.
Type Object
UCollectableDataAsset and UVFXEventDataAsset drive item and VFX behaviour via data rather than code. Designers tune pickups and effects in-editor without touching C++.

Inspirations & Mechanics

Gameplay mechanics // Gameplay — mechanics in action
⚡ X-Men Legends II: Rise of Apocalypse
The primary inspiration for the combat feel. Flight bursts, mana-driven spellcasting, environmental destruction, and dynamic voice-over feedback. The project aims to replicate the audiovisual power fantasy — spells feel loud, reactive, and satisfying even in a simple test environment.
🔮 Spell Combat System
Five distinct spells unlock across the run: Fire, Ice, Electric, Ice Spikes, and Lightning Strike. Each consumes mana via the centralised AttributesComponent. The HUD animates in real time — bar chunks, shake intensity scaled to mana cost, smooth interpolation back to full on regen. Casting with insufficient mana fires OnLowManaAttempt, triggering distinct audio feedback.
⚫ Giant Ball Hazard (Elden Ring)
Inspired by the Elden Ring Silver Sphere — a comedic, disruptive patrol enemy that pauses when the player is detected, then charges. Spline-following patrol, proximity trigger, rush mode, collision reset. Deliberately a "set-piece" enemy that breaks combat pacing. Iterating from Blueprint revealed: sphere half in ground (fixed by Z offset), unintentional jumpscare delay (fixed in patrol/charge transition logic), and overshoot not resetting (fixed via collision-triggered state reset).
🏰 World Features
Invisible Magic Path tiles revealed at runtime. Rotating Detection Turrets with cone-of-sight aggro. Breakable environmental objects that drop collectibles. Full checkpoint save/load system persisting player stats and inventory across sessions. Camera scroll-wheel zoom bound via Enhanced Input.

SOLID Principles Applied

  • SRP (Single Responsibility) — UAttributesComponent handles only stats. UAudioSystem handles only audio. UNiagaraSubsystem handles only VFX. No class does more than its domain.
  • OCP (Open/Closed) — ASpellBase is open for extension (new spell types via child classes), closed for modification (cast pipeline never changes). UTurretBase can gain new states without altering Disabled/Tracking/Firing logic.
  • LSP (Liskov Substitution) — Any ASpellBase child can replace the base in the player's spell socket without the player character knowing the specific type.
  • ISP (Interface Segregation) — IIA_Interface, IPlayerCharacter_Interface, IPlayerCharacter_Anim_Interface, ISaveableInterface, IInteractable, IMagicPathInterface — each covers one narrow contract. No bloated monolithic interfaces.
  • DIP (Dependency Inversion) — The player depends on UAttributesComponent abstraction, not concrete stat variables. Spells depend on the IIA_Interface abstraction, not on the player class directly.

Self-Hosted Perforce

Rather than using Git, the project was managed with Perforce P4V — the VCS standard across AAA game studios. A personal Perforce server was self-hosted on a home PC with a VPN over double-NAT to create a secure remote depot. The depot was named CPPforEngines, workspace NomNom_CPPforEngines_WS, with a .p4ignore file configured to exclude UE5 intermediate build artefacts.

This was a deliberate engineering decision — the forum thread explicitly frames it as industry practice preparation, not just convenience. Self-hosting the server rather than using a service like Helix Teams mirrors the studio environment where teams manage their own P4 infrastructure.

Blueprint-first prototyping rule Every mechanic was designed in Blueprint first to stabilise logic, fix bugs, and verify behaviour before migrating to C++. This reduced the number of compile-crash cycles and ensured the C++ implementation was based on proven logic rather than speculative architecture.

What Went Wrong

Roll / animation breakage — The original skeletal mesh had an extra bone absent from standard UE rigs, making animation retargeting impossible. The roll mechanic became a speedrunner glitch rather than a dodge. Ultimately cut.

HUD desyncs — Consecutive spell casts caused mana bar desync when events fired faster than the lerp completed. Fixed by always lerping toward the latest authoritative target value from AttributesComponent, not a cached local value.

Giant Ball overshoot — The Silver Sphere would continue charging past the player after collision. The charge state did not terminate on collision because the state machine transitioned only on a timer, not on an overlap event. Fixed by binding the reset to an OnCollision event that forcibly sets state to Recover.

HUD shake drift — Applying screen-space shake offsets without resetting the transform first caused incremental drift. Fixed by resetting to BaseTranslation before each shake tick.

Chunks not spawning — HUD chunk widgets were children of a Canvas Panel; grabbing them required the parent canvas reference, not the widget directly. A subtle UMG binding quirk that took considerable time to diagnose.

Scope lesson Documented contingency plans were written into every forum post — "if inheritance fails, fall back to UActorComponents", "if the Blueprint graph becomes too infested, migrate logic to C++", "if scope grows beyond timeframe, cut to core: Player, Enemy, GameMode, UI, Feedback". The habit of writing contingency before they were needed saved the project from spiralling.