Hour 22. Networking
What You’ll Learn in This Hour:
Basic concepts about networking for games and how to use UDP and TCP protocols to compete
Godot high-level networking
RPC system
Master and slave synchronization
With this 22nd hour, it’s time to deal with networking. Synchronizing players across the world to make them feel they play the same game at the
same time has always appeared as a daunting task. Having a multiplayer mode feature has to be considered early on in the game
development process to avoid long and painful rewriting of core components. Fortunately, Godot once again saves the day thanks to a shiny
high-level networking interface!
TCP, UDP, and Why It Matters
First things first: when talking about networking protocol, what immediately comes to mind is the TCP/IP stack that powers basically all of the
internet. The reason behind this is clear: TCP works well with the complexity of the internet (remember, it is not called “the web” for nothing!)
and makes you feel like everything is simple in the first place:
It is connection-based, so your server won’t mix the requests sent by multiple clients.
It guarantees reliability, so you’re sure to get notified if requests cannot reach their destination.
It guarantees in-order receiving.
A request can be almost as big as you want, because TCP takes care of splitting them into packets of the correct size.
All of these points are pretty awesome, but they come at a cost: latency. When a packet is lost, TCP asks the sender again, and when packet n
arrives, TPC cannot process it if packet n-1 is still in the wild. This is not a big concern for most applications on the internet (you just wait a bit
longer to get your webpage loaded), but not for a real-time application such as a game!
Enter UDP, a much rawer protocol that sends a packet to a remote computer: packet lost, ordering, sender identity, and multiple receptions of
the same packets are none of its concerns. You can use UDP for fine-tuning over handling networking. This allows you to go much faster at the
price of complexity.
This is where Godot’s high-level networking system comes in: it handles UDP complexity for us and integrates it inside its Scene system,
making a multiplayer game seem like a single-player game where you set each node to either synchronize with a remote player or send
synchronization information about its current state to other players.
NOTE
Use Raw UDP/TCP
In addition to its high-level networking system, Godot also offers classical UDP and TCP client/server capability with StreamPeerTCP,
TCP_Server, and PacketPeerUDP classes. This is useful if you want to write your custom game server or connect to a third-party API.
Managing Connections
Enough with theory! To connect multiple Godot instances, we need to configure one of them as a server and connect the others as clients to it
(Listing 22.1).
LISTING22.1 Creating Connection
Click here to view code image
# For the server
func hostgame(port):
var host = NetworkedMultiplayerENet.new()
host.create_server(port)
get_tree().set_network_peer(host)
# For the clients
func join_game(ip, port):
var host = NetworkedMultiplayerENet.new()
host.create_client(ip, port)
get_tree().set_network_peer(host)
We create a NetworkedMultiplayerENet instance, configure it as a server or client, and define it as the one to use for our Scene Tree. Note
that the Godot network API is defined in the abstract NetworkedMultiplayerPeer class, from which NetworkedMultiplayerENet is an
implementation using the ENet library.
Once our network peer is defined on eachGodot instance, we are notified of network activity by signals:
network_peer_connected: A new Godot instance joins the server. This signal is triggered on each peer (not only on the server) and
111111111
22222222the newly connected ones get this signal numerous times (one per peer is already connected before it arrives).
network_peer_disconnected: Triggered on each peer when someone left (except on the leaving peer).
connected_to_server: Triggered on the newly connected peer once connection has been achieved.
connection_failed: Triggered when the connection has failed with the server.
server_disconnected: Connection with the server has been lost, so other peers get a network_peer_disconnected signal.
NOTE
Beware of Firewalls
Be careful when choosing a port for your game! While it is easy to remember, low number ports (like TCP 80 used by HTTP) are most of the
time only accessible to users with advanced privileges (e.g., administrator). So, you should choose a port higher than 1023 and make sure it is
not already in use by some other application.
Finally, when you want to close the connection, simply remove the network peer from the Scene Tree (Listing 22.2).
LISTING22.2 Closing Connection
Click here to view code image
func finish_game(port):
get_tree().set_network_peer(null)
Note that this works the same for both the client and server sides.
Remote Procedure Call
Let’s make our multiple Godot instances talk! For this, we can use what is called RPC (Remote Procedure Call), which consists of triggering
from a peer the call of a procedure on one or multiple other peers. Be careful that you are talking of “procedure” and not “function,” because the
second procedure doesn’t return any value to the caller (so it’s a fire-and-forget call). Godot provides the following RPC methods:
Node.rpc: Regular RPC call; use it to call one of the node’s procedure.
Node.rset: Same as RPC, but used to remotely set a node’s property.
Node.rpc_id/rset_id: Instead of sending the procedure to all of the peers, we can use those methods to only send the procedure on a
given peer by providing its network ID.
Node.rpc_unreliable/rset_unreliable/rpc_unreliable_id/rset
**
unreliable_id: Finally, these are the unreliable versions of the
precedent methods. This means it’s possible your procedure won’t be received at all (remember how UDP works?).
Remote and Sync Keywords
That said, RPC can be a dangerous tool if not well controlled: what would happen if you allowed a complete stranger from across the globe to
call any function of your Godot instance? (Remember Hour 12, when you had access to the entire user’s filesystem from Godot?) To prevent
anything like that, a function (or a property) must be explicitly flagged as allowed to be called by RPC with a keyword (Listing 22.3):
sync: the procedure is called by all peers (including the caller).
remote: the procedure is only called on the remote peer and not on the caller.
LISTING22.3 Using Remote and Sync Keywords
Click here to view code image
var speed = Vector2(0, -200)
sync var alive = true
remote func update_pos_for_remotes(new_pos):
position = new_pos
func _process(delta):
if is_network_master():
position += speed * delta
if position.y < 0:
position.y = 0
rset(“alive”, false)
rpc_unreliable(“update_pos_for_remotes”, position)
Note: Beware of network congestion.
Keep in mind that using RPC, even unreliable, on each frame is costly and won’t scale well beyond the local network or a few player games, so
use the fixed timer for such a task.
Listing 22.3 illustrates how to use these keywords: we have a node falling down, and when it hits the ground, it gets killed. Once per frame,
process gets called to update the state of the node on the peer responsible for it (that’s what is_network_master() tells you, but more on this
later).
Now this peer needs to tell the other ones about the new position of this node. Thus, it uses the RPC call on update_pos_for_remotes, which
updates all of the peers except itself. Note that we use rpc_unreliable here given that the call is done for every frame, so missing one is not as
much of a concern.
111111111
22222222Finally, when the node hits the ground, we need to set the property alive to false. This time, even the peer responsible for the node must have
this property updated, so we flag it as sync and let the rset call update every peer, including ourselves at the same time.
Slaves and Masters
Even if remote and sync are pretty cool, they fall short on something: exploit protection. Given that any peer can do a RPC on remote or sync
procedure that will impact all the other peers, it’s pretty easy for a client to impersonate the server and trick the game.
In our previous example, we could have a malicious client using the rset (alive, false) to kill a node even if it is not responsible for it.
To solve this, Godot has a concept of master/slave that is set on a per-node basis for each peer. Remember the is_network_master() from
the last paragraph? It returns true if the peer on which the code is executed is in charge (i.e., the master) of the current node.
By default, the server is set as master of all the nodes in the Scene Tree. Later on, when clients start connecting, they can agree that a node is
managed by a specific peer. This means you should make sure a node has only a single peer declared as master; otherwise, strange things
will happen.
Also note that the default set_network_master() configures a node slave/master property (Listing 22.4) as well as all of its children in the
Scene Tree, but you can disable this behavior by setting false as a second parameter.
LISTING22.4 Declaring a Peer Master of a Node
Click here to view code image
var player_scene = preload(“res://scenes/player.tscn”)
func peer_joined(peer_id)
var player = player_scene.instance()
get_tree().get_root().add_child(player)
player.set_network_master(peer_id)
Now you can use is_network_master() (Listing 22.5) to separate the task that should only run on the master, typically processing a player’s
user input.
LISTING22.5 Filtering Input Processing Depending on Node’s Master Property
Click here to view code image
func _update(delta)
if is_network_master():
if Input.is_action_pressed(“ui_up”):
# Do something
Mixing sync/remote and is_network_master() can be cumbersome, so the master and slave keywords have been created to simplify
things. As you can guess from their names, a procedure flagged with master is only executed on the peer declared as master for the node. On
the other hand, slave is executed on the other peers (Figure 22.1).
FIGURE 22.1
Typical live Scene Tree view of each peer with master (in green) and slave (in red) nodes.
This allows really powerful synchronization patterns in which a master gets notification from slaves (with the latter using RPC on a master
procedure) and sends a synchronization command to them (RPC on a slave procedure) while being sure no malicious slave can impersonate
himself (a slave using RPC on slave procedure gets only called on itself!).
In noncompetitive or co-op games, exploits are a small concern. Depending on your use case, it can be necessary to avoid using remote and
sync keywords, given how they can be used by any slave and offer no protection against malicious use.
LISTING22.6 Replacing Sync by Slave
Click here to view code image
slave func update_score(new_score)
score = new_score
func _on_player_killed():
if is_network_master(): # Score should be handled only by the master
rpc(“update_score”, score + 100)
update_score(score + 100)
A common pattern to replace sync is to declare your procedure as a slave (Listing 22.6), then call it both as RPC (to synchronize all of the
slave peers) and directly as a regular function (to execute the function locally). This way, you’ll get the procedure called everywhere when
triggered from the master without the risk of exploits when used by a malicious slave.
TRY IT YOURSELF
Test the Synchronization
A great way to understand networking is to try it on a really simple project:
111111111
222222221. Create a very basic GUIwith a couple of buttons and a label. See Figure 22.2.
FIGURE 22.2
A basic GUI.
2. Connect the functions provided in Listing 22.1 to the client/server buttons.
3. Create a _update_text function, adding a line of log to the Label and multiple functions calling this _update_text, while flagging
with different network attributes. See Figure 22.3.
FIGURE 22.3
Finding a static mesh asset in the content browser.
4. Connect the ping button to a function making rpc, rpc_id or direct call of our network function.
5. Finally, launch your project and test with multiple configurations. It’s really informative to mess around to see what happens (like
having multiple servers or setting multiple peers as master).
Visual Script
As you just saw, networking is a lot about scripting: you must dynamically configure who is slave and master (given peers are not known
beforehand), then use RPC to call script functions as a procedure. So far, we’ve only showed examples withGDScript because it is the most
common way to script withGodot, but this doesn’t mean you cannot use VisualScript for networking (Figure 22.4).
FIGURE 22.4
Using networking with VisualScript.
As you can see in Figure 22.4, VisualScript provides an RPC dropdown menu to configure the network attribute on a function. To call the
procedure, you can use the CallSelf box configured on the RPC method (this is exactly the same as calling Node.rpc withGDScript).
Summary
In this hour, you learned about networking and how Godot simplifies this area through its high-level networking system. You saw how to
configure and connect client and server. Then you learned how to use RPC to synchronize across network peers. Finally, you learned how to
use the keywords sync, remote, master, and slave to decorate your procedures and use more powerful synchronization patterns by using the
per-peer and per-node master/slave properties. Networking is a complex beast with many concerns (typically designing your procedures to
avoid exploits from malicious clients, or keeping the amount of data low to synchronize and avoid latency), but you already have a good
overview of what Godot has to offer.
Q&A
Q. Is there a way to determine ahead of time the network ID of the peers?
A. Server ID is always 1, so it can be safely hardcoded in your game; however, clients get unique, randomly generated IDs at connection
time.
Q. How can you determine if the current node is master or slave?
A. You can use the Node.is_network_master() method, which returns a Boolean.
Q. Can I set up a dedicated server for my game with Godot?
A. By default, Godot ships as a full-grown game engine with 2D/3D renderer and audio engine. This is perfectly normal when used by a
player, but gets really annoying when you want to make it run on a headless server without GPU and a fraction of the CPU and RAM of a
modern gaming PC.
However, a server version of Godot is available on the official website (it currently supports Linux 64bits, which is a great choice as a
server).
Workshop
See if you can answer the following questions to fix your knowledge.
Quiz
1. What happens if you do a RPC on a sync procedure from a slave node?
2. What is the difference between master/slave and client/server?
111111111
222222223. Can I have two masters on the same node in my game?
4. What happens if I don’t explicitly define who’s the master for a node?
5. Given a sync func foo() function, what is the difference between foo(), rpc(‘foo’), and rpc_id(1, ‘foo’)?
Answers
1. The procedure gets called on all the peers (including ourselves).
2. Server/client is only a matter of a network connection. Master/slave defines which peer manages a given node and sends synchronization
to the others.
3. Strictly speaking, you can, but both peers will refuse to execute slave RPC and won’t send master RPC through the network, so your
game will desynchronize pretty fast. So don’t do that.
4. By default, the server is the master of all nodes.
5. foo() calls the function locally only, rpc(‘foo’) calls the function locally and on each peer (given function is marked sync), and rpc_id(1,
‘foo’) calls the function only on the remote peer with ID 1 (i.e., the server).
Exercises
The best way to work with networking is to take an existing single-player game and convert it to a multiplayer game:
1. Modify the space shooter from Hour 5 to add a lobby so that the game only starts when a client connects to a server. Once started, client
and server each have their own game independent from each other.
2. Now start synchronizing by providing a spectator mode so that only the server process with which the player inputs and synchronizes
game state is a connected client. This is done by flagging its functions as sync and using RPC instead of direct calls.
3. Finally, provide a co-op mode: when a peer connects, add a new ship to the scene (a new peer should be master of this ship node).
Don’t forget to check is_network_master() **before processing player input to have a peer controlling only its own ship.
111111111
22222222