๐น๏ธ Lesson 10.4: 2D Physics โ A Platformer Base (Mini-Project)
You have sprites, sorting, and a tilemap. Time to make something move. 2D physics is its own world โ a separate engine with its own components. In this mini-project you'll give a sprite a body, drop it onto your tilemap, and build a tight jump with a reliable ground check, driven by the new Input System.
๐ฏ Learning Objectives
By the end of this lesson, you will be able to:
- Use Rigidbody2D and its body types (Dynamic, Kinematic, Static)
- Choose the right Collider2D and tune gravity for platforming feel
- Move and jump a character with the new Input System
- Detect the ground reliably with
Physics2D.OverlapCircle - Assemble a playable platformer base on your tilemap level
Estimated Time: 50 minutes ยท Prerequisite: Lesson 10.3 (Tilemaps) & Module 3 (Input System)
In This Lesson
2D Physics Is a Separate Engine
Unity ships two physics engines: 3D (Rigidbody, Collider, Physics) and 2D (Rigidbody2D, Collider2D, Physics2D). They don't interact โ a 3D collider won't stop a 2D body. Everything in the 2D track uses the 2D variants, and mixing them up is the most common beginner slip here.
โ ๏ธ Everything ends in "2D"
Rigidbody2D, Box/Circle/Capsule Collider 2D, Physics2D.OverlapCircle, OnCollisionEnter2D, OnTriggerEnter2D. If you accidentally add a 3D Collider or subscribe to OnCollisionEnter (no 2D), nothing collides and no callbacks fire.
Rigidbody2D & Body Types
A Rigidbody2D hands an object to the physics simulation. Its Body Type decides how:
- Dynamic โ fully simulated: gravity, forces, collisions. Your player is Dynamic.
- Kinematic โ moved only by script (
MovePosition), not by gravity/forces, but still detects collisions. Good for moving platforms and some controllers. - Static โ never moves; the cheap default for level geometry (a Tilemap Collider is effectively static).
๐ Definition
Gravity Scale: a per-body multiplier on 2D gravity. Platformers almost never feel right at 1 โ a heavier value like 3โ4 gives a snappy, arcade fall instead of a floaty drift.
โ Pro Tip
On the player's Rigidbody2D, set Collision Detection to Continuous and freeze Z rotation (Constraints) so a capsule doesn't tip over when it bumps a wall.
Colliders & Feel
Add a Collider2D so the body actually touches things. A Capsule Collider 2D (vertical) is the platformer favourite โ its rounded bottom slides over tile seams that a Box collider would snag on. Your tilemap already has a Tilemap Collider 2D + Composite Collider 2D from Lesson 10.3, so the ground is ready.
๐ก Physics Materials 2D: assign one withfriction = 0to the player to stop it "sticking" to walls, and tunebouncinessif you want springy surfaces.
Moving & Jumping
Read input with the new Input System (Module 3): a Move Vector2 and a Jump button. Set horizontal velocity directly for crisp control, and jump by setting upward velocity. Do physics writes in FixedUpdate:
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(Rigidbody2D))]
public class PlatformerController2D : MonoBehaviour
{
[SerializeField] float moveSpeed = 8f;
[SerializeField] float jumpForce = 14f;
[Header("Ground Check")]
[SerializeField] Transform groundCheck; // an empty at the feet
[SerializeField] float groundRadius = 0.15f;
[SerializeField] LayerMask groundLayer; // set to your "Ground" layer
Rigidbody2D rb;
float moveInput;
bool jumpQueued;
bool isGrounded;
void Awake() => rb = GetComponent<Rigidbody2D>();
// Hooked up via a PlayerInput component (Invoke Unity Events)
public void OnMove(InputAction.CallbackContext ctx) => moveInput = ctx.ReadValue<Vector2>().x;
public void OnJump(InputAction.CallbackContext ctx) { if (ctx.performed) jumpQueued = true; }
void FixedUpdate()
{
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundRadius, groundLayer);
// horizontal: set velocity directly for responsive control
rb.linearVelocity = new Vector2(moveInput * moveSpeed, rb.linearVelocity.y);
if (jumpQueued && isGrounded)
rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
jumpQueued = false;
}
}
โ ๏ธ Unity 6 renamed velocity
On Rigidbody2D it's now linearVelocity (the old velocity is deprecated). Also note we queue the jump in the callback and consume it in FixedUpdate so a press is never missed between physics steps.
The Ground Check
You only want to jump when standing on something. The robust way is a tiny overlap circle at the character's feet: if it overlaps anything on the Ground layer, you're grounded. Put an empty child called GroundCheck at the feet and reference it.
The decision each physics step is simple:
on Ground layer?"] B -- "Yes" --> C["isGrounded = true"] B -- "No" --> D["isGrounded = false"] C --> E{"Jump pressed?"} E -- "Yes" --> F["velocity.y = jumpForce"] E -- "No" --> G["keep falling"] D --> G
Figure 2: Ground check gating the jump.
๐ก Feel bonus: add a short coyote time (allow a jump for ~0.1s after leaving a ledge) and jump buffering (remember a press for ~0.1s before landing). These two tricks are what make a platformer feel "tight" instead of stiff.
Mini-Project: The Platformer Base
๐๏ธ Put it on the tilemap
Goal: a sprite character that runs and jumps around the tilemap level you painted in Lesson 10.3.
- Put your ground tiles on a Ground layer (the tilemap with the Composite Collider).
- Create the player: a Sprite Renderer + Rigidbody2D (Dynamic, freeze Z rotation, Gravity Scale ~3) + Capsule Collider 2D.
- Add an empty child GroundCheck at the feet.
- Add a PlayerInput component with a Move (Vector2) and Jump (Button) action; set Behavior to Invoke Unity Events and wire
OnMove/OnJumpto the controller. - Attach
PlatformerController2D; set its Ground Layer mask to Ground. - Press Play: run left/right and jump only when grounded. Tune
moveSpeed,jumpForce, and Gravity Scale until it feels good.
๐ก Hint: my character jumps infinitely / mid-air
The ground check isn't finding the floor. Confirm the Ground Layer mask matches the tilemap's layer, the GroundCheck transform sits just at the feet (not inside the body), and groundRadius is small but non-zero (~0.15). Gizmo it by drawing the circle in OnDrawGizmosSelected.
โ Success check
The character runs with responsive control, falls with a satisfying weight, and jumps a consistent height โ but only from the ground. No tipping over, no sticking to walls.
๐ฏ Quick Quiz
Question 1: Your 2D player falls straight through the tilemap floor. The most likely cause?
Question 2: Which is the robust way to know the character can jump?
Question 3: Why write movement in FixedUpdate and only queue the jump in the input callback?
Summary
๐ Key Takeaways
- 2D uses a separate engine โ always the
2Dcomponents and callbacks. - Rigidbody2D body types: Dynamic (player), Kinematic (script-moved), Static (level).
- Tune Gravity Scale and use a Capsule Collider 2D for good platformer feel; freeze Z rotation.
- Set horizontal
linearVelocityfor crisp control; jump by setting upward velocity inFixedUpdate. Physics2D.OverlapCircleat the feet is the reliable ground check that gates jumping.
๐ What's Next?
Your character moves โ but it's a static sprite sliding around. In Module 11: 2D Animation & Lighting we bring it to life, starting with Lesson 11.1: Frame-Based Sprite Animation โ and you'll see the very same Animator state machine from Module 1 driving 2D frames.
๐น๏ธ You've got a platformer
Run, fall, and jump on a real tilemap level โ the physical heart of every 2D platformer, built on the same input and physics discipline you learned in 3D.