A continuation of this question,
The solution provided there by @Theraot works for _draw()
but not for Area2D
as you can see in this example:
script for player:
extends KinematicBody2D
var velocity: Vector2
func _physics_process(_delta):
velocity.y+=30
move_and_slide(velocity)
script for Holder:
extends Node2D
onready var item = $Item
onready var item_area = $ItemArea
onready var item_inital_offset = item.global_position - global_position
onready var area_inital_offset = item_area.global_position - item.global_position
var velocity: Vector2
func _body_entered(body):
print("body_entered=>", body)
func _body_exited(body):
print("body_exited=>", body)
func _physics_process(delta):
velocity.y+=30
global_position+=velocity * delta
item.global_position = global_position + item_inital_offset
item_area.global_position = item.global_position + area_inital_offset
func _ready():
item_area.connect("body_entered", self,"_body_entered")
item_area.connect("body_exited", self,"_body_exited")
when the Player
(green) falls into the ItemArea
(blue) and stops when it hits Item
(red), the body_entered
signal is not emitted even though you can clearly see that it entered the area
I have checked that the Area2D
node works by initially placing another body in it and it emits a signal just fine
my best guess is that maybe since both Holder
& Player
are both falling at the same speed they technically never intersect but since Item
is synced_with_physics there's a visual lag which makes it drag back and not take the ItemArea
along with it
Edit
I tried using intersect_shape
directly within Holder
but it still didn't work:
extends Node2D
onready var item = $Item
onready var item_inital_offset = item.global_position - global_position
const SHAPE_OFFSET = Vector2(0, -52)
export(Shape2D) var intersect_shape: Shape2D
var intersecting_bodies := {}
var velocity: Vector2
func _body_entered(body):
print("body_entered=>", body)
func _body_exited(body):
print("body_exited=>", body)
func _physics_process(delta):
velocity.y+=30
global_position+=velocity * delta
item.global_position = global_position + item_inital_offset
check_area_process(delta)
update()
func check_area_process(_delta):
var space := get_world_2d().direct_space_state
var query := Physics2DShapeQueryParameters.new()
query.set_shape(intersect_shape)
query.transform = item.global_transform
query.transform.origin = item.global_position + SHAPE_OFFSET
var currently_intersecting_nodes:Array = space.intersect_shape(query)
var new_intersecting_bodies := {}
# check for newly entered objects
for node in currently_intersecting_nodes:
node=node.collider
new_intersecting_bodies[node]=null
if(!intersecting_bodies.has(node)):
_body_entered(node)
# check for exited objects
for node in intersecting_bodies:
if(!new_intersecting_bodies.has(node)):
_body_exited(node)
intersecting_bodies=new_intersecting_bodies
func _draw():
if(get_tree().debug_collisions_hint):
draw_set_transform(to_local(item.global_position + SHAPE_OFFSET), 0, Vector2.ONE)
intersect_shape.draw(get_canvas_item(), Color(1, 1, 1, 0.5))
I remember an issue about areas not detecting bodies when they were moved. However, I have been unable to find it.
We can use force_update_transform
on the objects that moved (the body and the area). But - sadly - there isn't an equivalent to force_raycast_update
for the area.
So, I'll go ahead and give you the solution without area.
We will work on your holder script, because there is where you want to use the area...
For ease of use, I'll say have these exports:
export(int, LAYERS_2D_PHYSICS) var collision_mask:int
export(Shape2D) var shape_2d:Shape2D
Then, in _physics_process
you a Physics2DDirectSpaceState
like this:
var space := get_world_2d().direct_space_state
Then you need to create a Physics2DShapeQueryParameters
like this:
if shape_2d == null:
return
var space := get_world_2d().direct_space_state
var query := Physics2DShapeQueryParameters.new()
query.collision_layer = collision_mask
query.set_shape(shape_2d)
query.transform = global_transform
Addendum: I realize that you have the area offset from the holder, so you might want to compute an offset transformation for that. On a similar note, I remind you that the exported variables are for ease of use, but you can define those values in code if that makes more sense to you.
And then we can query what bodies are overlapping the shape:
if shape_2d == null:
return
var space := get_world_2d().direct_space_state
var query := Physics2DShapeQueryParameters.new()
query.collision_layer = collision_mask
query.set_shape(shape_2d)
query.transform = global_transform
currently_overlapping_bodies:Array = space.intersect_shape(query)
We can figure out what entered and what left... For that we need to add a variable on top (outside of _physics_process
) to keep track of the overlapping bodies:
var overlapping_bodies:Array = []
And now, in _physics_process
we can do this:
if shape_2d == null:
return
var space := get_world_2d().direct_space_state
var query := Physics2DShapeQueryParameters.new()
query.collision_layer = collision_mask
query.set_shape(shape_2d)
query.transform = global_transform
currently_overlapping_bodies:Array = space.intersect_shape(query)
exited:Array = []
entered:Array = []
for body in overlapping_bodies:
if not body in currently_overlapping_bodies:
exited.append(body)
for body in currently_overlapping_bodies:
if not body in overlapping_bodies:
entered.append(body)
overlapping_bodies = currently_overlapping_bodies
Be aware that since you wouldn't be using area anymore, it would not show up in the editor, nor while debugging with Visible Collision Shapes enabled. Thus, you need to use _draw
to have some representation of it. Be aware that Shape2D
has a draw
method that you can use for this:
func _draw() -> void:
if shape_2d == null or not get_tree().debug_collisions_hint:
return
shape_2d.draw(get_canvas_item())
Addendum: Since you might want to draw the shape offset, you might want to use draw_set_transform
.
And, of course, remember to call update
.