Quick answer: Calling set_deferred("disabled", false) immediately after set_deferred("disabled", true) in the same frame can cancel out. The physics server processes both in order, but the shape never actually disables because re-enable arrives before the next physics step. Add a frame delay or timer between disable and re-enable.
Here is how to fix Godot CollisionShape disabled not re-enabling. You implement an invincibility frame system — disable the hitbox collision shape, wait briefly, re-enable it. You call set_deferred("disabled", true) then later set_deferred("disabled", false). The shape stays disabled permanently. Or it never disables at all. The timing of deferred calls relative to physics steps is the root cause.
The Symptom
A CollisionShape2D or CollisionShape3D that was disabled via set_deferred("disabled", true) does not respond to set_deferred("disabled", false). The shape remains disabled indefinitely. The node’s disabled property reads false in the debugger, but collisions do not register.
Alternative symptom: the shape flickers — it disables and re-enables so fast that it never actually stops detecting collisions, making your invincibility frames useless.
What Causes This
Same-frame disable and enable. If both set_deferred("disabled", true) and set_deferred("disabled", false) are queued in the same physics frame, the physics server may process them both before the next collision check. The net result is no change.
Process mode preventing deferred execution. If the parent node’s process_mode is set to PROCESS_MODE_DISABLED, deferred calls on children do not execute. The call is queued but never processed.
Node freed before deferred call executes. If the node owning the CollisionShape is freed between queueing the deferred call and its execution, the call is silently dropped.
Multiple scripts competing. Two scripts both controlling the same shape’s disabled state. One disables, the other enables on the same frame. The final state is unpredictable.
The Fix
Step 1: Use a timer between disable and enable.
extends CharacterBody2D
@onready var hitbox: CollisionShape2D = $HitboxArea/CollisionShape2D
func take_damage():
# Disable collision for invincibility frames
hitbox.set_deferred("disabled", true)
# Wait for actual time to pass
await get_tree().create_timer(1.0).timeout
# Re-enable after the timer
hitbox.set_deferred("disabled", false)
The await ensures at least one physics frame passes between disable and enable. The physics server has time to process the disabled state before the enable arrives.
Step 2: Use physics_frame signal for frame-precise timing.
func disable_for_frames(frames: int):
hitbox.set_deferred("disabled", true)
for i in range(frames):
await get_tree().physics_frame
hitbox.set_deferred("disabled", false)
This guarantees the shape is disabled for exactly N physics frames before re-enabling. Each await get_tree().physics_frame yields until the next physics step completes.
Step 3: Verify the node is still valid before re-enabling.
func safe_reenable():
await get_tree().create_timer(0.5).timeout
if is_instance_valid(hitbox) and is_inside_tree():
hitbox.set_deferred("disabled", false)
If the player dies or the scene changes during invincibility, the hitbox node may be freed. Always check validity after an await.
Step 4: Use a single authority for shape state. Avoid multiple scripts toggling the same shape. Centralize control:
var _invincible: bool = false
func set_invincible(value: bool):
_invincible = value
hitbox.set_deferred("disabled", value)
func take_damage():
if _invincible:
return
set_invincible(true)
await get_tree().create_timer(1.0).timeout
set_invincible(false)
When set_deferred Is Not Needed
You only need set_deferred when changing collision shapes during physics callbacks (_physics_process, body_entered, area_entered). Outside these contexts, direct assignment is safe:
# Safe in _process, _input, or custom functions not called from physics
func _input(event):
if event.is_action_pressed("toggle_shield"):
hitbox.disabled = !hitbox.disabled # Direct is fine here
“Deferred calls queue for end-of-frame. Two deferred calls in one frame both execute before the next physics step sees either. Add real time between them.”
Related Issues
For physics material problems on the same shapes, see PhysicsMaterial Bounce Not Working. For character body physics, see CharacterBody2D Floor Snap.
Timer between disable and enable. Check validity after await. One script owns the shape state.