Back
MECHANICS DESIGN
89% · First Class

What is Mechanics Design?

Mechanics design gameplay // Early gameplay — forgiving mechanic showcase

Mechanics Design is a Level 5 module at Staffordshire University (GDEV50024 — Introduction to Mechanics Design), split across two assessments that together form one complete project: a 2D platformer built in Unity 6 with C#.

The central question across both halves is the same — how do you make a game feel good to play? Assessment 1 answers it through forgiving mechanics: systems that catch human error without removing challenge. Assessment 2 answers it through game juice and game feel: responsiveness, viscerality, and the tactile feedback layer that elevates interaction above input.

The game has a deliberate personality. A death-driven corruption system floods the screen with brutal taunt text as the player dies more — "WHY ARE YOU STILL TRYING", "YOU KNOW THIS ENDS BADLY", "YOU'RE WORTHLESS" — implemented through a real event-driven architecture in Unity UI Toolkit. Version control used a self-hosted Perforce P4V depot on a personal PC running 24/7 via client VPN.

  • Awarded 89% — First Class for the module
  • Custom partial-class CharacterMovement architecture across 8 C# files — Handlers, Coroutines, Contacts, Debug
  • Seven distinct forgiving mechanics implemented and tuned via ScriptableObject data assets
  • Full DualSense / DualShock lightbar colour system — event-driven state machine with idle pulse animation
  • Velocity-scaled controller rumble — continuous fall rumble, charged jump escalation, per-event presets
  • Death Corruption System — 4 subsystems (lighting, VFX, clouds, taunting prompts) driven by death count
  • In-game Tutorial Panels built in Unity UI Toolkit with embedded screenshots/video
  • Custom DebugLogger.cs with rich colour output, severity levels, and tag filtering
  • Highlight recording system wired through Unity Recorder — triggered in-editor via gameplay event
Assessment 1
Forgiving Mechanics
Sep – Oct 2025 · GDEV50024 · Unity 6 · C#

The brief: implement a suite of forgiving platformer mechanics — systems that compensate for human inaccuracy without removing difficulty. These are mechanics the player never consciously notices, but feels immediately if they are absent.

Jump Buffering
Jump input queued for a short window before landing. If the player presses jump slightly too early, the buffer fires on the next grounded frame. Implemented via ForgivenessTimer and the ForgivenessSettings ScriptableObject.
Coyote Time
Player retains the ability to jump for a short window after walking off a ledge. The m_coyoteTimer resets on each grounded frame and counts down independently of the physics state.
Wall Jump
Full wall-slide and wall-jump system. Includes a regrab block window (m_wallRegrabBlockTime), smooth acceleration override during wall-jump arc, and velocity damping when re-colliding with a wall mid-jump.
Double Jump
Configurable air jumps via m_airJumps in JumpSettings. A separate multiplier (m_doubleJumpMultiplier) scales the second jump strength independently. Jump count resets cleanly on landing.
Charged Jump
Hold jump after landing to charge — horizontal velocity freezes to x *= 0.1f while charging. Release fires a jump scaled between MinJumpStrength and MaxJumpStrength by charge ratio. Includes a charge coyote window.
Variable Jump Height
Hold-to-extend jump via gravity reduction while rising and jump button held. Early release applies gravityScale * 2.5f blended by velocity ratio, cutting the arc short. Smooth apex hang time at peak.
Sticky Feet
Near ledge edges, downward velocity is zeroed and horizontal drift toward the edge is clamped while the player isn't pressing toward it. Grants a micro window to react before coyote time activates. Disengages instantly on directional input.
Bumped Head Correction
Predictive upward BoxCast each physics tick while rising. If a ceiling corner is detected ahead of the physics resolver, the player is nudged horizontally to clear it — preserving vertical momentum. Prefers movement direction, falls back to opposite.
Step Hop / Step Down
Auto step-up over small ledges (MaxStepHeight) and smooth step-down at platform edges (MaxStepDown). Step-hop uses a lerp coroutine for smooth camera-friendly traversal. Step-down prevents micro-air-time on descending stairs.
Speed Apex Boost
At the top of a jump arc (when vertical velocity falls below a threshold), horizontal air-speed cap is multiplied by m_speedApexMultiplier. Gives the player enhanced directional control at the moment it feels most useful.
Edge Detection — Removed
Initially implemented but conflicted with Coyote Time — both systems competed for control of the same ledge-exit window. Per the Design Plan document: "Edge Detection removed from forgiving mechanics." Sticky Feet was the replacement approach.

