Quick answer: Set continuous_cd to CCD_MODE_CAST_SHAPE on fast rigid bodies, or thicken thin walls to at least speed_per_tick wide. For projectile-style objects, prefer a swept raycast over a physics body.
A bullet leaves the muzzle at 3000 pixels per second. At 60 Hz physics, it moves 50 pixels per tick. Your wall colliders are 4 pixels wide. On most ticks the bullet is in empty space; on the tick where it should hit the wall, it has already crossed to the other side. The collision is never detected and the bullet sails through.
Why Discrete Collision Misses
Godot’s default solver checks for overlap between shapes at the end of each physics step. If shape A and shape B are non-overlapping at tick N and non-overlapping at tick N+1 — even though A’s motion would have intersected B somewhere in between — the solver reports no collision. This is fine for bodies moving slower than their own thickness per step. It fails when speed exceeds the smallest collider dimension along the motion axis.
The math: tunneling occurs when velocity * delta > min(body_thickness, wall_thickness) along the motion vector. Faster bodies, thinner walls, or larger delta — any of those crossing the threshold — produces tunneling.
Fix 1: Continuous Collision Detection
On the moving body, set continuous_cd:
# bullet.gd
extends RigidBody2D
func _ready():
continuous_cd = CCD_MODE_CAST_SHAPE
Two modes exist:
- CCD_MODE_CAST_RAY — casts a single ray from the body’s center along its motion vector. Cheap. Works for bullets where the visual shape is small relative to the cast distance.
- CCD_MODE_CAST_SHAPE — sweeps the body’s actual collision shape along the motion vector. More accurate; catches glancing impacts the ray would miss. Pay the cost only for bodies that genuinely tunnel.
CCD has no effect on Area2D/Area3D or StaticBody. If your projectile is an Area for damage events, switch it to a RigidBody with a sensor-like collision setup, or replace the area entirely with a raycast (see Fix 3).
Fix 2: Thicken Walls or Bodies
If you can’t enable CCD — for example, on a CharacterBody using move_and_slide with a thin platform — thicken the geometry. Make wall colliders at least as thick as the maximum distance a body can travel in one tick:
# Compute minimum safe wall thickness
var max_body_speed = 2000.0 # pixels per second
var tick_rate = Engine.get_physics_ticks_per_second()
var min_wall_thickness = max_body_speed / tick_rate
print("Walls must be at least %s px thick" % min_wall_thickness)
At 60 Hz and 2000 px/s, walls need to be 34 pixels thick for guaranteed catches. If your art has 4-pixel walls, add an invisible thicker CollisionShape2D for physics, separate from the visual sprite.
Fix 3: Replace Projectiles with Raycasts
For bullets and other linear projectiles, the most reliable solution is to skip physics entirely. Each frame, cast a ray from the previous position to the new position and resolve the first hit:
# bullet.gd (non-physics version)
extends Node2D
var velocity: Vector2
func _physics_process(delta):
var prev = global_position
var next = prev + velocity * delta
var space = get_world_2d().direct_space_state
var query = PhysicsRayQueryParameters2D.create(prev, next)
var hit = space.intersect_ray(query)
if hit:
_on_hit(hit.collider, hit.position)
queue_free()
else:
global_position = next
This is mathematically equivalent to perfect CCD for point-shaped projectiles. It also runs faster than RigidBody simulation for the same workload because the broadphase, contact manifold, and integration steps are all skipped.
Fix 4: Raise the Physics Tick Rate
In Project Settings → Physics → Common, raise physics_ticks_per_second from 60 to 120 or 240. Halves or quarters the per-tick travel distance respectively. Use this only after profiling — physics work scales linearly with tick rate.
Verifying
Add a temporary trace in _physics_process that draws a line from previous to current position. Tunneling shows up as a line passing cleanly through a wall. After applying CCD or raycasting, the line stops at the wall surface.
“Fast plus thin equals tunneling. Pick one: slow it down, thicken it up, sweep it, or raycast it. Hoping isn’t on the list.”
CCD costs CPU per body — enable it per-projectile, not project-wide.