How Godot's 2D and 3D Physics Servers Manage Collision Detection

Godot isolates low-level collision detection behind the PhysicsServer abstraction, exposing ray-casting, point-queries, and shape-queries through the PhysicsDirectSpaceState API that delegates to backend implementations such as GodotPhysics or Jolt.

Godot’s collision detection system is decoupled from the scene graph through the PhysicsServer architecture. According to the godotengine/godot source code, the engine maintains separate singleton servers for 2D and 3D worlds that manage broad-phase and narrow-phase collision tests behind a unified query interface. This design allows game logic to perform complex spatial queries without direct dependency on the underlying physics backend.

Architecture of Godot Collision Detection

The collision detection stack is organized into distinct layers that separate the scripting API from the physics implementation.

The PhysicsServer Singletons

Godot instantiates PhysicsServer2D and PhysicsServer3D as singletons (PhysicsServer2D::singleton and PhysicsServer3D::singleton) during engine startup. These singletons manage the physics world, create bodies and areas, and store collision layers and masks. High-level scene nodes such as PhysicsBody2D and CollisionObject3D internally reference these servers to synchronize their state.

Direct Space State Interface

Query methods are exposed through PhysicsDirectSpaceState2D and PhysicsDirectSpaceState3D. These classes provide the primary interface for runtime collision detection via methods like intersect_ray, intersect_point, and intersect_shape. When a script calls one of these methods, the space state forwards the request to the concrete physics server implementation.

Backend Delegation

The actual collision calculations occur in pluggable backends (GodotPhysics, Jolt, or custom implementations). Each backend overrides the virtual query functions declared in physics_server_2d.h and physics_server_3d.h, executing broad-phase spatial hashing or BVH traversal followed by narrow-phase shape intersection tests.

2D Collision Query Flow

The 2D query pipeline demonstrates how Godot collision detection marshals data between script and engine internals.

A typical ray-cast begins with a script call to space_state.intersect_ray(). This invokes PhysicsDirectSpaceState2D::_intersect_ray in servers/physics_2d/physics_server_2d.cpp (lines 340–357), which constructs a RayResult struct and calls the pure virtual intersect_ray method:

// servers/physics_2d/physics_server_2d.cpp
Dictionary PhysicsDirectSpaceState2D::_intersect_ray(RequiredParam<PhysicsRayQueryParameters2D> rp_ray_query) {
    EXTRACT_PARAM_OR_FAIL_V(p_ray_query, rp_ray_query, Dictionary());
    RayResult result;
    bool res = intersect_ray(p_ray_query->get_parameters(), result);
    if (!res) return Dictionary();
    Dictionary d;
    d["position"] = result.position;
    d["normal"]   = result.normal;

    return d;
}

The backend implementation performs spatial queries against the 2D broad-phase structure, tests candidate shapes using narrow-phase algorithms, and fills the RayResult with collision data including position, normal, and collider ID. The wrapper then converts this struct into a Dictionary for GDScript consumption.

3D Collision Query Flow

The 3D pipeline mirrors the 2D architecture with dimension-specific data types (Vector3, Transform3D). The entry point is PhysicsDirectSpaceState3D::_intersect_ray in servers/physics_3d/physics_server_3d.cpp (lines 363–381):

// servers/physics_3d/physics_server_3d.cpp
Dictionary PhysicsDirectSpaceState3D::_intersect_ray(RequiredParam<PhysicsRayQueryParameters3D> rp_ray_query) {
    EXTRACT_PARAM_OR_FAIL_V(p_ray_query, rp_ray_query, Dictionary());
    RayResult result;
    bool res = intersect_ray(p_ray_query->get_parameters(), result);
    if (!res) return Dictionary();
    Dictionary d;
    d["position"] = result.position;
    d["normal"]   = result.normal;
    d["face_index"] = result.face_index;

    return d;
}

The virtual intersect_ray declared in physics_server_3d.h (line 129) is overridden by the active 3D backend to execute the actual collision test against the spatial partitioning structure.

Thread Safety and Command Queuing

Both physics servers support multi-threaded operation through the wrap-mt classes (PhysicsServer2DWrapMT and PhysicsServer3DWrapMT). When running on a dedicated physics thread, these wrappers enqueue commands (e.g., step, sync, flush_queries) into a thread-safe command queue.

In servers/physics_2d/physics_server_2d_wrap_mt.cpp (lines 58–63), the step method pushes simulation updates to the queue:

// servers/physics_2d/physics_server_2d_wrap_mt.cpp
void PhysicsServer2DWrapMT::step(real_t p_step) {
    if (create_thread) {
        command_queue.push(this, &PhysicsServer2DWrapMT::step, p_step);
    } else {
        physics_server_2d->step(p_step);
    }
}

