Mini Project: Your First 2D Platformer in Unity — Jump, Move, and Collect With Full Source Code

Unity Mini Projects

You will ship a playable, mobile-ready slice that includes movement, jump, touch buttons, camera follow, and coin pickups. This intro gives a working movement core so you can verify physics right away.

Follow the Unity Technologies “2D Beginner Adventure Game” learning path as a structured next step. Devin Curry’s series set the baseline: movement with touch buttons via Canvas UI and EventTrigger. You’ll avoid common beginner traps like HTML copy/paste errors that insert > and NullReferenceException from unassigned fields.

Test edge cases early — jump mashing, collider riding, and leaving platforms — or, as one tester joked, “My wife playing the simple platformer… broke it.” Also watch mobile limits: battery drain, excess draw calls, memory spikes, and varied screen sizes.

Drop this movement script onto your player to confirm Rigidbody2D behavior immediately:

using UnityEngine;
public class SimpleMove : MonoBehaviour
{
public float speed = 6f;
public float jumpForce = 8f;
Rigidbody2D rb;
void Awake() { rb = GetComponent<Rigidbody2D>; }
void Update()
{
float x = Input.GetAxisRaw("Horizontal");
rb.velocity = new Vector2(x * speed, rb.velocity.y);
if (Input.GetButtonDown("Jump")) rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
}
}

You’ll build a compact, testable scene with copy/paste-safe code, follow official Unity guidance, and avoid common early mistakes that break gameplay on phones.

Build a playable controller first: move + jump with Rigidbody2D

Start by making a tight, playable controller so you can test movement and jump before adding polish.

Create the Player GameObject

Make a GameObject with a SpriteRenderer, Rigidbody2D (Body Type = Dynamic), and a BoxCollider2D sized to the sprite. In the Rigidbody2D inspector, check Constraints → Freeze Rotation Z to prevent the player from spinning when colliding or when the camera moves.

Movement approach and FixedUpdate

Set horizontal motion by assigning rb.velocity for crisp, predictable strafing. Use AddForce with ForceMode2D.Impulse for jump so the impulse feels consistent. Run physics updates in FixedUpdate and read input in Update to keep timing stable.

Movement script and key lines

Include a ground-check raycast distance, a LayerMask for ground, and the velocity set vs AddForce lines. Watch for the HTML copy/paste bug: replace any GetComponent(); or the code will not compile.

InspectorSuggested ValueWhy it matters
moveSpeed6Responsive horizontal feel on phones
jumpImpulse8Predictable jump height using impulse
Gravity Scale / Linear DragGravity: 3, Drag: 0–0.2Tune fall speed; keep drag low to avoid sluggish control

Ground-check checklist

  • Ground objects have a Collider2D and are on the Ground layer.
  • Player has Rigidbody2D and BoxCollider2D sized to artwork.
  • LayerMask used by the raycast includes the Ground layer.
  • Raycast origin sits above the floor surface, not inside geometry.

Keep the Rigidbody2D manual and collision callback docs open while wiring. The Rigidbody2D API and OnCollisionEnter2D / OnTriggerEnter2D pages will save time and prevent common setup mistakes like a missing component, wrong mask, or an undersized collider.

Project setup for a clean Unity 2D workflow on mobile

A tidy on-disk layout keeps work moving when you add levels, enemies, or UI. You want a small, repeatable system that reduces time hunting for prefabs and fixes mistakes fast on-device.

Folder layout that won’t turn into a mess after your second script

Create a minimal Assets/_Project tree at the start. Keep art, prefabs, scenes, and scripts in predictable places so teammates and devices see the same structure.

  • Assets/_Project/Art/Sprites
  • Assets/_Project/Prefabs
  • Assets/_Project/Scripts
  • Assets/_Project/Scenes
  • Assets/_Project/UI
  • Assets/_Project/Audio

This layout stops the “where did I put that prefab?” problem. It also makes source-control diffs easier and speeds up iterative testing on phones and tablets.

Import sprites and set Pixels Per Unit for consistent scale across devices

Match the sprite PPU to your tile grid so the character-to-platform ratio stays consistent across aspect ratios. Use camera size and UI anchors to handle different screens.

Set Filter Mode to Point for pixel art and Bilinear otherwise. Apply compression conservatively to avoid memory spikes on low-end Android devices.

SettingSuggested ValueWhy it matters
Pixels Per Unit32 or 64 (match tiles)Keeps sprite scale consistent across devices
Filter ModePoint / BilinearPreserves crisp pixels or smooths textures
CompressionLow/Medium on phonesReduces memory use on constrained hardware
Default Script EditorYour IDE (set in Editor → Preferences)Avoids “works on my machine” compile friction

