UGTK cat mascotUGTKengine within an engine Request access

UGTK / Toolkit / Gameplay Systems / Platform 2D Framework Completed

Gameplay Systems — UGTK

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):

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:

  1. Add P2d_Player prefab to your scene (under a Canvas):

  1. Add P2d_Platform prefab to your scene:

  1. Add P2d_MovingPlatform prefab to your scene:

  1. Add P2d_Coin prefab to your scene:

  1. Add P2d_Enemy prefab to your scene:

  1. Add P2d_Checkpoint prefab to your scene:

  1. Add P2d_PowerUpStatus prefab 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 BoxCollider2D on a UI element does not follow the RectTransform size: without ColliderAutoResizeComponent (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

  1. Player state — all stats (score, coins, lives, hearts, status) live in a SO_P2dPlayerData asset owned by Mn_P2dPlayerManager (a scene singleton). Call AddScore, AddCoins, AddLives, RemoveLife, AddHearts, RemoveHeart, SetStatus, ResetToDefaults at runtime; wire onScoreChanged, onCoinsChanged, onLivesChanged, onLifeGained, onHeartsChanged, onHeartGained, onStatusChanged, onStatusNormal, onStatusFire, onStatusBecameNormalFromOther, onGameOver in the Inspector (or through the OnXxx properties 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.

  2. Movement — the player root has a dynamic Rigidbody2D; the child MovementComponent carries Mn_P2dTagBlocker2DMovement. KeyInputActionSystem children call SetSpeed(±15) on it, Mn_2DJumpComponent handles the jump (TriggerJump). Colliders tagged with one of the Blocked Tags (default Wall) 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 (P2dJumpMetricsTests logs it).

  3. Interaction layer (Scripts/Runtime/Interaction):

  4. Mn_P2dSwitch — a trigger the player touches; onActivated (one-shot, or toggling with oneShot off) tints its Graphic and fires whatever you wire: typically Mn_P2dDoor.Open.
  5. Mn_P2dDoor — a wall (tag Wall) that slides by openOffset in duration seconds (Open, Close, Toggle, SetOpenInstant, optional autoCloseAfter).
  6. Mn_P2dKey — a collectible that feeds an Mn_P2dCounter; wire the counter's onCounterReached to a door for "collect all the keys" puzzles.
  7. Mn_P2dCrumblingPlatform — collapses crumbleDelay seconds after the player stands on it and comes back after respawnDelay (shakes while crumbling).

  8. Moving platformPlatforms/Platform in the scenes moves between Platform_A_Position and Platform_B_Position with the UGTK follow/timer components. Mn_P2dMovingPlatform is the self-contained alternative: set pointA/pointB, speed and pauseAtEachEnd.

  9. CollectiblesMn_P2dCoin.NotifyCollected() adds coinValue through the player manager and fires onCollected; the coin prefabs call it from Mn_P2dCollider2DEventsTagFilter. Extend Mn_P2dCollectible for custom collectibles.

  10. Power-upsMn_P2dPowerUpStatus, Mn_P2dPowerUpHeart, Mn_P2dPowerUpLife extend Mn_P2dPowerUp: call Apply() — or the UnityEvent-friendly ApplyStatusToPlayer(), AddHeartToPlayer(), AddLifeToPlayer() — from a trigger; onApplied fires after the effect.

  11. Checkpoints — the flag prefab uses the UGTK CheckpointSystem: Mn_Checkpoint.SetAsLastCheckPoint() on trigger, Mn_CheckpointManager (with a SO_LastCheckpointData asset) teleports the player back on start. The saved checkpoint is scoped to its scene: another level never inherits it, and OnLevelCompleted() clears it.

  12. Camera — the Arcadevania camera is a child of the player (RightMain Camera) and carries Mn_P2dCameraSideParentSwitcher: give it two RectTransform markers and it follows the player only while the player is between them. Mn_P2dCameraBoundsFollow is the alternative for a camera outside the player hierarchy (normalized viewport bounds, resolution-independent).

  13. Enemy vision — configure Mn_P2dVisionCone2D with radius, half-angle angle, and ray count rayCount. The cone mesh and PolygonCollider2D update every frame including in Edit Mode.

  14. Input bridgesMn_P2dMoveAnimatorInputBridge fires Animator triggers move_on/move_off on horizontal-input state changes. Mn_P2dControllerTriggerFinder exposes gamepad L2/R2 Down/Held/Up as UnityEvents (New Input System and Legacy Input Manager).

  15. Tag-filtered collision eventsMn_P2dCollider2DEventsTagFilter routes OnCollisionEnter2D/OnTriggerEnter2D (and Stay/Exit) to UnityEvents only for the listed tags.

  16. HUD / UI helpersMn_P2dForceUiOnTop keeps the HUD above the level (override sorting + order in layer), Mn_P2dUiInstantYFlip mirrors the player sprite (SetY0/SetY180), Mn_P2dGraphicVisibility shows/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/:

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 = true themselves; 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

Real dependenciesOpen in the map →

Measured from the repository: code references (asmdef) plus prefab and asset GUIDs. Importing this module alone brings in 26 modules in total.