Mechanics Design
A 2D platformer in Unity 6 and C# asking how a game can feel good to play — first through forgiving mechanics, then through game juice: DualSense rumble and lightbar, shatter deaths and a taunting death-corruption system.
- Type
- University Assignment
- Module
- GDEV50024
- Engine
- Unity 6 (2D)
- Language
- C#
- Year
- Sep 2025 – Jan 2026
- Team
- Solo
- Version Control
- Perforce P4V
- Corruption States
- 4
What is Mechanics Design?
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.
// Early gameplay — forgiving mechanic showcase
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.
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 buffer — HUD feedback display
Jump Buffering
ForgivenessTimer and the ForgivenessSettings ScriptableObject.Coyote Time
m_coyoteTimer resets on each grounded frame and
counts down independently of the physics state.Wall Jump
m_wallRegrabBlockTime), smooth acceleration override during
wall-jump arc, and velocity damping when re-colliding with a wall mid-jump.Double Jump
m_airJumps in JumpSettings. A separate multiplier (m_doubleJumpMultiplier)
scales the second jump strength independently. Jump count resets cleanly on landing.Charged Jump
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
gravityScale * 2.5f blended by
velocity ratio, cutting the arc short. Smooth apex hang time at peak.Sticky Feet
Bumped Head Correction
Step Hop / Step Down
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
m_speedApexMultiplier. Gives the player enhanced directional control at the moment it feels most useful.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.
// Red glow platforms — hazard zone
-
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 viaZoomCamera.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.csfully refactored into partial-class architecture with regions, tooltips, XML doc comments.
Double Jump and Charged Jump implemented.ZoomCamera.csscroll-wheel zoom finalised.
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
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
GamepadHelper.cs using Unity's InputSystem DualShock API.Death Corruption System
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
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
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.
// Tutorial — double jump prompt UI
In-Game Tutorial Panels
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
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
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
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
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
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, and energy bars — runtime display
Health, Stamina & Energy HUD
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:
// Full death screen — red corruption overlay
CorruptionLighting
CorruptionClouds
Color(1f, 0.35f, 0.35f)) while darkening by up to 40%.CorruptionVFX
CorruptionPrompts
The comment in the source code reads: "Known as the bullying mechanic".
Lightbar State Machine
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.
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
DetectorHandler.cs
MovementHandler.cs
JumpHandler.cs
StepHandler.cs
Coroutines.cs
Contacts.cs
Debug.cs
ScriptableObject data assets decouple all numeric parameters from code. Every value the designer might want to tune lives in one of five SOs:
| ScriptableObject | Tunable values |
|---|---|
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
Jumping
Falling
WallJumping
ChargingJump
VariableJump
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.
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.
m_currentEvent and early-returning when the
incoming event matches the cached one — preventing redundant lerp starts.