godotengine / godot

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

RayCast2D detects object at previous position, if that object has just moved #95359

Open bunny-baxter opened 3 months ago

bunny-baxter commented 3 months ago

Tested versions

System information

Godot v4.3.rc3 - Windows 10.0.19045 - Vulkan (Forward+) - integrated Intel(R) HD Graphics 520 (Intel Corporation; 24.20.100.6293) - Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz (4 Threads)

Issue description

I'm making a turn-based game and I ran into this strange behavior with RayCast2D. If I move the player object (a CharacterBody2D) and then use RayCast2D to try to detect the player, it will detect the player at their previous position.

As an example, if you do something like this:

player.position.x += 100
var raycast = $RayCast2D
raycast.position = player.position
raycast.target_position = Vector2.ZERO
raycast.force_raycast_update()
if raycast.get_collider() == player:
    print("RayCast2D hit player")

Then the print statement will never run. If instead the RayCast2D is aimed at the player's previous position (i.e. raycast.position = player.position - Vector2(100, 0)) then the print statement will always run.

I thought calling force_raycast_update would cause RayCast2D to see the player at their current position, but it seems like it doesn't. (By the way, if you remove the call to force_raycast_update then it misses the player the first time it runs, but then always hits the player on subsequent runs. That also seems strange to me.)

Am I doing something wrong here? Is there a better way to accomplish this that doesn't run into this problem? Thank you!

Steps to reproduce

  1. In the MRP, use the arrow keys to move the player or the spacebar to wait.
  2. The MRP code will move a RayCast2D to the player's position, call force_raycast_update and then get_collider.
  3. If you move the player, it will print "RayCast2D missed player". I would expect it to always print "RayCast2D hit player". Waiting will correctly print "RayCast2D hit player".

Minimal reproduction project (MRP)

raycast2d-minimal-repro.zip

AThousandShips commented 3 months ago

This is probably to be expected, as force_raycast_update won't update the state of the space, or bodies, it only performs the cast again, on the same state, this should be documented though

bunny-baxter commented 3 months ago

Hi AThousandShips, thanks for the reply.

force_raycast_update won't update the state of the space, or bodies, it only performs the cast again, on the same state

If I understand correctly, are you saying: RayCast2D is accessing some cached information that's now stale because the player's position has changed, and that force_raycast_update does not update the cached information? If that's the case, why would the behavior be different if you don't call force_raycast_update?

By the way, I tried calling intersect_ray on PhysicsDirectSpaceState2D directly instead of using a RayCast2D, but it has the same behavior. I also tried calling move_and_collide(Vector2.ZERO) after updating the position, hoping it would do something to the cache, but that also didn't help.

Is there a way for me to force the player's position to be propagated? Or, if not, how can I otherwise work around this problem?

bunny-baxter commented 3 months ago

It seems like it solves the problem if I instead mark the player's turn as finished and return, and then do the raycast in the next call to _process. I think this induces a 1 frame delay, which feels a bit hacky but it doesn't seem to be noticeable in practice. I'm guessing that means the cached information can't be updated until control returns to whatever code is calling _input/_process?

AThousandShips commented 3 months ago

I suspect that the status of any node is updated each frame, not immediately, so the physics state is not up to date

Can you try moving it with move_and_slide or move_and_collide instead of setting the position?

bunny-baxter commented 3 months ago

Hi AThousandShips,

Can you try moving it with move_and_slide or move_and_collide instead of setting the position?

I moved the input code into _physics_process and set the player's velocity instead of their position. Then I tried both move_and_collide(velocity) and move_and_slide() (though the latter is a bit weird in this case; to get the player to actually move 100 pixels I had to divide the velocity by the frame delta). However, neither one seemed to change the behavior.

Hope that helps!

AThousandShips commented 3 months ago

Then probably just a limitation, and probably something to document, as the state isn't updated until the physics is synched, the world is still the same way until things have updated, probably at the end of _physics_process when data is written back

bunny-baxter commented 3 months ago

Okay, makes sense. Thanks for your help.

After thinking about it since yesterday, I realized that in the real game, I can probably loop through all the objects I care about and check their positions manually, instead of doing a raycast, and that should work just fine.