UGTK / Toolkit / Gameplay Systems / Platform 2D Framework Completed
Platform 2D Framework
Summary
The Platform 2D Framework is a ready-to-use set of prefabs, scripts, a central player-state
manager, an interaction layer (switches, doors, keys, crumbling platforms) and three playable levels
for building 2D platformer games on a Unity Canvas with no custom boilerplate. It covers the full
gameplay loop: a controllable player with score/coins/lives/hearts/status tracking, static and moving
platforms, collectible coins, checkpoints, cannons and enemies with configurable vision cones, water,
status power-ups, puzzles that open walls, and a HUD with pause/options screens — all wired through
typed UnityEvents that any UI or game-logic script can listen to without coupling.
The framework is the UGTK port of the Arcadevania game: it behaves exactly like that game while
being built on UGTK systems (Mn_MovementComponent, Mn_2DJumpComponent, KeyInputActionSystem,
Mn_HealthComponent, CheckpointSystem, AudioSystem, ...). The art is Kenney Pixel Platformer
(CC0) — see ART_STYLE.md.
Content
Modules Dependencies
Runtime code (UGTK.Framework.Platform2D assembly):
- Generic Component and
Movement Component:
Mn_P2dTagBlocker2DMovementis anMn_MovementComponent, so speed, direction, pause and the start/stop events come from there. - Singleton:
Mn_P2dPlayerManageris anMn_Singleton.
The scenes and the Arcadevania/ prefabs also use, as plain scene content:
ColliderAutoResizeComponent, JumpComponent, KeyInputActionSystem, DamageSystem
(Mn_HealthComponent, Mn_HealthLivesUI, Mn_CollisionDamage2DComponent), ShootingSystem,
FollowSystem, CheckpointSystem, AudioSystem, PoolingSystem, TimingSystem,
TextUpdaterSystem, TextTyperSystem, SceneSystem (Mn_OpenScene), CollisionSystem,
WindowsDeviceManagement. Import the whole toolkit, or at least those modules, before opening the scenes.
Setup
The quickest start is to duplicate Scenes/Platform2D_Debug.unity and edit the level: every
piece of it is a real prefab or a plain RectTransform with a BoxCollider2D and a
ColliderAutoResizeComponent child that keeps the collider the size of the rect.
The second way is to write a level as text and compile it (see Level compiler below): a
32×18-cell screen grid where # is a wall, = a floor, C a coin, K a checkpoint, v/^ a
cannon, W water, X a kill zone, plus JSON lists for lifts, doors, switches and keys.
To build a level by hand instead:
- Add
P2d_Playerprefab to your scene (under a Canvas):
- Add
P2d_Platformprefab to your scene:
- Add
P2d_MovingPlatformprefab to your scene:
- Add
P2d_Coinprefab to your scene:
- Add
P2d_Enemyprefab to your scene:
- Add
P2d_Checkpointprefab to your scene:
- Add
P2d_PowerUpStatusprefab to your scene:
The game art is in Arcadevania/: animated coins (Prefabs/Coins), cannons
(Prefabs/Enemies/Cannons), the three power-ups, projectiles, the checkpoint flag, the player
sprites and animators, the tileset, the sky background, the sounds and the single Kenney tiles used
by the compiler (Art/Kenney). Everything in there is wired to the framework scripts.
Colliders under a Canvas. A
BoxCollider2Don a UI element does not follow theRectTransformsize: withoutColliderAutoResizeComponent(a child object, owner = the parent) a 1920×40 ground has a 1×1 collider and the player falls through it. Every framework prefab and every wall of the scenes carries that child; keep it when you build your own.
How To Use
-
Player state — all stats (score, coins, lives, hearts, status) live in a
SO_P2dPlayerDataasset owned byMn_P2dPlayerManager(a scene singleton). CallAddScore,AddCoins,AddLives,RemoveLife,AddHearts,RemoveHeart,SetStatus,ResetToDefaultsat runtime; wireonScoreChanged,onCoinsChanged,onLivesChanged,onLifeGained,onHeartsChanged,onHeartGained,onStatusChanged,onStatusNormal,onStatusFire,onStatusBecameNormalFromOther,onGameOverin the Inspector (or through theOnXxxproperties from code). The asset keeps its runtime values across scene reloads, which is how lives survive a death that reloads the level; with Reset To Defaults When No Lives (default on) a reload after a game over starts a fresh run. -
Movement — the player root has a dynamic
Rigidbody2D; the childMovementComponentcarriesMn_P2dTagBlocker2DMovement.KeyInputActionSystemchildren callSetSpeed(±15)on it,Mn_2DJumpComponenthandles the jump (TriggerJump). Colliders tagged with one of the Blocked Tags (defaultWall) stop the horizontal move; the Input Gate methods (DisableHorizontalInputIfBlocked, ...) can be wired to collision events. Measured reach: a standing jump climbs 2.5 cells, a running jump clears 5 cells (P2dJumpMetricsTestslogs it). -
Interaction layer (
Scripts/Runtime/Interaction): Mn_P2dSwitch— a trigger the player touches;onActivated(one-shot, or toggling withoneShotoff) tints its Graphic and fires whatever you wire: typicallyMn_P2dDoor.Open.Mn_P2dDoor— a wall (tagWall) that slides byopenOffsetindurationseconds (Open,Close,Toggle,SetOpenInstant, optionalautoCloseAfter).Mn_P2dKey— a collectible that feeds anMn_P2dCounter; wire the counter'sonCounterReachedto a door for "collect all the keys" puzzles.-
Mn_P2dCrumblingPlatform— collapsescrumbleDelayseconds after the player stands on it and comes back afterrespawnDelay(shakes while crumbling). -
Moving platform —
Platforms/Platformin the scenes moves betweenPlatform_A_PositionandPlatform_B_Positionwith the UGTK follow/timer components.Mn_P2dMovingPlatformis the self-contained alternative: setpointA/pointB,speedandpauseAtEachEnd. -
Collectibles —
Mn_P2dCoin.NotifyCollected()addscoinValuethrough the player manager and firesonCollected; the coin prefabs call it fromMn_P2dCollider2DEventsTagFilter. ExtendMn_P2dCollectiblefor custom collectibles. -
Power-ups —
Mn_P2dPowerUpStatus,Mn_P2dPowerUpHeart,Mn_P2dPowerUpLifeextendMn_P2dPowerUp: callApply()— or the UnityEvent-friendlyApplyStatusToPlayer(),AddHeartToPlayer(),AddLifeToPlayer()— from a trigger;onAppliedfires after the effect. -
Checkpoints — the flag prefab uses the UGTK
CheckpointSystem:Mn_Checkpoint.SetAsLastCheckPoint()on trigger,Mn_CheckpointManager(with aSO_LastCheckpointDataasset) teleports the player back on start. The saved checkpoint is scoped to its scene: another level never inherits it, andOnLevelCompleted()clears it. -
Camera — the Arcadevania camera is a child of the player (
RightMain Camera) and carriesMn_P2dCameraSideParentSwitcher: give it twoRectTransformmarkers and it follows the player only while the player is between them.Mn_P2dCameraBoundsFollowis the alternative for a camera outside the player hierarchy (normalized viewport bounds, resolution-independent). -
Enemy vision — configure
Mn_P2dVisionCone2Dwithradius, half-angleangle, and ray countrayCount. The cone mesh andPolygonCollider2Dupdate every frame including in Edit Mode. -
Input bridges —
Mn_P2dMoveAnimatorInputBridgefires Animator triggersmove_on/move_offon horizontal-input state changes.Mn_P2dControllerTriggerFinderexposes gamepad L2/R2 Down/Held/Up as UnityEvents (New Input System and Legacy Input Manager). -
Tag-filtered collision events —
Mn_P2dCollider2DEventsTagFilterroutesOnCollisionEnter2D/OnTriggerEnter2D(and Stay/Exit) to UnityEvents only for the listed tags. -
HUD / UI helpers —
Mn_P2dForceUiOnTopkeeps the HUD above the level (override sorting + order in layer),Mn_P2dUiInstantYFlipmirrors the player sprite (SetY0/SetY180),Mn_P2dGraphicVisibilityshows/hides a Graphic by alpha.
Level compiler. A level is a JSON file of ASCII zones (see the legend in Setup and the full brief
in FantasyRollDice/_tools/platform2d/LEVEL_DESIGN_BRIEF.md). p2d_zones_assemble.py validates
and chains the zones, p2d_level_compiler.py turns them into a .unity scene: walls and floors
become tiled Images with colliders, C/K/v/^/F/H/L become the Arcadevania prefabs,
W/X/E copy the water, kill-zone and end-level templates, lifts copy the A→B platform, doors,
switches, keys and crumbling floors become the interaction components already wired together. The
generated scene keeps the Player, HUD, audio and camera of the Debug scene. Both tools live outside
the package (they are authoring tools, not runtime code).
Testing Scene
Three scenes in Scenes/:
Platform2D_Debug.unity— the A → B example. The first section of the Arcadevania level: ground, the water pit with its coins, two cannons above and two below, a checkpoint, the life and fire power-ups, the moving lift betweenPlatform_A_PositionandPlatform_B_Position, and theEndLeveltrigger on the right (it loads the Arcadevania scene), closed by a wall. Press Play, walk with A/D or the arrows, jump with Space, shoot with the fire status.Platform2D_Arcadevania.unity— the whole Arcadevania level, every element: 42 walls and surfaces, 39 coins and coin groups, 6 cannons, 4 water areas, 3 checkpoints, 3 power-ups, the moving platform, the HUD with options, audio and tutorial screens, the lose screen.Platform2D_Odyssey.unity— the long level, 52 screens compiled from 16 designed zones, about half an hour of play with every mechanic of the framework:- Act I, the meadow: Meadow Steps (walking and first coins), Coin Hollow (gaps over spikes), Watchtower Road (first cannon and first lift), The Gatehouse (first switch-door).
- Act II, the keep: The Rotten Bridge (crumbling bridges over pits), The Cistern (a flooded cistern with a sunken switch), Bell Tower (a vertical climb on two lifts), Twin Keys (two keys for one locked door).
- Act III, the chasms: Chasm of Chains (long lifts over deadly chasms), The Twin Sentinels (two switches on two branches open twin doors), The Ashen Gallery (timed crumbling bridges under cannon fire), The Drowned Vaults (a flooded maze hiding three keys).
- Act IV, the finale: Cinder Gauntlet (lifts, crumbling bridges, water and cannons), Vault of Four Seals (three switch-doors, then a four-key hunt), The Drowned Well (a descent and a climb back on lifts), The Last Corridor (everything at once, then the exit).
The long level, its edge tiles and the layered background:
A switch that opens a door, on the way through the keep:
The tile theme changing from grass to stone as the level moves from the meadow into the keep:
Art and background. Every tile comes from one collection, the Kenney Pixel Platformer set (CC0), so the
level always looks drawn by one hand: each zone declares a theme (grass, stone) that picks the fill of the
solid blocks and the tile that caps them wherever they are open to the sky, and the level compiler emits those
edge tiles on its own. The backdrop is three layers built locally from the same collection by
_tools/platform2d/make_background.py: a sky gradient, a band of distant hills and a band of clouds and
trees, the last two carrying Mn_P2dParallaxLayer so they slide behind the level as the camera moves.
Fills use textures imported with Wrap = Repeat and Mesh Type = Full Rect: a tiled Image on such a sprite is
one quad with tiled UVs, so a block of any size costs the same and Unity never reports "Too many sprite
tiles". A sprite taken from a multiple-sprite sheet cannot do that, and the compiler warns when one is used
for a large fill.
Performance of long levels. A Screen-Space Canvas has no frustum culling: without help every Image of
every screen is laid out and batched each frame, and a 50-screen level stutters. Mn_P2dChunkCuller (on the
Canvas of each level scene, added automatically by the level compiler) slices the level parents into chunks
one screen wide and keeps only the chunks around the camera active, so the cost stays constant however long
the level is. On Odyssey it takes the active objects from about 2800 to about 130.
Tests/PlayMode/P2dAutopilot is the playtest driver: it runs the real player with the real components, jumps
at the edge of gaps, waits for lifts, rides moving platforms and turns back when it stops making progress. It
keeps a decision trace so a failed run says why the player died.
Tests/PlayMode/P2dScenesPlayModeTests opens the Debug and Arcadevania scenes in Play Mode and fails
if a script is missing, if the player collider was not resized, if the player is still falling after
2.5 s, or if pushing the movement component to the right does not move it (reaching the EndLevel
counts as a pass). P2dJumpMetricsTests measures the jump and fails when it shrinks below the level
rules. Run them from the Test Runner (PlayMode) or with
-runTests -testPlatform PlayMode -testFilter UGTK.Framework.Platform2D.Tests.
The Editor stops ticking Play Mode while it is not the foreground application: the tests set
Application.runInBackground = truethemselves; do the same in any automation that drives Play Mode.
Technical Info
Scripts:
Player
- SO_P2dPlayerData: ScriptableObject with the defaults (score, coins, lives 3, hearts 0, status
"Normal") and the runtime values; clamped setters, ResetData, ResetToDefaults. Create via
Assets > Create > UGTK > Framework > Platform 2D > Player Data.
- Mn_P2dPlayerManager: Mn_Singleton that owns the data asset and broadcasts typed UnityEvents
when a value changes (onScoreChanged, onCoinsChanged, onLivesChanged, onLifeGained,
onHeartsChanged, onHeartGained, onStatusChanged, onStatusNormal, onStatusFire,
onStatusBecameNormalFromOther, onGameOver); optional reset on Awake / on game over.
Collectibles
- Mn_P2dCollectible: Abstract base; NotifyCollected() fires onCollected.
- Mn_P2dCoin: Adds coinValue to the player through Mn_P2dPlayerManager.AddCoins.
Power-Ups
- Mn_P2dPowerUp: Abstract base; Apply() calls ApplyEffect() then fires onApplied.
- Mn_P2dPowerUpStatus: Sets a status string (default "Fire"); ApplyStatusToPlayer() alias.
- Mn_P2dPowerUpHeart: Adds hearts; AddHeartToPlayer() alias.
- Mn_P2dPowerUpLife: Adds lives; AddLifeToPlayer() alias.
Interaction
- Mn_P2dSwitch: Touch-activated trigger with onActivated/onDeactivated, one-shot or toggle, tinted Graphic.
- Mn_P2dDoor: Sliding wall (Open/Close/Toggle/SetOpenInstant, openOffset, duration, autoCloseAfter).
- Mn_P2dKey: Collectible feeding an Mn_P2dCounter; hides itself on pickup.
- Mn_P2dCrumblingPlatform: Platform that collapses after crumbleDelay and returns after respawnDelay.
Movement
- Mn_P2dTagBlocker2DMovement: Mn_MovementComponent for platformers — transform, kinematic or
dynamic Rigidbody2D (X velocity only, Y left to gravity and jump), never pushes into colliders
with a blocked tag, input gate to freeze horizontal input, LastBlocker for diagnostics.
Platform
- Mn_P2dMovingPlatform: Kinematic Rigidbody2D oscillating between two world-space waypoints
with MovePosition; configurable speed and optional pause at each end.
Camera - Mn_P2dCameraSideParentSwitcher: Arcadevania camera rule — follows the player only while it is between two UI markers (screen-space comparison). - Mn_P2dCameraBoundsFollow: Keeps the target inside normalized viewport bounds by moving the camera; resolution-independent, optional smoothing.
Input
- Mn_P2dMoveAnimatorInputBridge: Horizontal axis with hysteresis → Animator triggers
move_on/move_off on many Animators; drops null entries automatically.
- Mn_P2dControllerTriggerFinder: Gamepad L2/R2 Down/Held/Up UnityEvents (New Input System or
Legacy fallback).
Audio - Mn_P2dAudioByRole: Random clip by role (Wolf/Sheep/Hunter) without consecutive repeats.
Enemy
- Mn_P2dCollider2DEventsTagFilter: 2D collision/trigger Enter/Stay/Exit → UnityEvents,
filtered by tag list.
- Mn_P2dVisionCone2D: Live vision-cone mesh + matching PolygonCollider2D; radius, angle,
ray count; [ExecuteAlways].
Utility
- Mn_P2dForceUiOnTop: Nested Canvas with override sorting (orderInLayer), parent sorting
layer copied, optional GraphicRaycaster.
- Mn_P2dUiInstantYFlip: Y rotation 0°/180° of a target RectTransform (SetY0, SetY180,
Toggle, initial flip on Awake).
- Mn_P2dGraphicVisibility: Shows/hides a Graphic by alpha (SetVisible, SetInvisible, Toggle).
- Mn_P2dCounter: Counts increments and fires onCounterReached when the target is met or exceeded.
Editor - Mn_P2dPlayerManager_Editor, Mn_P2dCameraBoundsFollow_Editor, Mn_P2dVisionCone2D_Editor: custom inspectors.
Tests
- P2dScenesPlayModeTests (Tests/PlayMode): both scenes open clean, the player lands and walks.
- P2dJumpMetricsTests: standing jump height and running jump length in cells (logged as [P2dJump]).
Folders: Prefabs/ (framework prefabs), Arcadevania/ (game art, prefabs, animators,
scriptables, sounds, Kenney tiles and the CC0 licence), Scenes/ (Debug = A → B example,
Arcadevania = full level, Odyssey = long level), Art/Sprites/ (white/square/circle base sprites),
ART_STYLE.md (the art rules).
© 2026 Marcello De Bonis. All rights reserved
Depends on 12
Used by 0
- No other module depends on it.
Measured from the repository: code references (asmdef) plus prefab and asset GUIDs. Importing this module alone brings in 26 modules in total.
UGTKengine within an engine