Development Changelog

01
Danger Blocks & Game Over System
Collision detection for hazard tiles wired to a game over sequence. First pass of the death response system.
02
Cinemachine, Edge Detection, Hop Jump, Player Death VFX
Switched from a basic camera script to Cinemachine for dynamic follow. Added edge detection (later removed). Implemented bump head correction (hop jump). Player explosion/death particle effect added.
03
Wall Jump, Camera Lens, 2D Freeform Lights
Full wall-slide and wall-jump implementation. Scroll-wheel camera zoom via ZoomCamera.cs. 2D freeform lights added to the level; global lighting toned down to let them read.
04
Perforce Depot Migration
Classic depot structure converted to Streamlined depot for better branching support. Self-hosted Perforce server running on personal PC accessed via VPN client.
05
DebugLogger, CharacterMovement Refactor, Double & Charged Jump, Zoom
DebugLogger.cs — rich coloured console output, severity levels (Info / Warning / Error / Success), tag filtering.
DebugExtension.cs — gizmo drawing helpers (wire spheres, arrows) for runtime forgiveness visualisation.
CharacterMovement.cs fully refactored into partial-class architecture with regions, tooltips, XML doc comments.
Double Jump and Charged Jump implemented. ZoomCamera.cs scroll-wheel zoom finalised.
Assessment 2
Game Juice & Game Feel
Nov 2025 – Jan 2026 · GDEV50024 · Unity 6 · C#

Game Feel = Responsiveness + Intuitiveness + Viscerality. Assessment 2 builds the full feedback layer on top of the forgiving mechanics foundation — every action the player takes should produce a physical, audio, and visual response that makes the interaction feel like it has weight.

