godotengine / godot

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

RigidBody2D position change when changing 'mode' (KINEMATIC to CHARACTER) #17860

Closed iPatso closed 4 years ago

iPatso commented 6 years ago

Godot version: 3.0.stable.official OS/device including version: OSX Version 10.13.3

Expected: Player starts on Platform. Press space bar >> Player square is launched upward at a random direction (linear_velocity). Either press space bar again to bring Player down to the Platform or simply let the Player land naturally with physics. Repeat from landed position on the Platform.

Note: When the Player is in the air, its mode is Character. When the Player is on the Platform (i.e. at rest), its mode is Kinematic. This is so that when the Platform moves, the Player will move with it programatically.

Note2: When space bar is pressed when at rest, the mode is changed from Kinematic to Character.

Problem: Whenever space bar is pressed when at rest, the launch position of Player always returns back to the initial position of Player (set in the editor) when it should start at the Player's current position on the Platform after the initial launching and landing. Doing some random tests in the code, if mode is changed to Character on the last line of function "_on_Player_body_entered" (commented out in code), then the position does not reset (although this is not the behavior wanted because the mode should still be Kinematic at this point).

Basic project: PhysicsTesting.zip

Rubonnek commented 6 years ago

By setting the Player's Continous Cd to Cast Ray in Inspector was able to get desired behaviour leaving Player.gd as is.

I still think this may be a bug.

eon-s commented 6 years ago

You should not set position manually of a rigid body (not even on kinematics if want to use move methods), change the transform.origin of the body state instead.

iPatso commented 6 years ago

@Rubonnek , Yes great! That did fix it. Can you please explain why that fixes the issue? Thanks!

@eon-s , ahh yes. Thank you for that. I did read that in the docs for RigidBody2D but did not address it fully at the time. So what I did was change this line: position.x = current_platform.position.x - player_platform_x_distance to this: state.transform.origin.x = current_platform.position.x - player_platform_x_distance

It does seem to work fine regarding keeping the position set when the platform moves and the Player is launched again, but the actual sprite itself does not update on the screen (i.e. moves with the platform). I will be looking into this, but at the moment cannot think of the reason (because of my limited knowledge of Godot).

Rubonnek commented 6 years ago

@iPatso Essentially every game engine out there that makes use of scene trees also makes use of matrices called affine transforms. These matrices contain the translation, rotation and scaling of each of the nodes in your scene tree and it is because of these transforms that node positions, rotations and scalings of a parent node affects the child nodes in your scene tree.

Having said that, if these affine transforms are not updated properly between the physics world where the physics objects are located, and what is seen on the screen, weird things can happen just like what you found out.

I haven't really found the root cause of the issue, but it seems highly likely that the affine transform of the initial position of Player gets stored somewhere in the code, and then when the RigidBody's mode gets changed to BODY_MODE_CHARACTER, that affine transform gets set again to Player which brings it back to its initial position. This is why I think you found a bug in the engine -- this should not happen.

With this in mind, when I was backtracking your GDScript code I saw this:

https://github.com/godotengine/godot/blob/d2eb731878a4d660dd4a6babaea5967183b8f324/servers/physics_2d/body_2d_sw.cpp#L561-L562

Which simply means that a new affine transform gets buffered and then applied somewhere else in the code if Continous Cd just differs from Disabled in Inspector. It was then when I figured that setting Continous Cd to something else might update the Player's transform before I pressed the space bar again, and to my surprise it did.

I believe the root of the problem could be found in body_2d_sw.cpp, but I haven't looked into it.

I just want to keep making games :P

iPatso commented 6 years ago

@Rubonnek , wow thanks for the detailed response. You definitely taught me something, granted there is still a lot for me to learn. Very good to know and I appreciate it a lot!

Rubonnek commented 6 years ago

@iPatso You are welcome!

I think this issue should stay open though -- one of the core developers should take a look at this, and at least tag this.

Calinou commented 4 years ago

Can anyone still reproduce this bug in Godot 3.2.1 or 3.2.2beta4?

Rubonnek commented 4 years ago

@Calinou, on a fresh compiled 3.2.2beta4, attempting to reproduce this issue results in:

ERROR: body_set_mode: Can't change this state while flushing queries. Use call_deferred() or set_deferred() to change monitoring state instead.
   At: servers/physics_2d/physics_2d_server_sw.cpp:626.

Pretty descriptive. Imho we can close this now.

Calinou commented 4 years ago

@Rubonnek Thanks for replying :slightly_smiling_face: I'll close this then.