Summary
Rigidbody2D is the Unity component that makes a 2D GameObject participate in the physics simulation. It enables realistic movement driven by forces and velocities, and is required for OnCollisionEnter2D and OnTriggerEnter2D callbacks to fire on a moving object. There are three body types, each suited to a different role. Physics-related code belongs in FixedUpdate, which runs on a fixed timestep independent of frame rate.
Key ideas
Body types:
| Body type | Behaviour | Typical use |
|---|---|---|
Dynamic | Fully simulated — responds to forces, gravity, and collisions | Moving enemies, projectiles, falling objects |
Kinematic | Moves via script only (MovePosition or linearVelocity); not affected by forces or gravity. Behaves like an immovable object in collisions. By default its colliders only make contacts with Dynamic bodies, not with Static or other Kinematic ones, unless Use Full Kinematic Contacts is enabled; even then the contacts are reported but nothing pushes it back | Moving platforms; players with a custom character controller that does its own collision checks |
Static | Does not move | Walls, floors, static hazards |
Gravity Scale: How strongly gravity pulls the object. Set to 0 for top-down 2D games or for kinematic players that manage their own movement.
Freeze Rotation Z: Prevents the physics engine from rotating the object when it collides. Almost always enabled on player characters — a player that tips over when touching a wall is rarely intentional.
FixedUpdate(): The correct place for all physics and Rigidbody2D calls. It runs on Unity’s fixed timestep (default 50 times per second), independent of frame rate. Calling physics in Update causes frame-rate-dependent behaviour — the physics will feel different on machines with different performance.
rb.linearVelocity (Unity 6) / rb.velocity (older Unity): Direct velocity assignment gives crisp, predictable movement, which is why it is the usual choice for responsive character control. The trade-off with AddForce is about control versus momentum:
- Setting
linearVelocitygives instant, exact speed every step, but it overwrites whatever velocity physics or other code produced: a knockback impulse, an explosion or a conveyor belt is cancelled on the nextFixedUpdateunless you skip the assignment while it plays out (the knockback example below does this with anisKnockedBackwindow). AddForce(continuous force, the defaultForceMode2D.Force) adds to the current velocity, so the body builds up speed, keeps momentum and combines naturally with other forces. That suits vehicles, spaceships, ice and deliberately floaty movement, but you have to cap the speed and add damping or counter-force yourself, and the response is less immediate.
(An earlier version of this page said AddForce “introduces acceleration drift”; that is not a defined term, and the trade-off above is the accurate version.)
AddForce(direction * magnitude, ForceMode2D.Impulse): Applies the entire force in a single physics step. Use for one-shot impulses: knockback, jump, explosion.
Vector2.normalized: Returns a direction vector with a magnitude of exactly 1. Normalising before multiplying by speed prevents diagonal movement from being faster than horizontal or vertical movement (diagonal magnitude is √2 ≈ 1.41 without normalisation).
Vector2.ClampMagnitude(velocity, maxSpeed): Limits the magnitude of a vector without changing its direction. Use as a speed cap to prevent velocity accumulating beyond a safe maximum.
In practice
Caching Rigidbody2D in Awake:
private Rigidbody2D rb;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
if (rb == null) Debug.LogError("[PlayerPhysics] No Rigidbody2D found!");
}Physics movement in FixedUpdate:
private Vector2 moveInput;
private void Update()
{
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
// Store input for the next physics step
moveInput = new Vector2(h, v).normalized;
}
private void FixedUpdate()
{
HandleMovement();
EnforceSpeedCap();
}
private void HandleMovement()
{
// Direct velocity assignment — crisp, predictable
rb.linearVelocity = moveInput * moveSpeed;
}
private void EnforceSpeedCap()
{
// Preserves direction while clamping magnitude
rb.linearVelocity = Vector2.ClampMagnitude(rb.linearVelocity, maxSpeed);
}Read this as two clocks working together. Update runs once per rendered frame and records the latest player input. FixedUpdate runs on Unity’s physics timestep and applies that stored input to the Rigidbody2D. This separation is more reliable than mixing all input and physics work into one method, especially when a project grows beyond held movement keys.
One-shot input (jump): read in Update, consume in FixedUpdate:
Held axes can simply be stored each frame, as above. A button press is different. Input.GetButtonDown is true only during the frame the button went down, and Unity says to call it from Update “since the state gets reset each frame” (Unity Technologies, Scripting API: Input.GetButtonDown). FixedUpdate, meanwhile, “may be called zero, one, or multiple times per frame” (Unity Technologies, Scripting API: MonoBehaviour.FixedUpdate). So a press read inside FixedUpdate is lost on any frame that has no physics step. The fix is a flag that Update sets and FixedUpdate clears:
[SerializeField] private float jumpForce = 8f;
private bool jumpRequested;
private void Update()
{
if (Input.GetButtonDown("Jump"))
jumpRequested = true; // remember the press until physics can use it
}
private void FixedUpdate()
{
if (jumpRequested)
{
jumpRequested = false; // consume it, so one press = one jump
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
}
}Two details matter. Only ever set the flag to true in Update (writing jumpRequested = Input.GetButtonDown("Jump"); would overwrite an unconsumed press with false on the next frame if no physics step ran in between). And clear it in FixedUpdate even if the jump is refused (for example, not grounded), unless you deliberately want a short jump buffer, which is a game-feel choice. The same rule applies to the Input System: check WasPressedThisFrame() in Update and store the result (see unity-input-system).
Does storing input in Update make movement inconsistent? Not the speed: FixedUpdate always applies the latest stored input over a fixed timestep, so the distance travelled per second is the same on any machine. What can look inconsistent is the smoothness, because the screen is drawn more often than physics moves the body. That is what interpolation fixes.
Smooth motion: Rigidbody2D interpolation
Physics runs at a fixed rate (50 steps per second by default) while frames are drawn at whatever rate the machine manages, often 60 or 144 per second. Some frames get no physics step and some get two, so a body moved by physics can appear to advance in uneven hops. Unity’s 3D Rigidbody reference explains that “Physics simulation runs at discrete timesteps, while graphics are rendered at variable frame rates”, which “can lead to visual jitter”, and that the effect “is particularly noticeable on GameObjects that the camera follows” (Unity Technologies, Scripting API: Rigidbody.interpolation). A follow camera moving every frame makes the player’s stutter obvious against a smoothly scrolling background.
Set Interpolate on the Rigidbody2D component (Inspector field Interpolate, or rb.interpolation in code):
| Setting | What it does | Use for |
|---|---|---|
None (default) | Draws the body exactly where the last physics step left it | Most objects, especially ones the camera does not follow |
Interpolate | ”Motion is smoothed based on the object’s positions in previous frames” (Unity Technologies, Scripting API: Rigidbody2D.interpolation) | The player and anything the camera follows: the usual choice |
Extrapolate | ”Smooths motion based on an estimate of its position in the next frame” (same source) | Rarely; because it predicts, it can briefly overshoot when the body stops or bounces (this wiki’s caution) |
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
rb.interpolation = RigidbodyInterpolation2D.Interpolate; // or set it in the Inspector
}Guidance and gotchas:
- Unity recommends turning interpolation on “for the main character but disable it for everything else” (Unity Technologies, Scripting API: Rigidbody.interpolation). The 3D page says this; the 2D behaviour is the same in principle.
- Interpolation only smooths what physics moves. Moving an interpolated body by setting
transform.positionfights the physics system; when interpolation is enabled “the physics system takes control of the Rigidbody’s transform” (same source). Move it withlinearVelocity,AddForceorMovePosition. - If jitter remains, check that the camera follows in
LateUpdate(or uses Cinemachine) rather than inFixedUpdate; for pixel art, see also unity-pixel-perfect-camera.
MovePosition vs setting velocity
Both are valid ways to turn input read in Update into physics movement, but they suit different body types. In both cases, read input in Update, store it, and apply it in FixedUpdate, never in Update.
rb.linearVelocity = input * speed | rb.MovePosition(rb.position + input * speed * Time.fixedDeltaTime) | |
|---|---|---|
| Body type | Dynamic | Kinematic: “MovePosition is intended for use with kinematic rigidbodies” (Unity Technologies, Scripting API: Rigidbody2D.MovePosition) |
| What it sets | A speed; the solver works out the position and resolves collisions | A target position; Unity calculates the velocity needed to reach it “during the next physics update” (same source) |
| Collisions | The body is pushed back by walls and pushes other Dynamic bodies | A Kinematic body is not stopped by what it hits; it pushes Dynamic bodies out of the way and does not collide with Static walls by default (see body types above) |
| Gravity and other forces | Gravity still applies to the axis you do not overwrite (keep rb.linearVelocity.y for a platformer) | Neither gravity nor linear damping affects the body during the move (same source); you write any falling yourself |
| Calling it twice per step | The last assignment wins | Has no cumulative effect: only the last target is used at the next physics update (same source) |
| Typical use | Player characters, enemies, anything that should bump into the world | Moving platforms, lifts, scripted paths, character controllers that do their own collision queries (for example with Rigidbody2D.Cast) |
Rule of thumb: if you want the physics engine to stop you at walls, use a Dynamic body and set velocity; if you want an exact path and will handle collision yourself, use a Kinematic body and MovePosition. Unity also notes that Rigidbody2D.MovePosition is meant for “relatively small distance movements”, because 2D bodies have a speed limit per step; to teleport, set rb.position instead.
3D Rigidbody works the same way. Unity 6 renames velocity to Rigidbody.linearVelocity in 3D too, and Rigidbody.MovePosition “Moves the kinematic Rigidbody towards position” and “complies with the interpolation settings”; for an instant jump to a new place, “use Rigidbody.position instead of MovePosition” (Unity Technologies, Scripting API: Rigidbody.MovePosition). Use the 3D CharacterController instead when you want neither (see unity-character-controller).
Knockback impulse:
public void ApplyKnockback(Vector2 sourcePosition)
{
// Direction: away from the damage source
Vector2 direction = ((Vector2)transform.position - sourcePosition).normalized;
rb.linearVelocity = Vector2.zero; // clear existing velocity for clean impulse
rb.AddForce(direction * knockbackForce, ForceMode2D.Impulse);
}Invoke for timed callbacks (e.g. ending a knockback window):
isKnockedBack = true;
Invoke("EndKnockback", knockbackDuration); // calls EndKnockback() after N seconds
private void EndKnockback()
{
isKnockedBack = false;
}Inspector setup checklist
For a typical 2D player character:
- Body Type: Dynamic (if physics should stop it at walls; the usual beginner choice and the one this page’s examples use) or Kinematic (if a custom controller handles collision itself; tick Use Full Kinematic Contacts if it must get collision callbacks from Static tilemaps and walls)
- Gravity Scale:
0for top-down; keep default for platformer - Freeze Rotation → Z: enabled (prevents tipping on collision)
- Attach the Rigidbody2D to the same GameObject as the movement script
Gotchas
- Never use
transform.Translateor settransform.positiondirectly on a Dynamic Rigidbody2D — this bypasses the physics engine and causes jitter, missed collisions, and tunnelling. Userb.linearVelocity,AddForce, or (for Kinematic)rb.MovePosition. - Read input in
Update; apply it inFixedUpdate. CallingInput.GetAxisRawinFixedUpdateis often acceptable for held movement axes in small beginner demos, but it can miss one-frame button events such asGetButtonDownon frames with no physics step. Use the one-shot flag pattern above. - A Kinematic player walks through Static walls. With Use Full Kinematic Contacts off, “a Kinematic Rigidbody 2D will only collide with Dynamic Rigidbody 2Ds”; turning it on enables contacts with all body types, but a Kinematic body still gets no collision response, so your script must stop it (Unity Technologies, Unity Manual: Kinematic Body Type reference; Scripting API: Rigidbody2D.useFullKinematicContacts). The Inspector label reads Full Kinematic Contact in some Unity 6 versions. The same Manual page notes that “Trigger Colliders are an exception to this rule”, so trigger overlaps are still reported.
rb.linearVelocityis the Unity 6 API name. In older Unity versions (pre-6), the property isrb.velocity. Both refer to the same thing.
Worked example
The first script in the beginner 2D route is the movement pattern from this page as a complete component:
- TopDownPlayerMovement.cs —
[RequireComponent(typeof(Rigidbody2D))], caches the body inAwake, reads normalisedGetAxisRawinput inUpdateand setsrb.linearVelocityinFixedUpdate; the folder’s README has the scene set-up (Gravity Scale 0, Freeze Rotation Z)
Practice
Build a top-down player that moves with physics and has a one-shot dash:
- a Dynamic Rigidbody2D with Gravity Scale
0and Freeze Rotation Z ticked - held movement read in
Update, normalised, and applied withrb.linearVelocityinFixedUpdate - Space requests a dash: set a flag in
Update, then inFixedUpdateclear it and applyAddForce(direction * dashForce, ForceMode2D.Impulse)in the current movement direction; while a short dash window runs (a bool cleared withInvoke, as in the knockback example), skip the velocity assignment and the speed cap so they do not cancel the impulse - a camera that follows the player in
LateUpdate
Success test:
- diagonal movement is no faster than straight movement
- every press of Space produces exactly one dash, and holding it does not repeat
- the player does not spin when it bumps a wall
- you can describe the difference between Interpolate
NoneandInterpolateon the followed player against a patterned background
Extension:
- move the
GetButtonDowncheck intoFixedUpdate, raise the frame rate (for example by turning off VSync), and count how many presses are lost; then restore the flag pattern
Self-test
- Why does Rigidbody2D movement code go in
FixedUpdaterather thanUpdate? - Why normalise the input vector before multiplying by speed?
- When would you use
AddForce(..., ForceMode2D.Impulse)instead of settinglinearVelocity? - Why can
Input.GetButtonDowninsideFixedUpdatemiss a jump, and what is the fix? - Why should the jump flag only ever be set to
trueinUpdate, never assigned straight fromGetButtonDown? - A player moved by physics jitters when the camera follows it. Which Rigidbody2D setting fixes this, and why does the jitter happen?
Answers
FixedUpdateruns on Unity’s fixed physics timestep, independent of frame rate, so physics behaves the same on every machine.- Without it, diagonal input has a magnitude of about 1.41, so diagonal movement would be faster than straight movement.
- For one-shot forces such as knockback, a jump or an explosion; velocity assignment suits continuous character movement.
GetButtonDownis true only during the frame of the press, andFixedUpdatecan run zero times in a frame, so the press can fall on a frame with no physics step. Set a flag inUpdateand consume it inFixedUpdate.- Assigning it directly would overwrite an unconsumed press with
falseon the next frame if no physics step ran in between. - Interpolate. Physics moves the body at a fixed rate while frames are drawn at a different rate, so some frames show no movement and some show two steps.
Related
- unity-collider2d-and-triggers — collision and trigger detection
- monobehaviour-lifecycle —
FixedUpdateandAwakein the lifecycle - unity-transform — transform-based movement (use instead of Rigidbody2D on non-physics objects)
- unity-getcomponent — caching the Rigidbody2D reference
- unity-input — reading buttons and axes in
Update - cinemachine-overview — camera follow without hand-written jitter fixes
- game-feel — jump buffering and responsiveness
- unity-tilemap — Static Rigidbody2D and composite colliders for tilemap level geometry
- unity-character-controller — The 3D non-physics alternative: CharacterController