Quick answer: The most common cause is physics tunneling. When a Rigidbody moves fast enough to pass completely through a thin collider in a single physics step, Unity's Discrete collision detection misses the overlap.
Here is how to fix Unity rigidbody falling through floor. You hit Play and your Rigidbody-powered character or object drops right through the floor into the void. The floor is there, it has a collider, and everything looks correct in the Scene view — but the physics engine acts as if the ground does not exist. This is physics tunneling, and it is one of the most frustrating bugs in Unity because it often works at low speeds but fails unpredictably at higher velocities or lower frame rates.
The Symptom
A GameObject with a Rigidbody component falls under gravity and passes through a floor, wall, or platform that has a collider. The object continues to fall indefinitely. Sometimes it happens immediately on the first frame. Other times it happens only when the object reaches a certain velocity — for example, after a long fall or when launched by an explosion force.
The problem may be intermittent. It works on your development machine at 144 FPS but tunnels on a player’s machine running at 30 FPS. Or it works with a thick floor collider but fails when you switch to a thin mesh collider. In multiplayer, clients at different frame rates see different collision results.
You might also notice that OnCollisionEnter never fires, confirming that the physics engine genuinely does not detect the contact — the object is not clipping through visually, it is passing through physically.
What Causes This
There are four primary causes, and they often combine:
1. Discrete Collision Detection with high velocity. By default, Unity’s Rigidbody uses Discrete collision detection. This means the physics engine checks for overlaps only at the Rigidbody’s position each fixed timestep (default: 0.02 seconds). If the object moves far enough in one step to completely pass through a collider, the engine never sees the overlap. A Rigidbody falling for 5 seconds reaches roughly 49 m/s. If the floor collider is 0.1 units thick, the object needs to travel less than 0.1 units per physics step to be detected — but at 49 m/s, it moves 0.98 units per step. It overshoots the collider entirely.
2. Moving Rigidbodies in Update instead of FixedUpdate. Physics runs on a fixed timestep. If you set transform.position or rb.velocity in Update(), you are moving the object at a variable rate that is not synchronized with the physics solver. The Rigidbody can teleport between physics steps, bypassing colliders entirely. This is not tunneling in the traditional sense — it is desynchronization.
3. Thin or missing colliders. If the floor is a plane (infinitely thin) or uses a very thin Box Collider, even moderate speeds can cause tunneling. Mesh Colliders on thin geometry (like a sheet or a ramp) are especially vulnerable because their collision surface has negligible thickness.
4. Physics Layer Collision Matrix exclusion. Unity’s Layer Collision Matrix (under Edit → Project Settings → Physics) controls which layers can collide with which. If the Rigidbody’s layer and the floor’s layer have their intersection unchecked, the physics engine ignores collisions between them entirely. No collision detection mode will help if the layers are configured to not interact.
The Fix
Step 1: Enable Continuous Collision Detection on the Rigidbody.
Select the GameObject with the Rigidbody, and in the Inspector, change Collision Detection from Discrete to Continuous or Continuous Dynamic. You can also set this in code:
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class ProjectileSetup : MonoBehaviour
{
[SerializeField] private float launchSpeed = 50f;
private Rigidbody rb;
void Awake()
{
rb = GetComponent<Rigidbody>();
// Continuous: sweeps against static colliders
// ContinuousDynamic: sweeps against both static and dynamic
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
// For 2D, use:
// rb2d.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
}
public void Launch(Vector3 direction)
{
rb.linearVelocity = direction.normalized * launchSpeed;
}
}
Use Continuous when the fast object only needs to collide with static geometry (floors, walls). Use ContinuousDynamic when it also needs to collide with other moving Rigidbodies (such as projectiles hitting enemies). ContinuousDynamic is more expensive, so only use it where necessary.
Step 2: Move all physics interactions to FixedUpdate.
Never set transform.position directly on a Rigidbody. Use Rigidbody.MovePosition, AddForce, or velocity changes, and always do so in FixedUpdate:
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private float moveSpeed = 8f;
[SerializeField] private float jumpForce = 7f;
private Rigidbody rb;
private Vector3 moveInput;
private bool jumpRequested;
void Awake()
{
rb = GetComponent<Rigidbody>();
rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
rb.interpolation = RigidbodyInterpolation.Interpolate;
}
void Update()
{
// Read input in Update (runs every frame)
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
moveInput = new Vector3(h, 0f, v).normalized;
if (Input.GetButtonDown("Jump"))
{
jumpRequested = true;
}
}
void FixedUpdate()
{
// Apply movement in FixedUpdate (synced with physics)
Vector3 targetVelocity = moveInput * moveSpeed;
targetVelocity.y = rb.linearVelocity.y; // Preserve gravity
rb.linearVelocity = targetVelocity;
if (jumpRequested)
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
jumpRequested = false;
}
}
}
Note the RigidbodyInterpolation.Interpolate setting. This smooths the visual position between physics steps, preventing jittery movement while keeping physics calculations at the fixed timestep.
Step 3: Verify colliders and the Layer Collision Matrix.
Make sure both the falling object and the floor have collider components. Then check the Layer Collision Matrix:
using UnityEngine;
public class CollisionDiagnostic : MonoBehaviour
{
void Start()
{
var rb = GetComponent<Rigidbody>();
var col = GetComponent<Collider>();
if (rb == null)
Debug.LogError("No Rigidbody on this object!", this);
if (col == null)
Debug.LogError("No Collider on this object!", this);
if (col != null && col.isTrigger)
Debug.LogWarning("Collider is set to Trigger - it will not block physics.", this);
// Check if this object's layer can collide with the Default layer (0)
int myLayer = gameObject.layer;
int floorLayer = 0; // Default layer, change if your floor is different
bool canCollide = !Physics.GetIgnoreLayerCollision(myLayer, floorLayer);
Debug.Log($"Layer {myLayer} can collide with layer {floorLayer}: {canCollide}");
Debug.Log($"Collision Detection Mode: {rb.collisionDetectionMode}");
Debug.Log($"Is Kinematic: {rb.isKinematic}");
}
}
Also check that neither collider has Is Trigger enabled. A trigger collider does not block physical movement — it generates OnTriggerEnter events instead of OnCollisionEnter. If your floor collider is accidentally set to trigger, objects will fall right through it.
Why This Works
Continuous Collision Detection changes how the physics engine checks for contacts. Instead of only sampling the Rigidbody’s position at each fixed step, it performs a swept-volume test between the previous and current positions. If the swept volume intersects any collider, the engine catches the collision and resolves it, even if the discrete positions on either side of the collider do not overlap. This eliminates tunneling at the cost of additional CPU time per Rigidbody.
FixedUpdate synchronization ensures that every velocity or position change is processed by the physics solver. When you modify a Rigidbody in Update(), the change may not be picked up until the next FixedUpdate tick, and by then the Rigidbody may have already been stepped forward from its old state. The result is a teleportation that the solver never evaluates for collisions.
Layer verification confirms that the physics engine is even considering the collision pair. The Layer Collision Matrix is a global setting that overrides everything else — no amount of CCD or thick colliders will help if the matrix says these two layers do not interact.
"If it falls through at high speed, it is CCD. If it falls through at any speed, it is layers. If it falls through only sometimes, it is FixedUpdate."
Related Issues
If your Rigidbody collides with the floor but the movement is jittery or unstable, see our guide on fixing jittery Rigidbody movement, which covers interpolation and time step tuning. If collisions are detected but OnCollisionEnter is not being called, the issue may be layer-related or trigger-related — check out OnCollisionEnter not called for a focused walkthrough.