You can check full source code here:
https://github.com/BitR13x/2DGodotMultiplayer
Keep in mind that my code isn’t perfect and can be definitely upgraded, but still it can save you some time.
Server-side vs Client-side
It’s important to know the difference.
When you understand it, everything else will become easy.
If you know what is server and client you already know what is sever-side and client-side, but in the simple term, but in the simple term:
Server-side refers to operations that are performed by the server.
Client-side refers to operations that are performed by the client.
When you encounter any problem when something isn’t synchronizing, ask your self, “Is it server-side or client-side?”
It will help you in most cases.
I would recommend to look up some docs before we start.
RPC
We will use RPC function to send over data or calling functions.
There is two types of RPC:
Reliable: when the function call arrives, an acknowledgement will be sent back; if the acknowledgement isn’t received after a certain amount of time, the function call will be re-transmitted.
Unreliable: the function call is sent only once, without checking to see if it arrived or not, but also without any extra overhead.
Two Approaches To Make Multiplayer
The 1st one is running server which is not running instance of the game and will sync clients.
The 2nd one is similar to first one, but the server actually running instance of the game.
There isn’t very big difference between them, only when you want to do the first approach you will need to create 2nd project.
We will be using the 2nd approach.
World (Map)
First we will start with creating a world node and also we need add some Position2D, it will help us determine where to spawn players (so they wont spawn on themselves), you can also fix that by code.
Our structure should look like this:
This will be our holder for other objects (enemies, players, etc…)
Menu and Server
We will create new scene (user interface will be “Root Node”) where we need input field(LineEdit) for IP address and button for joining and 2nd button for creating server.
Scene Structure:
Signals
signals allows object to react to a change in another without them referencing one another.
We want to create server when we click on the CreateServer button and join when JoinServer button.
Pretty simple huh?
First we attach script to Network_setup, then we will attach signal to the script from buttons.
We will click to the button switch to Node and we will connect signal pressed() and connect it to Network_setup node which will add function _on_ButtonName_pressed().
Create a server and Join server
Okay now we got Menu and now we just need server, first we need to create global Script, create a script and go to Project Settings -> Autoload and select path to the script and click add (you can name it as you want, but keep in mind that I’ve named it “Network”).
This is whole script for server, there you can see bunch of functions, but we are interested in join_server() and create_server(), these functions using the High-level multiplayer functions which creates server for us.
In the join_server() function is also timer which we are setting in _ready() function which is just taking care of connection timeout.
Now we will update Menu script and just call join_server() or create_server() function according to what was pressed.
And also we connect signals when _connected_to_server, etc… it will be important later on (now you can try running two instances, if you can connect it should response to output with player id).
Player and Camera
I will use only “icon.png”, but you can download some assets and animate them it’s up to you.
We will create next scene with KinematicBody2D as parent, CollisionShape2D, Sprite(texture) and Camera2D as children.
What is kinematic body?
Kinematic body is special type of body that are meant to be user-controlled.
Scene Structure:
click on the Sprite and drag over into texture your texture in the CollisionShape2D you must setup RectangleShape2D.
Movement
Okay lets move into coding, but first go to Project Settings -> Input Map and setup keys to move around.
I will use _process(), for smoother movement.
Nothing to say here except function move_and_slide(velocity) is setting the position and allowing us to move and collision with other objects.
Turning it into multiplayer, we need somehow send over to all players our position and where we are moving, for this we will be using puppet var and setget.
You will need to add timer and connect signal timeout() which will be sending packets through the network, we will be using rpc_unreliable, because we just need to keep sending the packets as fast as possible.
You may also notice I added Tween, it will help soften the movement.
When someone connect to you and he start moving he is actually teleporting and you want reduce this feeling by Tween and when lagged completely, it will keep moving in the same direction.
Instancing Player
For this we will create global scene with node which will be our holder for players.
Again create scene with just Node and go to Project Settings -> Autoload and select path and click add (I named it Persistent_nodes).
I would create 2nd global script with function which we can use through Project (I named it “Global”).
And I would put this inside that script:
By this function we will instance player, when we connect to server and when we create server, there isn’t any collision with sync so we don’t have to use any special functions.
I actually added also timer so it can load for the player which is connecting to server.
And also now we need to manage when the player disconnects or player object will be still there and no one will be able to move with him.
We just check Persistent_nodes if there is player with that id and just remove him by “queue_free()”.
Final version of Menu.gd:
I added Control Node so I can hide when we create/join a server.
Camera fix
What’s wrong with camera?
Instancing a player will add he’s own camera and it will focus new camera instead yours.
How to fix it?
Pretty simple, just when instancing new player check if the local system is the master of this node, make camera current (but we will need to wait one frame, because we are setting the master inside instance_player() function little bit later on).
Finally Switch To Game World
We will create another button for starting game and connect signal from the button to the Menu.gd which will be calling RPC function which will call sync function switch_to_game().
Now we will create World.gd script and attach it to World(Map), here we just loop through our Spawning_nodes update position of the player according the node
Should look like this, we need also handle _player_disconnected().
Enemy
Enemy will be similar to Player, but we will need some to add two more timers and two Area2D.
We are going to use Area2D to determine if player inside damage and 2nd Area2D for switching targets if someone will cross our enemy.
Timer will be our attack cool-down.
Here you can go pretty insane, but we are going to stick to the simplicity.
Scene Structure:
Coding
Here will not be anything new, same as player except now we are using _physics_process() and movement is not by keys but from direction_to() function which will return vector which will be pointing to our position.
And we will need to add player into player group, because when enemy is created it will call the function on him self.
Click on the KinematicBody2D -> Node -> Groups.
Also you can notice the loop which will loop through players and if player inside Area2D and timer stopped it will damage our player, but we didn’t declare this function, so let’s repair that.
Likewise the movement, we are going to use puppet var and setget to keep track of health and when we reach 0 health, we call sync function which will free our player.
Now everything should work just fine, but when enemy is created it don’t know who to target.
We can fix it by keeping track of alive players in global script or we can just loop through Persistent_nodes and player select from there (you can make it random).
BONUS
Random Number Generator
what is the problem here?
Well if you create it only on client-side it will generate completely different numbers so be careful, it could introduce some issues.
You will need to use “seed()” function which sets the generation
(you can know this approach from “Minecraft”)
We just generate random integer and send it into sync function which will apply that seed and now we can generate random numbers, because we are using the same seed on the client-side and server-side.
Enemy Spawner
what is the problem here?
When you are instancing enemy in multiplayer, you need to sync everything also the name.
If you don’t, simply they won’t sync.
Conclusion
We created game which provides movement, enemies and it’s all sync with multiple players, from this point you can create GUI, story, etc…
If you still didn’t notice yet, just create single player and on it add some functions which send over data and you got multiplayer.
It’s also good to mention that sorting the folder structure brings some clarity in your project, but I created just small project so it’s no that big need for me.
Thank you for reading and hopefully see you in the next article.