#Implementing Server-Authoritative Movement in Godot 4.2 for a 3D Multiplayer Game

2 messages · Page 1 of 1 (latest)

shadow socket
#

I'm currently developing a 3D multiplayer game using Godot 4.2 and have hit a bit of a snag regarding implementing server-authoritative movement, despite having made some progress. Here's a breakdown of what I've achieved and the challenges I'm facing:

What I've Done:
I've been using PlayerBody3D for move and slide functionality to handle player movement.
I've implemented client-side prediction to some extent, allowing players to move smoothly on their end.
My Understanding So Far:
To transition to server authority, I believe the next steps involve sending player inputs (alongside a timestamp or input order) to the server, which also stores them locally.
The server then processes these inputs. However, it's unclear to me whether this processing involves simulating the movement on the server or using a method like move and slide on server-side objects.
Where I'm Stuck:
Handling the server response: When the server sends back the confirmed state, there's often a noticeable lag, leading to unhandled input. If a player's position is adjusted to match the server's, how can I properly simulate movement (like move and slide) over time to correct any discrepancies?
Currently, my workaround involves players sending their positions to the server, which then broadcasts these positions to ensure synchronization. However, this method leaves room for cheating, as players could manipulate their client code to alter movement speeds or gravity.
Questions for the Community:
How can I effectively simulate or process player movement on the server side to maintain server authority, especially regarding using move and slide or an equivalent method?
What strategies can I employ to manage and correct for the delay in server responses, ensuring that player movement remains smooth and accurate according to the server's state?
Are there recommended practices within the Godot community for securing player movement against cheating, specifically when trying to shift from a client-authoritative to a server-authoritative model?

func handle_movement(_delta):
    if !is_on_floor():
        velocity.y -= gravity * _delta
    if Input.is_action_just_pressed("move_jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY
    var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
    var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized().rotated(Vector3.UP, $MeshContainer.rotation.y)
    if direction:
        pathing = false
        velocity.x = direction.x * SPEED
        velocity.z = direction.z * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
        velocity.z = move_toward(velocity.z, 0, SPEED)
    move_and_slide()
    if !Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) and !Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and input_dir != Vector2.ZERO:
        reset_camera()            
shadow socket
#

The Challenge:
In a server-authoritative setup, ensuring smooth player movement can be quite tricky, especially when dealing with the inevitable network lag and the need to correct player positions based on server data. Directly adjusting player positions to match the server can often result in jittery or unnatural movement.

The Breakthrough:
The solution I discovered revolves around the creative use of velocity dictionaries. Here’s a step-by-step breakdown of the method:

Storing Velocities: First, I found that by storing each player's velocity in a dictionary, I could keep a record of their movement over time. This method allows for a more detailed account of player actions than simply updating positions.

Jump Setting Positions: When the server sends an updated position (indicating where the player should be), instead of directly teleporting the player and calling it a day, I use this position as a starting point for the next step.

Reapplying Velocities: I then iterate through the previously stored velocities in a for-loop, setting the player's current velocity and calling the move_and_slide function for each entry. This effectively "replays" the player's movements from the last known position, adjusting their course to match server data without visible disruptions.

Handling Special Cases: In my case, I also needed to integrate stair handling functions to ensure the movement corrections didn't interfere with the game's physics, like climbing stairs or navigating uneven terrain. Depending on your game's mechanics, you might need to adjust this step to fit.

The Result:
This method results in what feels like seamless movement from the player's perspective. Even though, behind the scenes, the character might be "teleporting" to adjust to server data, these adjustments are smoothed over by the reapplication of move and slide inputs, making the corrections practically invisible.