Controller Rumble
Per-event rumble presets via RumblePreset ScriptableObjects keyed by RumbleID: Jump, LandLight, LandHeavy, WallJump, StepHop, Death, ChargeStart, ChargeRelease, FallingLoop. Fall rumble scales continuously with velocity using a power curve (m_fallRumbleCurvePower = 2.8f). Charge rumble escalates via AnimationCurve tied to charge ratio.
DualSense Lightbar
Event-driven lightbar state machine for PlayStation controllers (DualShock4, DualSense). States: Idle (blue, gentle sine-wave pulse), Jumping (colour change), Sprinting, Charging Jump, Charged Jump Performed, Game Over. Smooth lerp transitions between all states. Implemented via GamepadHelper.cs using Unity's InputSystem DualShock API.
Death Corruption System
Four coordinated subsystems driven by death count via DeathCorruptionManager: CorruptionLighting (shifts global light colour), CorruptionClouds (tints background sprite renderers red), CorruptionVFX (scales particle intensity), CorruptionPrompts (spawns taunting text via UI Toolkit). Reaches full corruption at 10 deaths by default, shaped by an AnimationCurve.
Taunting Prompt System
Known internally as the "bullying mechanic". Implemented in CorruptionPrompts.cs using Unity UI Toolkit. As corruption rises, random-position labels fade in and out across the screen: "WHY ARE YOU STILL TRYING" / "YOU KNOW THIS ENDS BADLY" / "YOU'RE WORTHLESS" / "YOU'RE NOT GOOD ENOUGH" / "WHAT'S THE POINT IN TRYING IF YOU'RE GOING TO FAIL". Spawn rate, max prompt count, and lifetime all scale with the corruption float.
Player Feedback System
Central event router (PlayerFeedbackSystem.cs) wired to CharacterMovement UnityEvents: OnJump, OnLand (severity-graded rumble), OnWallJump, OnChargeStart, OnChargeRelease, OnStepHop, OnDeath. Coordinates audio, VFX pool, trail controller, and gamepad helper from one component. Entire layer toggleable via GameFeelSettings ScriptableObject.
In-Game Tutorial Panels
Contextual tutorial cards displayed via TutorialManager and TutorialPanelUI. Cards contain embedded screenshots or video clips of the mechanic they describe (e.g. DoubleJump card with an in-game screenshot). UI Toolkit UXML/USS layout. TutorialUIAnim.cs handles expand/collapse animations. Triggered by proximity-based TutorialTrigger and TutorialArrow components.
Shatter Death Effect
ShatterPlayer.cs breaks the player into physics fragments on death via HealthComponent.OnDeath. Death also registers to DeathTracker (increments the global counter that feeds the corruption system), plays a death SFX, fires the gamepad Death rumble preset, and disables the jump trail.
Player Trail Controller
State-driven trail system (PlayerTrailController.cs) with distinct states: Jump, Land, Dash. Trail visual changes per movement event. Jump trail enabled on air, disabled on landing. Charge release activates dash trail state. Coordinated through the PlayerFeedbackSystem.
VFX Pool Manager
Object-pooled VFX system (VFXPoolManager.cs) keyed by string ID. Effects: JumpParticle, LandingDust, ChargeParticle, ChargeBurstParticle. GetVFX / ReturnVFX API with parent reassignment for world-space positioning. Prevents per-frame instantiation overhead.
Checkpoint System
Persistent CheckpointManager singleton (DontDestroyOnLoad). Activates the nearest checkpoint on touch, saves ID to PlayerPrefs, and restores on scene load. PlayerRespawnHandler subscribes to OnRespawnTriggered and re-enables the player at the checkpoint's RespawnPoint transform.
Audio System
AudioManager.cs with a central AudioLibrary ScriptableObject. Events played by ID string: Jump, Land, Death. CharacterVoiceover.cs handles character-specific audio triggers. Wired through PlayerFeedbackSystem — entirely decoupled from movement code.
Moving Platforms
MovingPlatform.cs exposes a DeltaPosition vector consumed each physics tick in CharacterMovement. When grounded on a platform, the player's Rigidbody2D position is offset by the platform's delta — no physics parenting required, no jitter.
Combat — Shooter & Enemies
Projectile system via BaseProjectile / ShooterBase / Shooter chain. Enemies: EnemyBase, EnemyRanged, EnemyFlying. Predictive aiming via PredictiveAim2D. Reinforcement spawner (EnemyReinforcementSpawner) for wave management. Health via IHealth interface + HealthComponent with DamageSource2D.
Highlight Recorder
Editor-only highlight capture system (HighlightRecorder.cs) wrapping the Unity Recorder API. Triggerable in-editor during Play Mode — captures a configurable clip length (default 4s) then stops automatically. Prevents recorder window from stealing focus during gameplay. The highlight recordings from Jan 2026 were captured this way.
Health, Stamina & Energy HUD
Three-bar HUD rendered via Unity UI Toolkit (PlayerHUD.uxml / PlayerHUD.uss). HealthUIController and StaminaUIController update bar fill in response to component events. Damage numbers spawn via UIDamageNumberManager with pooled popup elements. Stamina drains on sprint (coroutine-based), regenerates after a delay.

The Corruption Architecture

The corruption system is driven by a single float — the player's death count mapped to a [0,1] range via an AnimationCurve. DeathCorruptionManager routes this value to four independent subsystems simultaneously:

CorruptionLighting
Shifts the scene's global light colour progressively toward a hot red as corruption rises.
CorruptionClouds
Lerps background cloud SpriteRenderer colours toward a deep red tint (Color(1f, 0.35f, 0.35f)) while darkening by up to 40%.
CorruptionVFX
Scales particle system emission intensity, size, and lifetime with the corruption value.
CorruptionPrompts
Spawns and fades taunting text labels at random screen positions. Spawn rate, lifetime, and maximum active count all grow linearly with corruption.
// Corruption prompt visualiser — live preview

The comment in the source code reads: "Known as the bullying mechanic".

Lightbar State Machine

Health stamina energy bars // Health, stamina, and energy bars — runtime display

The DualShock / DualSense lightbar is wired to a configurable event-to-colour mapping via Inspector-serialised EventLightbarMapping structs. All transitions lerp smoothly over m_lightbarTransitionDuration (default 0.35s). The idle state adds a gentle sine-wave pulse on top of the base colour.

