I am trying to make an explosion effect with Area3D, but get_overlapping_bodies() isn’t returning anything. I made sure monitoring is turned on and layers are correct.
extends Area3D
@export var explosion_duration := 0.5
@export var damage := 50
func _ready():
# Play visual effect
$GPUParticles3D.one_shot = true
await get_tree().physics_frame
apply_effect()
# Queue free after effect ends
await get_tree().create_timer(explosion_duration).timeout
queue_free()
func apply_effect():
var bodies = get_overlapping_bodies()
print("get_overlapping_bodies(): ", bodies)
for body in bodies:
if body.has_method("apply_damage"):
body.apply_damage(damage)
For performance reasons (collisions are all processed at the same time) this list is modified once during the physics step, not immediately after objects are moved. Consider using signals instead.
You’ll need to wait at least 2 physics frames: 1 for the spawn of the object and 1 to update the overlapping objects list after the area has been moved.
About the signal solution, I tried it and it triggered correctly, but I am not sure how to get all bodies in an area. If I use CONNECT_ONE_SHOT I only get the first body, and without it signal is triggered multiple times with
Ah, I didn’t notice what the apply_effect() function was doing. I though it was just playing some animation. In that case just call the apply_damage() function in the callback without any flag:
Still I wonder if it’s viable to do something like this if I need to get all overlapping bodies at once, for example if I need to know how many targets were caught in the explosion for some game logic
func _ready():
$GPUParticles3D.one_shot = true
apply_effect()
func apply_effect():
await get_tree().physics_frame
await get_tree().physics_frame
# can get a full list at once
var bodies = get_overlapping_bodies()
print("bodies: ", bodies)
for body in bodies:
if body.has_method("apply_damage"):
body.apply_damage(damage)
self.monitoring = false
await get_tree().create_timer(explosion_duration).timeout
queue_free()
# output (all bodies at the same time)
# bodies: [body1, body2]
var state := get_world_3d().direct_space_state
var shape_rid := PhysicsServer3D.sphere_shape_create()
PhysicsServer3D.shape_set_data(shape_rid, 10) # radius
var shape_query := PhysicsShapeQueryParameters3D.new()
shape_query.shape_rid = shape_rid
var results: Array[Dictionary] = state.intersect_shape(shape_query)
Saw something like this on github, but I didn’t want to use it because it returns everything ignoring layers/masks set in the editor. Also shape_data radius must be a float 10.0
func _ready():
$GPUParticles3D.one_shot = true
var state := get_world_3d().direct_space_state
var shape_rid := PhysicsServer3D.sphere_shape_create()
PhysicsServer3D.shape_set_data(shape_rid, 10.0) # radius must be float
var shape_query := PhysicsShapeQueryParameters3D.new()
shape_query.shape_rid = shape_rid
shape_query.collision_mask = (1 << 2) | (1 << 3) # set collision layers manually
var results: Array[Dictionary] = state.intersect_shape(shape_query)
print(results)
Good catch, reading the docs closer seem to recommend using shape instead of shape_rid; I think your collision layers are off by one you’d want 1 << 1 | 1 << 2 for layers 2 and 3, or using binary notaion 0b110
var state := get_world_3d().direct_space_state
var sphere := SphereShape3D.new()
sphere.radius = 10.0
var shape_query := PhysicsShapeQueryParameters3D.new()
shape_query.shape = sphere
shape_query.collision_mask = 0b110 # masking 2 and 3
var results: Array[Dictionary] = state.intersect_shape(shape_query)