Let's Make A Multiplayer Game #7: Movement Synchronization
In this tutorial, we are going to synchronize the player position, as well as his animation, between all the clients. We will also synchronize the flip_h property of the player, so when a client is facing the left, it will be facing the left in all the clients. Let’s get started.
Synchronize Position
To do it, we are going to use signals. Let’s start with the position. In the player script, define a signal called “position_changed” with an argument that will contain the new position. Emit this signal at the end of the _physics_process
function, passing the current position of the player as argument. So, every physics frame, the signal position_changed
will transmit the current position of the player. But where? We have to connect the signals.
We will manage all the signals from the client script. Open it and, at the pre_configure_game function, after spawning the client player, connect the signal. Connect it to the _on_position_changed
function and print an error message if the connection fails.
Now, create the _on_position_changed
function. Inside this function, call the change_player_pos
of the server, passing the new player position as argument. Note that, this time, I use the rpc_unreliable_id
function instead of the rpc_id
function I used the other times. Since the position of the player will update a lot of times every second, I don’t care if the data is lost a few times.
rpc_unreliable
does not make sure the data is delivered correctly, so, sometimes we can loss it.
Go to the server project and add the change_player_pos
function. Get the sender id using the get_rpc_sender_id
function. With this id, get the room of the player who called the function. After that, iterate over all the players in the room. Add an if to make sure the player id of the player in the current iteration is not the one who called the function. We don’t need to update the position of the player who called the function, his player is already in the right position. Call the update_player_pos
of the rest of the players. Pass the id of the player who changed position and his new position as arguments.
I also use rpc_unreliable here. Like before, losing information sometimes won’t affect the game much.
Back at the client project, add the update_player_pos
function. Get the player information using the id as the key in the player_info
dictionary. We have the player stored in the instance
variable of this information. Update his position with the new one.
Open 2 clients and start a game. As we can see, the player position gets synchronized between the clients. However, his facing direction and animations are not updated. Let’s synchronize them too.
Synchronize flip_h and animation
Go to the player script and add 2 new signals:
flip_h_changed
with an argument with the new value of theflip_h
propertyanimation_changed
with an argument containing the name of the new animation.
We want to emit the flip_h_changed
signal when the facing direction of the player changes. That’s easy, we already have a function that is called when the player changes the facing direction, the _flip
function. Emit the flip_h_changed
signal at the end of the function, passing the value of the flip_h
property of the sprite
as the argument. We emit the signal after the flip_h
property has been updated, so it will have the current value.
As for the animation_changed
signal, the AnimationPlayer
has a signal that will be emitted when the animation changes. Connect the animation_started
signal to the script. Inside the function connected to the animation_started
signal, emit our animation_changed
signal. Pass the name of the new animation as argument.
Don’t confuse the
animation_started
signal with theanimation_changed
signal. The last one is emitted when a queued animation plays, we don’t queue animations, so this signal will never be emitted.
Connect the signals in the client script, in the pre_configure_game
function, below the line where we connect the position_changed
signal. Let’s start with the flip_h_changed
signal. Connect it to the _on_flip_h_changed
function. Print an error message if the connection fails. Next, connect the animation_changed
signal to the _on_animation_changed
function. If the connection fails, print an error message too.
Create the _on_flip_h_changed
function. Call the change_player_flip_h
function of the server, passing the flip_h
value as argument. This time, I don’t use rpc_unreliable
because we can’t lose any packet. This signal will only be emitted once when the flip_h
property changes, if the server does not receive the information, the players could end facing the wrong direction.
Create the _on_animation_changed
function. Call the change_player_anim
function of the server. Pass the name of the animation. Like the previous function, I don’t use rpc_unreliable
because it’s important that the server receives all the calls. It’s not like the position that is sent a lot of times every second, the animation signal is only emitted when the animation changes.
Go to the server project to add the 2 new functions we are calling from the client. The structure is the same as the change_player_pos
function, they tell the other clients in the room about the change.
Add the change_player_flip_h
function. Get the id of the caller and get the room with this id. Iterate over all the players in the room, excluding the caller, and call their update_player_flip_h
function, passing the id of the caller and the new flip_h
value as arguments.
Add the change_player_anim
function too. Get the id of the client who called the function. With the id, get the room. Call the update_player_anim
of all the other players in the room. Pass the id of the player that changed animation and the name of the new animation as arguments.
Go to the client project. Create the update_player_flip_h
function. Change the flip_h
property of the sprite
of the player specified by the id parameter. The player is stored in the instance
variable of player_info
.
Add the update_player_anim
function. Get the instance
of the player specified by the id
parameter, and use his AnimationPlayer
to play the new animation, the one in the second parameter.
Open 2 clients and test it. The game crashes. It says that animation_player
is null.
The problem is that the onready variable containing the AnimationPlayer
is in the Player script. But we need to change the animation of the other players too, the ones controlled by the other clients and represented by base characters. So, move the onready variable to the BaseCharacter script. This way, both of them have access to the AnimationPlayer
.
Open some clients and test it. Now, the flip_h
property and the animations are synchronized too.
In the next tutorial, we will make the players able to attack other players, and we will make them resurrect a few seconds after dying.