Idle — blue, sine pulse
Jumping — blue shift
Sprinting — bright blue
Charging Jump — white
Charge Released — yellow flash
Game Over — red
Falling — wind-blue scale

CharacterMovement — Partial Class System

The player controller was refactored from a monolithic MonoBehaviour into a partial-class architecture across eight C# files. Each file owns a distinct responsibility. All handler instances are initialised through a chain-init pattern in Awake(): DetectorHandler → MovementHandler → StepHandler → JumpHandler.

The physics loop runs as a coroutine (C_PhysicsLoop) rather than FixedUpdate directly — this gives full control over execution order relative to other subsystems and simplifies coroutine lifecycle management.

CharacterMovement.cs
Core: fields, init, input handling, state machine, physics loop, sprint, helpers
DetectorHandler.cs
Ground / wall detection via raycasts and overlap checks
MovementHandler.cs
Horizontal movement, damping, air control
JumpHandler.cs
All jump types — ground, double, wall, charged, variable
StepHandler.cs
Step-hop and step-down traversal, lerp coroutine
Coroutines.cs
All coroutines: physics loop, variable jump, step cooldown, landing cooldown
Contacts.cs
Contact point callbacks, platform attachment, wall contact events
Debug.cs
Gizmo drawing via DebugExtension, DebugLogger integration, runtime state display

ScriptableObject data assets decouple all numeric parameters from code. Every value the designer might want to tune lives in one of five SOs:

MovementSettings
Move speed, air speed cap, ground/air damping, sprint multiplier, stamina drain & regen
JumpSettings
Jump strength, air jumps, double-jump multiplier, variable jump hold time, charged jump min/max, apex hang time & gravity scale
WallSettings
Wall slide speed cap, wall check distance, wall-jump up/push forces, regrab block time, jump duration
StepSettings
Max step height, probe distance, forward offset, skin width, smooth step duration, max/min step-down
ForgivenessSettings
Jump buffer time, coyote time, ground check radius, sticky feet probe/duration, speed apex threshold & multiplier, bumped head nudge steps & distance

Player States

The player controller runs a six-state machine dispatched inside the coroutine-based physics loop. Each state owns its own movement logic, transition conditions, and feedback hooks:

Idle
Ground movement, step mechanics, buffered jump checks, sticky feet clamp. Transitions to Jumping or Falling.
Jumping
Air movement, apex hang smoothing, speed apex boost, double jump window. Transitions to Falling when velocity drops.
Falling
Wall slide, air control, coyote/buffer jump, double jump from fall. Landing resets all jump state and fires OnLand.
WallJumping
Timed wall-jump arc with smooth velocity lerp. Buffer cleared continuously. Wall collision dampens vertical momentum.
ChargingJump
Freezes horizontal velocity while charge accumulates. Charge coyote window allows charging at ledge edge. Exits to Idle/Falling on release.
VariableJump
Gravity reduction while jump held and rising. Early release spikes gravity for short arc. Smooth apex gravity restore when velocity near zero.

What Needed Solving

The biggest architectural challenge was the conflict between Edge Detection and Coyote Time. Both systems respond to the same moment — the player at a ledge edge — but with contradictory logic. Edge detection tries to keep the player on the platform; coyote time grants a jump window after leaving it. Running both together created situations where the player could neither walk off nor jump correctly. Edge Detection was removed; Sticky Feet was designed as a replacement that only acts as a passive drag, disengaging immediately when the player gives directional input.

Wall jump clipping The initial wall-jump implementation let the player clip back into the wall they jumped off. Fixed by introducing m_wallRegrabBlockTime — a timer that blocks wall detection from firing again for a short window after a wall jump, giving the physics resolver time to separate the capsule.

The coroutine-based physics loop was a deliberate deviation from Unity convention. Using FixedUpdate directly caused ordering issues when subsystems needed to communicate in a defined sequence each tick. Running the loop as a WaitForFixedUpdate coroutine made the execution order explicit and allowed all coroutine lifetimes to be controlled from one place.

Lightbar spam prevention Initial lightbar implementation triggered a lerp transition every frame the movement state was evaluated, causing rapid flicker as states changed multiple times per second. Fixed by caching m_currentEvent and early-returning when the incoming event matches the cached one — preventing redundant lerp starts.