godotengine / godot

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

RigidBody2D doesnt get updated properly with MultiplayerSynchronizer #76299

Open Lodugh opened 1 year ago

Lodugh commented 1 year ago

Godot version

4.0.2

System information

Linux Mint 21.1, Vulkan, GTX 1660Ti

Issue description

On the clientside the RigidBody2D constantly jumps back to the original position if you try to synchronize its velocities, position and rotation with a MultiplayerSynchronizer. There is no issue like that with RigidBody3D.

Steps to reproduce

Set up a multiplayer scene as described in the blog post: https://godotengine.org/article/multiplayer-in-godot-4-0-scene-replication/ Then instead of the 3D scene recreate it in 2D and without players like that: Add a RigidBody2D with collision on top of a StaticBody2D and make it bouncy(to have some movement). Then add a MultiplayerSynchronizer and synchronize linear_velocity, angular_velocity, position and rotation with it. If you now host an join you can see the issue on the client window.

Minimal reproduction project

scene_replication.zip

pennyloafers commented 1 year ago

This is a know limitation to Godot in general. The documentation suggests not changing physics attributes directly instead using integrated force functions. The issue is that there is a physics body resource owned by the Physics server that is happening elsewhere in the loop. The work around is to use the RID of the RigidBody with the PhysicsServer and get the physics body state and synchronize that data. It's a little cumbersome, but This should solve the jumping around bit.

There is also the potentially a another work around if you change the multiplayer synchronize loop parameters. Maybe if you use the process loop it will happen before the physics loop...

JanneKro commented 1 year ago

I think I am experiencing the same thing trying to synch mere sprites. The server gets the client's sprite, but its position is not updated when moving around (changing the position property). Is this also an expected limitation?

pennyloafers commented 1 year ago

@JanneKro if it isn't a physics based node then syncing the position should be okay. From what you have said, it seems like your expecting the movement of the client sprite to update the server. This can be done, but you need to give the sync node on the server its multiplayer_authority to the client, and the client also has set authorization on its copy of the sync node.

The main problem I see with this approach is that all sync/spawn nodes will be authed to the server by default. It would be better practice if you only authed a MultiplayerSynchronizer that sent client input data to the server. Allowing the server to make the sprite position adjustment and syncing the position back to the client.

The main reason for all of this is to limit vectors for cheating from clients.

pennyloafers commented 1 year ago

@Lodugh this is how i set it up for 3d:

extends MultiplayerSynchronizer

# used to create ordered unreliable udp packets
@export var frame : int = 0

# syncing bodystate provides smoother physics movement.
@onready var sync_object : RigidBody3D = get_node(root_path)
@onready var bstate : PhysicsDirectBodyState3D = \
    PhysicsServer3D.body_get_direct_state( sync_object.get_rid() )

# from here you can pull transform information from the bstate in any process loop function (_process, _physics_process) on the server.
# you can send the whole transform, which is costly, as the packet encoding isn't fully optimized in Godot.
#  you can shave off some bytes if you choose  at least these: [origin (position), linear velocity, angular velocity, rotation]
# then use the synchronized() signal event on the client node to set the received info onto the clients bstate.

the cool part here is that bstate is a reference, so once you apply the remote data onto the client's PDBS3D your job is done

theromis commented 6 months ago

@pennyloafers on 4.3-dev5 trying to make my physics game smoother with your approach also combined with https://www.reddit.com/r/godot/comments/180ywzs/multiplayersynchronizer_and_rigidbody/ so on my side it looks approximately like:

extends RigidBody3D
class_name Player

@export var replicated_transform : Transform3D
@export var replicated_linear_velocity : Vector3
@export var replicated_angular_velocity : Vector3
var prev_replicated_transform : Transform3D
var prev_replicated_linear_velocity : Vector3
var prev_replicated_angular_velocity : Vector3

func _integrate_forces(state : PhysicsDirectBodyState3D) -> void:
    if multiplayer.is_server():
        replicated_transform = state.transform
        replicated_linear_velocity = state.linear_velocity
        replicated_angular_velocity = state.angular_velocity
    else:
        if replicated_transform != prev_replicated_transform:
            state.transform = replicated_transform
            prev_replicated_transform = replicated_transform
        if replicated_linear_velocity != prev_replicated_linear_velocity:
            state.linear_velocity = replicated_linear_velocity
            prev_replicated_linear_velocity = replicated_linear_velocity
        if replicated_angular_velocity != prev_replicated_angular_velocity:
            state.angular_velocity = replicated_angular_velocity
            prev_replicated_angular_velocity = replicated_angular_velocity

and ServerSynchronizer synchronizes like this with replication_interval = .25:

Screenshot 2024-03-31 at 20 54 00

still don't understand is it right or wrong but with this approach it constantly jumping back and forth finally loosing synchronization and just infinitely falling.

Could you help me with ideas how to make it smoother?

@Calinou I know you the men, maybe you can direct me in the right direction?