A continuation of the previous question
How exactly do we detect collision from body_set_force_integration_callback
?
For context, we have a body RID
:
var _body:RID
And we set a callback with body_set_force_integration_callback
:
Physics2DServer.body_set_force_integration_callback(_body, self, "_body_moved", 0)
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
pass
Before going further, I want to point out that the the final parameter of body_set_force_integration_callback
is what we get in _user_data
. But, if I set it to null
Godot will not pass two arguments to the call, in which case I should define _body_moved
with only the state
parameter.
Godot will be calling our _body_moved
every physics frame if the state of the body is active (i.e. not sleeping).
Note: We need to call body_set_max_contacts_reported
and set how many contacts we want reported, for example:
Physics2DServer.body_set_max_contacts_reported(_body, 32)
Now, in the Physics2DDirectBodyState
we get contacts, and we can ask what a few things about each contact, including the body:
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
for index in state.get_contact_count():
var body:RID = state.get_contact_collider(index)
var instance:Object = state.get_contact_collider_object(index)
If it is the body of a PhysicsBody2D
, then instance
will have it.
If we want to implement body_entered
and body_exited
, we need to keep track of the bodies. I'll keep a dictionary of the instances (i.e. PhysicsBody2D
) and I'll use it to report get_colliding_bodies
too.
Then we need to keep track of shapes for body_shape_entered
and body_shape_exited
, not only bodies. We can find them out like this:
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
for index in state.get_contact_count():
var body:RID = state.get_contact_collider(index)
var instance:Object = state.get_contact_collider_object(index)
var body_shape_index:int = state.get_contact_collider_shape(index)
var local_shape_index:int = state.get_contact_local_shape(index)
Notice they are not RID
. They are the position of the shape in the body (so 0
is the first shape of the body, 1
is the second one, and so on). This means that we cannot keep track of the shapes separately from the bodies, because they shape indexes do not make sense without knowing to what body they belong. That means we cannot simple use two arrays of bodies like we did before.
Also, if we only have one shape - which was the case of the prior answer - we could ignore local_shape_index
because it is always 0
. In which case I only need a Dictionary
indexed by body:RID
of body_shape_index:int
.
If I don't take that assumption, I struggle to decide the data structure.
Dictionary
indexed by body:RID
of Dictionary
indexed by body_shape_index:int
of local_shape_index:int
, in which case I want helper methods to deal with it, which pushes me to make a class for it.Dictionary
indexed by body:RID
of tuples of body_shape_index:int
and local_shape_index:int
. Except there is no tuple type, so I would cheat and use Vector2
.You know what? I'll cheat and use the Vector2
.
signal body_entered(body)
signal body_exited(body)
signal body_shape_entered(body_rid, body, body_shape_index, local_shape_index)
signal body_shape_exited(body_rid, body, body_shape_index, local_shape_index)
var colliding_instances:Dictionary = {}
var colliding_shapes:Dictionary = {}
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
var old_colliding_shapes:Dictionary = colliding_shapes
var new_colliding_shapes:Dictionary = {}
colliding_shapes = {}
var instances:Dictionary = {}
for index in state.get_contact_count():
# get contact information
var body:RID = state.get_contact_collider(index)
var instance:Object = state.get_contact_collider_object(index)
var body_shape_index:int = state.get_contact_collider_shape(index)
var local_shape_index:int = state.get_contact_local_shape(index)
var vector := Vector2(body_shape_index, local_shape_index)
# add to instances
instances[body] = instance
# add to colliding_shapes
if not colliding_shapes.had(body):
colliding_shapes[body] = [vector]
else:
colliding_shapes[body].append(vector)
# remove from old_colliding_shapes or add to new_colliding_shapes
# there is room for optimization here
if (
old_colliding_shapes.has(body)
and old_colliding_shapes[body].has(vector)
):
old_colliding_shapes[body].erase(vector)
if old_colliding_shapes[body].size() == 0:
old_colliding_shapes.erase(body)
else:
if not new_colliding_shapes.had(body):
new_colliding_shapes[body] = [vector]
else:
new_colliding_shapes[body].append(vector)
for body in old_colliding_shapes.keys():
# get instance from old dictionary
var instance:Object = colliding_instances[body]
# emit
if not instances.has(body):
emit_signal("body_exited", body)
for vector in old_colliding_shapes[body]:
emit_signal(
"body_shape_exited",
body,
instance,
vector.x,
vector.y
)
for body in new_colliding_shapes.keys():
# get instance from new dictionary
var instance:Object = instances[body]
# emit
for vector in old_colliding_shapes[body]:
emit_signal(
"body_shape_entered",
body,
colliders[body],
vector.x,
vector.y
)
if not colliding_instances.has(body):
emit_signal("body_entered", body)
# swap instance dictionaries
colliding_instances = instances
func get_colliding_bodies() -> Array:
return colliding_instances.values()
The variable old_colliding_shapes
begins with the shapes already known to be colliding, and in in the iteration we are removing each one we see. So at the end, it has the shapes that were colliding but no longer are.
The variable new_colliding_bodies
begins empty, and in the iteration we are adding each shape we didn't remove from old_colliding_shapes
, so at the end it has the shapes that are colliding that we didn't know about before.
Notice that old_colliding_shapes
and new_colliding_bodies
are mutually exclusive. If a body is in one it is not in the other because we only add the body to new_colliding_bodies
when it is not in old_colliding_shapes
. But since they have shapes and not bodies a body can appear in both. This why I need an extra check to emit "body_exited"
and "body_entered"
.