Query calls from the main thread are synchronized automatically; if the physics thread is active, the wrapper ensures safe access to the space state data structures.

Collision Filtering with Layers and Masks

Every physics body and area stores a collision layer (defining which groups it belongs to) and a collision mask (defining which groups it can interact with). Query parameters include a collision_mask field that is bitwise AND-ed against each candidate object’s layer during the broad-phase test. This filtering occurs before expensive narrow-phase calculations, optimizing performance for complex scenes.

Extension API for Custom Physics Backends

Godot supports PhysicsServer2DExtension and PhysicsServer3DExtension for developers implementing custom physics engines. These extension classes mirror the core API, exposing virtual methods like _intersect_ray, _intersect_shape, and _intersect_point that external libraries can implement. The extension interface ensures that custom backends integrate seamlessly with the existing PhysicsDirectSpaceState query system.

Practical Collision Detection Examples

2D Ray Casting in GDScript

This example queries the 2D space for obstacles along a line segment:

var space_state = get_world_2d().direct_space_state
var from = Vector2(10, 10)
var to   = Vector2(200, 200)

var result = space_state.intersect_ray(
    PhysicsRayQueryParameters2D.create(
        from,
        to,
        collision_mask = 1,          # only layer 1

        exclude = []))
if result:
    print("Hit at ", result.position, " normal ", result.normal)

Behind the scenes, this calls PhysicsDirectSpaceState2D::_intersect_rayPhysicsServer2D::intersect_ray.

3D Shape Sweeping in GDScript

To detect collisions along a moving shape (sweep test):

var space_state = get_world_3d().direct_space_state
var shape = SphereShape3D.new()
shape.radius = 1.0

var params = PhysicsShapeQueryParameters3D.new()
params.shape = shape
params.transform = Transform3D(Basis(), Vector3(0, 5, 0))
params.motion = Vector3(0, -10, 0)   # sweep downwards

var collisions = space_state.intersect_shape(params, 10)
for hit in collisions:
    print("Collided with body ", hit.collider, " at shape ", hit.shape)

This invokes PhysicsDirectSpaceState3D::_intersect_shapePhysicsServer3D::intersect_shape.

Direct C++ Server Access

For engine module development, access the server directly:

// Assume PhysicsServer3D* server = PhysicsServer3D::get_singleton();
PhysicsRayQueryParameters3D *q = memnew(PhysicsRayQueryParameters3D);
q->set_from(Vector3(0, 10, 0));
q->set_to(Vector3(0, -10, 0));
q->set_collision_mask(0xFFFFFFFF);

Dictionary hit = server->space_get_direct_state(space_rid)
                     ->intersect_ray(q);
if (!hit.is_empty()) {
    Vector3 pos = hit["position"];
    Vector3 normal = hit["normal"];
    // process hit …
}

Summary

  • PhysicsServer2D and PhysicsServer3D are singletons that abstract the physics engine implementation from the game logic.
  • PhysicsDirectSpaceState provides the scripting API for collision queries including intersect_ray, intersect_point, and intersect_shape.
  • Query implementations reside in physics_server_2d.cpp (line 340) and physics_server_3d.cpp (line 363), delegating to backend-specific broad-phase and narrow-phase algorithms.
  • Collision layers and masks filter queries during the broad-phase to avoid unnecessary calculations.
  • PhysicsServer2DWrapMT and PhysicsServer3DWrapMT handle thread synchronization via command queues when physics runs on a separate thread.
  • The extension API allows custom physics backends to integrate with Godot’s collision detection system by implementing the same virtual query methods.

Frequently Asked Questions

How do I access the direct space state for collision queries in Godot?

Obtain the space state from the current world using get_world_2d().direct_space_state for 2D scenes or get_world_3d().direct_space_state for 3D scenes. This returns a PhysicsDirectSpaceState object that exposes query methods like intersect_ray() and intersect_shape().

What is the difference between intersect_ray and intersect_shape?

intersect_ray performs a line segment query returning the first collision along the path, while intersect_shape performs a volume query (or sweep test) returning all bodies overlapping a specified shape. Ray queries optimize for single-hit detection, whereas shape queries support collision masks and can return multiple results for overlap detection.

Are collision queries thread-safe in Godot?

Yes, when using the multi-threaded physics mode, the PhysicsServer2DWrapMT and PhysicsServer3DWrapMT classes ensure thread safety by queuing physics commands and synchronizing space state access. However, you should only call queries from the main thread or use appropriate synchronization primitives, as the space state data changes during the physics step.

How do collision layers and masks affect query results?

The collision_mask parameter in query structs (such as PhysicsRayQueryParameters2D) is compared against each physics object’s collision_layer using bitwise AND. If the result is zero, the object is skipped during the broad-phase, preventing it from appearing in the final results regardless of geometric intersection.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →