godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.71k stars 21.12k forks source link

IntersectRay does not account for sub-frame movement #17609

Closed TheFacto closed 6 years ago

TheFacto commented 6 years ago

Godot version:

OS/device including version:

Issue description: When using IntersectRay(), it appears that it is using the positions of collidable objects at the start of the _PhysicsProcess(). I have a case where I first move an object via SetPosition() and subsequently call IntersectRay() and expect that to collide with the object at its new position. I realize this may not be worded clearly, so for sake of clarity, allow me to bullet-ize these statements.

Please pardon me, I realize this may be working as expected, so maybe I'm asking for some sort of enhancement? I basically followed a 2D Unity tutorial using raycasts and ported that over to Godot using IntersectRay() and encountered this situation.

Steps to reproduce: Please see minimal reproduction project.

Minimal reproduction project: raycast-sub-frame-check.zip

SirRamEsq commented 6 years ago

I'm having the same issue as this on a Linux machine (same version of Godot) in GDScript.

Making use of force_raycast_update doesn't seem to alleviate the problem either. I use raycasts for moving across terrain in a 2D platformer. As long as the colliding body against the raycast is a StaticBody, my collisions are fine. With any moving scenes, however, the reported collision points lag one frame behind.

SirRamEsq commented 6 years ago

Digging around in the source code for a bit, I found a comment that reads //not quite correct, should compute the next matrix.. In collision_object_2d_sw.cpp CollisionObject2DSW::_update_shapes()

The same comment is also present in CollisionObjectSW::_update_shapes() and the_update_shapes_with_motion() functions

Is this comment implying that updating collisions for all CollisionObjects are a frame behind? That doesn't seem super likely to me, but I do wonder what the intent is...

EDIT

The purpose of _update_shapes appears to be to refresh the cached data stored in Rect2 aabb_cache. In the header file, the comments indicate that aabb_cache is used for rayqueries.

I'm not entirely sure how one would compute the 'next' matrix (maybe that's what the real problem is here). Anyone have any insights?

SirRamEsq commented 6 years ago

After toying with this some more, I've noticed this bug only occurs 'sometimes' With certain scenes, Raycast2Ds will always report the correct collision point with another scene. With certain other scenes, they will always report the scene's position in the previous frame.

One instance of a scene can always be correct, whereas another identical instance of the scene will always lag one frame behind.

It leads me to believe that perhaps this bug is caused by the order in which scenes are updated. If a scene that would collide with a RayCast2D has already had its movement applied, then the RayCast2D will correctly intersect with it Whereas if the scene in question will be moved after the intersect_ray is called, then the RayCast2D will lag one frame behind

eon-s commented 6 years ago

Have you tried moving the transform.origin of the body state instead? setting position may not update the body state as fast as you want (or at all in some cases).

SirRamEsq commented 6 years ago

Not entirely sure how to set the origin correctly vs setting the position

Currently, I update scenes by what is intuitive to me. For the player, I use a KinematicBody2D and call move_and_slide, in addition to manually setting the position afterward via a few RayCast2Ds When setting the position manually, I'll use either position or global_position

Some scenes (like kinematicBodies acting as floating platforms) do not set their own position or call move_and_slide, but are moved by parent nodes that rotate, follow a path, or move linearly

When landing on a floating platform that moves, the player RayCasting against the moving platform will report an incorrect position for certain combinations of player and moving platform (which I think is based on the update order).

How would setting the transform.origin help? What's the difference between setting the position vs setting the origin?

eon-s commented 6 years ago

Setting position does not affect the body state immediately and is possible that rays are failing for that reason. If that is the case, then bodies may need a better method to sync the state to the Node transform

To modify the state's transform, use (the C# equivalent of) Physics2DServer.body_get_direct_state for the state, then just state.transform.origin to move it.

I suspect that this is what may be happening and the way to deal with this could have room for improvement.

SirRamEsq commented 6 years ago
I'm able to trigger this behaviour based on how I order the player and moving platform scenes If the player is 'higher' than the FloatingPlatform scene in the scene tree view like so: O - root O - Player O - Raycasts
O - RotatingJoint
O - FloatingPlatform
Then the raycast from the player is always correct. If I change the order so that the player is 'lower' than the FloatingPlatform scene like so: O - root O - RotatingJoint O - FloatingPlatform
O - Player
O - Raycasts

The raycast is always one frame behind.

Currently, there is no script attached to the FloatingPlatform scene. Per your direction, I attached the following script to it

extends KinematicBody2D

func _process(delta):
    var state = Physics2DServer.body_get_direct_state(get_rid())
        #If this isn't how I'm supposed to set the origin, by all means let me know
    state.transform.origin = position

And ordered the scene so that the Player is 'lower' (thus triggering the weird RayCast behaviour) Unfortunately, the Raycasts still 'lagged' one frame behind the actual position

Since this behaviour occurs based on how the scenes are ordered, it seems like the FloatingPlatform scene simply hasn't updated at all when the Player RayCast makes it's check when it's 'higher'.

reduz commented 6 years ago

This is not a bug, for performance reasons you should not expect objects to update their position in the physics immediately after you move them. Transforms are accumulated and then set.

Probably you could try call_deferred after you move an object if you want to do a raycast.

reduz commented 6 years ago

I was thinking, maybe a method to force an object update could be added, since this is not the first time it is asked for.

reduz commented 6 years ago

fixed via 74359a1d1