Physics Bodies, Areas, Collision Layers & Raycasts

Don't set position of a rigid body every frame; use forces/impulses, freeze, or _integrate_forces (teleport via state.transform).

21.1 Collision layers and masks

Layer: which layers this object is on.

Mask: which layers this object detects/collides with.

Name layers in *Project Settings → Layer Names → 2D Physics* (e.g., 1 World, 2 Player, 3 Enemies, 4 PlayerBullets, 5 Pickups).

World, Enemies, Pickups World, Enemies

collision_layer = 1 << 1                  # only layer 2
collision_mask = 0b1101                   # layers 1, 3, 4
set_collision_layer_value(3, true)        # 1-based layer number
set_collision_mask_value(5, false)
print(get_collision_mask_value(1))

21.2 Areas

get_overlapping_bodies(), get_overlapping_areas(), has_overlapping_bodies() — updated once per physics frame.

Areas can override gravity, damping, and audio reverb in a region (gravity_space_override).

Hitbox/Hurtbox pattern: attacks are Area2D "hitboxes"; characters have "hurtbox" areas; the hurtbox's area_entered applies damage.

# coin.gd
extends Area2D

@export var value := 1

func _ready() -> void:
	body_entered.connect(_on_body_entered)

func _on_body_entered(body: Node2D) -> void:
	if body.is_in_group("player"):
		Events.coin_collected.emit(value)
		queue_free()

21.3 RigidBody2D

Don't set position of a rigid body every frame; use forces/impulses, freeze, or _integrate_forces (teleport via state.transform).

extends RigidBody2D

func _ready() -> void:
	contact_monitor = true          # required for body_entered on rigid bodies
	max_contacts_reported = 4
	body_entered.connect(func(b): print("hit ", b.name))

func kick(direction: Vector2) -> void:
	apply_central_impulse(direction * 400.0)       # instant change
	apply_torque_impulse(200.0)

func _physics_process(_delta: float) -> void:
	apply_central_force(Vector2(0, -50))          # continuous force (per physics step)

func _integrate_forces(state: PhysicsDirectBodyState2D) -> void:
	# Safe place to directly modify the body's state
	if state.linear_velocity.length() > 800.0:
		state.linear_velocity = state.linear_velocity.limit_length(800.0)

21.4 RayCast2D / ShapeCast2D nodes

@onready var ray: RayCast2D = $RayCast2D   # target_position set in the Inspector

func _physics_process(_delta: float) -> void:
	if ray.is_colliding():
		var hit := ray.get_collider()
		var point := ray.get_collision_point()
		var normal := ray.get_collision_normal()
	ray.target_position = Vector2(200, 0)
	ray.force_raycast_update()                  # update immediately (otherwise next physics frame)

21.5 Raycasts in code (direct space state)

Only query the space state during _physics_process (it's locked otherwise in some cases).

func line_of_sight(from: Vector2, to: Vector2) -> bool:
	var space := get_world_2d().direct_space_state
	var query := PhysicsRayQueryParameters2D.create(from, to, 0b0001, [get_rid()])
	query.collide_with_areas = false
	var result := space.intersect_ray(query)
	return result.is_empty()        # result keys: position, normal, collider, collider_id, rid, shape

func objects_at_point(p: Vector2) -> Array[Dictionary]:
	var params := PhysicsPointQueryParameters2D.new()
	params.position = p
	return get_world_2d().direct_space_state.intersect_point(params)

21.6 3D equivalents

CharacterBody3D, RigidBody3D, StaticBody3D, Area3D, RayCast3D, PhysicsRayQueryParameters3D, get_world_3d().direct_space_state. Godot 4.4+ ships Jolt Physics as an alternative 3D engine (*Project Settings → Physics → 3D → Physics Engine*); it is faster and more stable for many bodies.