Keep your Project Settings and Editor preferences aligned across machines. Set the default script editor so double-clicking opens the right IDE and team builds remain reliable. This workflow is tuned for shipping fast to phones, not a desktop-only setup.

Scene basics: platforms, colliders, and a “don’t fall through the floor” checklist

Lay out your scene so collision shapes are predictable before you add art or enemies. A clean blockout saves hours of debugging later and makes raycasts trustworthy.

Quick blockout and collider tips

Duplicate ground tiles instead of scaling wildly. Use sliced sprites for long runs so collider shapes stay consistent. Keep each platform’s collider aligned to the sprite grid.

BoxCollider2D vs EdgeCollider2D

  • BoxCollider2D: cheap, consistent, ideal for tiled floors and rectangles.
  • EdgeCollider2D: good for slopes and irregular outlines but can create snag points if vertices misalign.
  • Choose the simpler collider when possible to avoid unexpected catches and physics glitches.

Layer scheme and raycast hygiene

Use layers: Player, Ground, Collectible. Filter raycasts with a LayerMask so you don’t register ground on coins or UI colliders. This keeps grounded checks reliable for your platformer game.

IssueFixWhy it matters
TunnelingUse Continuous collision or increase fixed timestepPrevents passing through thin platforms at low frame rates
Wall stickApply PhysicsMaterial2D with zero friction to playerStops side contacts from halting horizontal movement
Missing collisionsVerify Z positions and overlapping collidersScene view can lie if objects are offset on Z

Don’t forget to test like a chaos player: sprint off edges, land on corners, and slide along walls. These quick checks reveal most physics problems early in your game build.

Touch input you can ship: on-screen left/right buttons using Canvas + EventTrigger

Create touch input that survives different screen sizes: anchored buttons, a tiny controller object, and clean event wiring.

UI hierarchy and anchoring

Build this exact hierarchy: Canvas (Screen Space – Overlay) → TouchController (empty RectTransform, anchor: bottom stretch) → LeftButton and RightButton (Image components).

Anchor buttons to bottom corners with comfortable padding so thumbs hit them on 16:9 phones, elongated devices, and tablets. The container stretch preserves layout across resolutions.

EventTrigger wiring and movement API

Add an EventTrigger to each button and add Pointer Down and Pointer Up entries. Bind these to public methods on a small controller script that calls SetMove(-1), SetMove(0), or SetMove(1).

Keep UI logic out of physics. Let the controller script expose a clean API so later features like knockback or ice won’t require rewriting your input wiring. This helps when you start new gameplay coding.

Common device issues and fixes

  • Ensure Canvas has a Graphic Raycaster.
  • Include an EventSystem in the scene.
  • Enable Raycast Target on button Images.
  • Remove any full-screen invisible Images that block touches.
ProblemFixWhy
Works in Editor but not on deviceAdd Graphic Raycaster + EventSystemEditor input can differ from touch raycasts on a device
NullReferenceException on EventTrigger bindingAssign player/controller reference in InspectorEventTrigger calls fail when target fields are unassigned
Button doesn’t receive touchesCheck Raycast Target and blocking UIInvisible overlays can intercept pointer events

Camera follow without motion sickness or wasted battery

Make the camera follow feel deliberate: smooth, simple, and predictable for the player. A calm camera preserves focus and reduces motion sickness on mobile devices.

Simple follow with dead zone

Use a lightweight script that interpolates the camera toward the player while honoring a dead zone. The dead zone prevents tiny player movements from causing jitter on small screens.

Keep the math cheap: a single Vector3.Lerp and a precomputed offset per frame avoids allocations and heavy CPU work.

Why parenting the camera is a named pitfall

Parenting the camera to the player looks like a quick fix, but when physics rotation or knockback occurs the camera inherits those transforms. That can spin the view and make the whole experience nauseating fast.

Instead, let the camera follow in script and freeze player rotation (Rigidbody2D → Freeze Rotation Z) so the character stays upright and the view remains stable.

Update frequency and battery note

Run camera visuals in LateUpdate so the view matches the latest physics frame without introducing jitter. Keep physics logic in FixedUpdate to stay deterministic.

  • Avoid per-frame allocations and expensive distance checks.
  • Limit complex smoothing to a single pass; every extra compute increases battery drain.
  • Test on target devices to balance smoothness and power cost.

Collectibles: coins with trigger colliders, UI count, and zero-GC pickups

Design coin pickups so they trigger consistently, update your on-screen score, and reuse objects to avoid frame drops. Keep the prefab simple and predictable so testing takes less time and the player sees instant feedback.

Coin prefab and pickup plan

Make the coin with a SpriteRenderer and a CircleCollider2D set to Is Trigger. Tune a small pickup radius so touches feel fair on phones.

Tag coins “Collectible”. In your script use OnTriggerEnter2D to detect the collision, increment an int counter, and update a UI Text or TMP label only when the value changes. This avoids per-frame string allocations.

Trigger rule checklist and pooling

  • Rule: both objects need Collider2D; at least one must have a Rigidbody2D; coin collider must be Is Trigger.
  • Pool coins: pre-spawn, Disable on pickup, reuse on reset to avoid Instantiate/Destroy spikes and GC hitches on midrange Android devices.
  • Update UI only on pickup to reduce string churn and GC time.
ItemRecommended SetupWhy it matters
Coin prefabSpriteRenderer + CircleCollider2D (Is Trigger)Simple collision and visual clarity
Pickup codeOnTriggerEnter2D, tag check, increment counter, update UIReliable detection and zero-GC updates
PoolingPre-spawn pool, Disable/Enable reusePrevents frame drops from Instantiate/Destroy bursts
Debug checklistLayers/collision matrix, tag spelling, UI ref assignedFixes most “trigger never fires” issues quickly

Animations that won’t throw NullReferenceException

Make animation wiring a non-event: predictable parameters and safe references stop most runtime crashes. Use clear names and update values from your existing checks so the Animator stays in sync with the player state.

Animator parameters and update path

Define a minimal set: float Speed, bool Grounded, and trigger Jump. Set Speed from Mathf.Abs(rb.velocity.x). Set Grounded from your ground-check raycast. Fire Jump when you apply the jump impulse, not on button press alone.

Avoiding the classic “myAnim is null”

  • Common causes: missing Animator on the GameObject, script not attached, or a public field never assigned in the Inspector.
  • Fix pattern: add [RequireComponent(typeof(Animator))], call animator = GetComponent<Animator>() in Awake, and mark external refs [SerializeField] and assign them in Inspector.
  • Debug tip: if the error line doesn’t match the tutorial, search for the null variable name and verify that field at runtime in the Inspector.
ParameterTypeUpdate source
SpeedfloatMathf.Abs(rb.velocity.x) in FixedUpdate
GroundedboolGround-check raycast result
JumptriggerWhen AddForce impulse is applied

Mobile performance pass for a 2D platformer: memory, draw calls, battery

Do a focused performance pass so your game runs smoothly across phones and tablets. Triage memory, draw calls, and CPU hot spots early. Test on a physical device, not just the Editor, because device hardware and thermals matter.

Sprite atlas and texture compression

Pack UI and in-game sprites into atlases to reduce texture swaps and draw calls. Use the engine’s sprite packing tools and an atlas per resolution bucket to keep VRAM low.

Apply compression per atlas. Avoid shipping everything uncompressed. Test on actual Android hardware for visible artifacts and memory use.

Reduce overdraw and UI cost

Large translucent buttons cost fill-rate. Tighten sprite shapes, trim alpha areas, and prefer opaque UI where possible.

Keep the UI hierarchy shallow and disable offscreen canvases. That lowers canvas rebuilds and GPU pixel work.

Physics and update frequency

Run only the raycasts you need. One grounded check is usually enough; avoid five per frame. Lower FixedUpdate if physics precision allows it.

Let static colliders stay static, and ensure dynamic bodies can sleep. Piles of awake rigidbodies will keep the CPU busy and heat the device.

  • Profile builds on device with the Unity Profiler and CPU/GPU charts.
  • Pool frequently spawned objects to avoid Instantiate/Destroy spikes.
  • Cap frame rate if ultra-high FPS gives no gameplay benefit.
SettingRecommended ValueWhy it matters
Sprite AtlasGroup by usage + resolutionReduces texture swaps and lowers draw calls
CompressionMedium (test artifacts)Saves VRAM while avoiding visible banding
FixedUpdate rateDefault → increase only if neededBalances physics accuracy vs CPU cost
UI Alpha UsageMinimize translucent pixelsCuts overdraw and GPU fill work

Device heat checklist: avoid unchecked high frame caps, heavy post-processing, many animators, and continuous allocations. Profile with a build on device; check CPU, GPU, and battery drain. Use your system preferences and Player settings to match target device limits before wide testing so your games behave well in the real world.

Player Settings and Build Settings for Android (US-targeted device reality)

Make a quick test build early to catch SDK, signing, or scene-order issues on a physical device. This saves time and gives real-world information about touch, aspect, and performance.

Build Settings: scenes-in-build and switching platform

Save your scene. Open File > Build Settings, add the scene to Scenes In Build, and make sure scene 0 is the entry point. Switch the platform to Android and do a small test build before adding more content.

Player Settings that affect feel

Set orientation (portrait or landscape) to match your control layout. Handle aspect and safe area by anchoring UI to device edges so notches and gesture bars do not hide buttons.

External Tools and quick triage

In Preferences > External Tools confirm the SDK/NDK/JDK paths. Common build failures start with mismatched API levels or missing SDK components. Change one variable at a time when you troubleshoot.

  • Device test loop: install on a midrange phone, verify touch hitboxes, confirm stable frame times, and check package name/signing before sharing APKs.
SettingRecommendedWhy
OrientationMatch control layoutComfortable play and UI placement
Safe AreaCanvas anchors + RectMaskPrevents hidden buttons on US devices
External ToolsCorrect SDK/JDK pathsPrevents “why won’t it build” errors

Beginner mistakes you’ll hit fast (and how to fix them before they waste your night)

Most nights get wasted by simple wiring mistakes; hunting those first saves hours of frustration. This short guide lists the usual failures and a repeatable fix workflow so you can get back to adding features.

Quick checklist of the usual culprits

  • Script not attached to the Player GameObject.
  • Missing Rigidbody2D or colliders on expected objects.
  • Empty LayerMask for ground checks or wrong layers on objects.
  • UI buttons not wired, EventSystem or Graphic Raycaster missing.
  • Public references left unassigned in the Inspector causing NullReferenceException.

Repeatable fix workflow

Open the Console, click the error, and jump to the exact line. Verify the referenced component exists on that GameObject or is assigned in the Inspector. If a field is null, mark it [SerializeField] or make it public and assign the correct reference in the Editor.

HTML copy/paste trap and device-only failures

If you copied code from a web tutorial and see &gt; in your editor, replace it with > (the real greater-than sign). That artifact causes compile errors that look unrelated if you don’t know to check.

Works in Play but fails on a device? Check for missing EventSystem, GraphicRaycaster, or invisible UI layers blocking touches. Toggle suspect images off to find blockers quickly.

Edge-case tests — “my wife broke it”

Run these before you call the build done: hold direction while landing on a platform edge, mash jump at the lip, slide along a wall, and sprint off-screen to test camera bounds. Fixes mean the player stays responsive, won’t jitter, can’t jump repeatedly while grounded incorrectly, and won’t get stuck inside colliders.

unity 2d platformer mobile beginner mini project extensions you can add next

Focus on practical, low-cost features that expand play without bloating systems. Each extension below maps to a clear design pattern and avoids heavy CPU or GPU work.

Enemy patrol AI that turns at walls and edges

Use a forward raycast to detect walls and a downward raycast to detect missing ground, then flip the sprite and velocity. This mirrors Devin Curry’s Part 4 approach and keeps enemies predictable on phones.

Simple damage, death loop, and checkpoint respawn

Add a health int, a short hurt cooldown, and a checkpoint transform. On death, reset player position and health at the last checkpoint. This follows the Part 5a/5b direction so you build a full loop rather than isolated features.

Multiple levels with a clean scene-loading pattern

Keep one persistent UI scene (scoped managers + audio) and load per-level gameplay scenes additively. Alternatively use a bootstrap scene that loads UI then swaps level scenes to avoid duplicating managers and references.

  • Minimize raycasts per frame; reuse pooled enemy objects to cut Instantiate/Destroy spikes.
  • Profile extra enemies: more sprites and physics checks increase CPU/GPU and battery use over time.
  • Practice step: follow Unity’s “2D Beginner Adventure Game” course to see these patterns applied in a structured tutorial.

Conclusion

Wrap up confidently: you now have a compact, playable game slice with movement, touch controls, camera follow that avoids spin, and clean collectible triggers.

Before you ship, test on a real device. Verify UI anchors across aspect ratios, run a development build to confirm no Console errors, and watch frame time for stability.

Double-check common pitfalls: frozen rotation to avoid camera spin, EventSystem and raycast setup, HTML copy/paste artifacts in code, and null references from unassigned fields. When in doubt, consult Rigidbody2D, Collider2D callbacks, and EventSystem docs for definitive information.

Keep performance basics in place: atlas + compression, reduce UI overdraw, pool frequent spawns, and trim physics checks to protect battery. If you have time, add enemy patrols and checkpoints, then turn this into a reusable template.

Leave a Reply

Your email address will not be published. Required fields are marked *