Let's Make A Multiplayer Game #5: Disconnections And Player List
In this tutorial we are going to implement the list of the players and handle the disconnections.
Register players
Let’s start with the server project. When a client connects we have to send all the information of the players to him and send the information of the new player to the other ones. To add a player to a room, we use the _add_player_to_room
function, that’s the perfect place to send the information to the newly joined player.
After calling the update_room function of the client, iterate over the player information we have in the players array of the room where the player is. player_id
takes the keys of the dictionary, the player ids. But the parameter of the function is called “player_id” too, let’s change it to “id”. So, call the register_player function of each of the connected clients, including the one who just joined. Send the id of the new player and his information as parameters. With this, all the clients will register the client who just joined. But we have to send the information of the other players to the new one too.
Iterate over the players dictionary again. We don’t want to send the player information of the player who just joined to himself, since we already did it in the other for loop. So, make sure the id of the player who owns the information is not the same as the one who just joined. Call the register_player function of the new player. Pass the id of the other player and his information as arguments. We can get the other player information using his id as key in the players dictionary of the room. After this, the newly joined player will have all the information of the players who were already in the room.
Now, go to the client project. Create a script for the player list VBoxContainer. Add the script to both containers: the one in the create dialog, and the one in the join dialog. Inside this script, we will define some functions to add and remove labels with the name of the players.
Create a function called add_player with a String
parameter that indicates the name of the player. Inside the function, create a new Label
, change the text of the label to the name of the player, and, finally, add the label as a child of the player list.
Create another function with the name remove_player and a parameter with the index of the player who want to remove. Free the label in the position specified by the index.
Add another function, “remove_all”, that iterates over all the labels and frees them all. We will call this function if the client disconnects to delete all the players in the list.
Next, open the menu script and add some functions to call the ones we made in the player list. Add a function called “add_player_to_ui” with a parameter that contains the name of the player. If the client is the creator of the room, call the add_player function of the player list in the create dialog. Otherwise call the add_player function of the player list in the join dialog. Pass the name of the player as argument.
Remember that we made onready variables for the player lists in previous tutorials.
Create a function called “remove_player” with a parameter containing the index of the player we want to remove. Like in the previous function, check if the client is the creator of the room, and call the remove_player function of the corresponding player list.
Finally, add a function called “remove_all_players”. As the other 2, check if the client is the creator and call the remove_all function of the corresponding player list, but, this time, hide the corresponding window too. If we remove all the players, it means we disconnected from the server, we don’t want to keep the window open.
To summarize, we will call this functions from the client script without worrying if the player is the creator or not, the functions we created will handle that.
Open the client script and add, at last, the register_player
function. Don’t forget to add remote. Add the 2 parameters corresponding to the arguments we passed in the server project: an integer for the id of the player, and a dictionary for his information. Inside the function, put the information of the player in the player_info dictionary, use the id of the player as the key. After that, call the add_player_to_ui
function of the menu script. We will only register players in the menu scene, so, we can get the menu using get_tree().current_scene
. Pass the name of the player as argument, it’s inside the info dictionary.
I opened 4 clients to test it. Create a room with one of the clients. As we can see, now the name of the player appears in the player list, below the room id. If we join with the other clients, their name will appear in the player list. Each one of the clients has a list with the name of the 4 players connected.
Well, for the moment they all have the same name, we will be able to change it when we make the player creator.
Now, if we start to close the clients, we can see that the names of the disconnected players are still in the list. That is not what we want. We have to remove the label of the players that disconnect.
Remove players
We have to notify the other players in the room when one disconnects. We can tell when a player disconnects thanks to the _player_disconnected
function. But the _player_disconnected
function only has a parameter and it indicates the player id, how do we know the room of the player? Well, we could iterate over all the rooms and search for the id, but that would be slow.
So, instead, create a new global variable called players_room and initialize it to an empty dictionary. This dictionary will associate the room id to the id of the player. At the cost of a little more memory, we will find the room of the player quickly.
At the _add_player_to_room
function, after assigning the player information in the players dictionary of the room, assign the id of the room to the players_room
dictionary using the id of the player as key. Every time we add a player, it will be added at the players_room dictionary.
In the _player_disconnected
function, check if the player id is in the players_room. If it is not, return and print some message. If it is not in the players_room dictionary, it means it has not joined a room.
This can happen when the player tries to join to a nonexistent room, we will handle this in the future.
After that assign the room id in a variable. We can get the room id using the id of the player as the key in the players_room
dictionary.
The player left the room, so we have to remove his entries from the players dictionary in the room and from the players_room
dictionary. We can do this with the erase
function. This function returns false if the entry does not exist, in that case print an error message. If the player disconnects, it must be in the dictionaries.
Next, check if the room is empty after deleting the player entry. If so, print a message and remove the room from the rooms dictionary. As before, print an error message if the erase
function does not find the room. Also, add the room id to the empty_rooms dictionary so we can reuse the room id. If there are still players, print a message too, they are good for debugging. Iterate over the players in the room and call the “remove_player” function of the players with the id of the removed player as argument.
Nice, the server is ready, go to the client project now. Open the client script and add the remove_player function. Call the remove_player function of the menu to remove the player from the player list. This functions expects the index of the player. We can get it using the find function in the array of keys of the player_info dictionary. The find function will return the index of the first entry it finds where the key is the same as the id of the player.
Next, remove the entry of the removed player from the player_info dictionary. Print an error message if erase doesn’t find the entry.
I opened 2 clients to test it. As we can see, the label with the name of the other player is not deleted when he disconnects. We have other problems too. If we reopen the closed client, the previous label names will still be there and it should ask the room id of the room we want to join. The same happens with the creator, if we close and reopen it, the labels are not cleared. Let’s fix all that.
First of all, the client is not stopped when the join dialog is closed, so, connect the popup_hide signal to the menu script. It will be stopped now.
Connect the signal to the join dialog script too. If the window is hidden, we want to reset his initial state. Hide the wait container and show the connect container so we can specify the room id again.
In the client script, add a new function called “_remove_all_players”. Inside the function, call the remove_all_players
of the menu. After that, set the player_info
dictionary to an empty dictionary, since we have removed all the players.
Call the function at the end of the stop
function. It will be called when we close the connection.
It all works now. In the next tutorial we will make the transition to the game scene.