Download Documentation API Reference Samples Asset Store Donate🡕




Networking


~~ Networking Concepts ~~



This section will cover basic networking concepts, not just those applicable to video games.
There are many resources covering these topics so I will try to give the brief rundown as is relevant to video games.
This is an oversimplification, but should give you some idea of how things work.

You can skip this section if you are familiar with networking basics.



How Data Goes from Point A to Point B



When two computers try to send data to each other over the internet that data must have some way of findings its way to the other
computer. The protocol for doing that is known as Internet Protocol (IP). Data is transmitted via *packets*. These packets contain
not only the data that the user wishes to send, but extra information in *headers* that contain information on where to send that
data to and more. When a sender, the *source host*, tries to send data to the *destination host*, it includes an *IP address* in
the packet header. This address can be thought of as the destination house address.

However, is it usually not the case that there is a direction connection between the two computers / *peers*.
Instead the packet is sent to some other in-between computer that then sends it to another in-between computer and so on, until it
reaches the destination. These *hops* between computers may be unreliable. The internet was built to withstand large scale
disruption by allowing packets to take multiple *routes* between the source and destination, but even with that packets may
still be *lost*.



IP Addresses



There are two main types of IP addresses you will probably encounter, IPv4 (INET), and IPv6 (INET6). In the past IPv4 had enough
possible address values for all the computers on the internet, but as the internet rapidly increased in size, they ran out of
IPv4 addresses, this oversight led to the creation of IPv6, which has more than enough possible values. But much of the software
running on the internet was programmed to work with IPv4 already, and so to prevent having to rewrite a ton of software internet
service providers started adding *subnetworks*.

The idea is that you can have a computer with one IPv4 address that accepts packets and distributes it to a
bunch of computers all on the same *network* that each have an address that is only valid within that network.
This led to the current structure of the internet consisting of networks of networks. And using IPv6
does not overcome this new problem, since the physical network structure is not changed by choice in address type.

Since personal computers now each exist on their own *local area network* (LAN), and talk to computers on the
*wide area network* (WAN), we now need to go through the *router* that manages communication between the LAN and the WAN.
This also means that there are now two IP addresses involved, the private address (inside our network), and the public address,
which is our address as seen from outside our network. There are services that can tell you what your public address is, for
more on that look into STUN, TURN, and ICE.

There are also several more layers in between us and the LAN, but those are outside of our control. We often need
to configure our routers to allow certain traffic through. This usually comes in the form of *port forwarding* when hosting a server locally.



Ports



In addition to IP addresses telling us where to send information to, we may also have multiple destinations at that destination
computer. If you are running two different networked applications on the same computer then the packets need to not only know
which computer to go to, but which application on that computer once it gets there. This is what the *port number* is for.

Each application is assigned a port number that is required in addition to the address. If a server is hosting some application on
some given port number it will accepts packets targeted at that port number. But if it's part of a local network, the router does
not know that, and so it needs to be configured to allow packets with the target port number to pass through. This is what port
forwarding is, we are forwarding our port through the router. Routers typically have a website that can be used to configure them.
You can access that website using a web browser. The address is some local network address, such as "192.168.0.1". You can find
instructions for your specific router online. If you are hosting on a server provided by some service, you will probably need to
configure some network traffic / firewalls rules, similar to port forwarding.



Communication Protocols



There are two widely used protocols for transmitting data on top of IP. They are the User Datagram Protocol (UDP), and
Transmission Control Protocol (TCP). A protocol being the agreed upon way to communicate between computers. UDP is the more raw
way to communicate, it leaves it up to the user to do what needs to be done. UDP does not have connections, establishing a
connection is left up to the user. UDP simply tries to send the given data to the destination, and it may fail, or arrive out of
order.

TCP does have connections, and many other things handled for the user, including reliable, order communication. The way it
achieves this is by sometimes resending packets until it gets confirmation from the other computer that it received the data.
It also assign ordering values to packets so that it can put them back in order on the receiving end. TCP seems a lot better
than UDP, but it comes with downsides. The resending behavior is not ideal for realtime video games due to it causing
high latency spikes.



References



+ https://en.wikipedia.org/wiki/Internet_Protocol
+ https://en.wikipedia.org/wiki/Local_area_network
+ https://en.wikipedia.org/wiki/Wide_area_network
+ https://en.wikipedia.org/wiki/Port_(computer_networking)
+ https://en.wikipedia.org/wiki/Port_forwarding









~~ Game Networking Concepts ~~



There are different game networking models for different types of games. This section will cover some of them.
You can skip this section if you are familiar with game networking concepts.



Multiplayer Preparations



Adding multiplayer after the fact to a game may be infeasible. It's a good idea to think about multiplayer from the beginning.
However, not all the details need to be thought out from the beginning. With good preparation adding multiplayer can be made much
easier. Making the game logic mostly deterministic or entirely deterministic in the case of deterministic lockstep networking can
make adding multiplayer much easier. To achieve this it is important to have all the game's logic execute in a fixed timestep
update. Which means that the delta time used in physics processes, timers, etc is fixed, rather than variable.

Another important preparation is to have some convenient way of copying input and game state and saving it (in memory to a buffer).
This is required by several networking methods that require rewind and replay of the game. To achieve this it is recommend for
your entities to have a copy implementation and for the visual aspect of your entity to be separated from the game logic state.
If your game can run without any display (headless), it's a good sign that these two parts have been separated.

Finally, it's important to make sure that your game logic (not graphics) can run faster than realtime. This is needed for when a
networking method needs to replay game state. If the game struggles to run at the target framerate then it will have a hard time
with multiplayer. Make sure there is still a good chunk of compute still available for use.



Sending Inputs



One option is to only send player inputs across the network. This can either take the form of a "dumb terminal" or a
*deterministic lockstep* game. In the "dumb terminal" setup, each player sends their input to a server that is running the actual
game, and the servers sends back render information telling the clients how to display its game state. The clients themselves are
not actually running the game itself, they simply act as frontends rendering the game state to the user. In deterministic lockstep,
each peer sends their player inputs to each other, and use that to advance the game state. Each runs the game locally, see the
deterministic lockstep section for more. The main advantage to sending only player input is that it greatly reduces network usage,
and does not depend on the complexity or size of the game simulation, allowing the game simulation to be as big as is possible for
each client to run. It's CPU/GPU bound, rather than network bound.



Sending Game State



Another option is to send game state across the network. In this setup one peer is the server that has authority over the game
state. The server sends game state every time an action is taken for non-realtime games or at a fixed rate for realtime games.
The game state data sent over the network usually consists of an array of entity data and a unique ID number for each.

This ID number is the same on both the server and the client, and the client uses it to map the entity data it receives to
the associated entities. The client updates their local entity data with this new information. This approach is network bound,
the more entities that need to be send across the network, the more bandwidth is required. A game with too many entities will not
be able to use this approach. However, this approach is standard and will work well given that the amount of data being sent does
not become too large. If you are unsure whether to send just inputs or game state, sending game state is recommended as sending
just inputs places extra constraints on the game's code, and bandwidth has been increasing over time and continues to increase.



Client Authoritative



In client authoritative games the clients can determine their own player's state. They calculate their player state locally and
send it to the other peers/server.  This approach can simplify the networking code a lot for a game, but opens the game up to easy
cheating since each player is in full control of their own state. However, many games manage to get away with this approach and if
your game is not a highly competitive game, or you know that each player you are playing with is trusted, then it can be an
optimal choice in terms of complexity. Games that will use this method include party games, and self host / private server games.
This method is not well suited for a game that wants to have match making between strangers.



Server Authoritative



Server authoritative games have the server by the final authority over what the current game state is. Players may send their
inputs and/or state, but it's up to the server to choose what to do with that. Each client receives updates from the server and
use that to update their local state. If their local state does not match that which was received from the server, they use the
server's given state. This method does not allow for easy cheating via manipulation of player state or network packets and is well
suited for most games, but comes with a major downside of increased complexity. However, it's the ideal solution and should be
preferred given enough development time and budget. It requires having either dedicated servers hosting the game or allowing
players to host the game themselves by providing the server executable in addition to the client to users.
If you want your game to be competitive, or allow for match making between strangers, then server-authoritative dedicated servers
is the best choice.



Deterministic Lockstep



Deterministic lockstep is one of the main networking methods used in video games. It involves sending inputs between peers and
when they are ready, stepping, meaning running a single frame of the simulation with the given inputs. If each peer involved
has a simulation that gives the *exact same result* for a given starting state and inputs, then each peer will have the *exact
same simulation* running on their local machine and all peers will be in sync with each other. If there is even a slight
difference in how the state is calculated on each peer, their states will desync over time. This desync can happen much faster
than expected from only slight deviations in the calculations.This puts a great burden on the developers of the game to ensure
that their simulation is deterministic *across different devices*. This can require some drastic changes in how a game
is programmed.

For example, floating point operations do not always produce the same results on different devices and therefore floating point
operations can't be used. Instead games will use just integer types, and either use software fixed point or floating point
arithmetic,similar to how games used to before floating point hardware existed. In Python, there are some options to help with
this such as the decimal module. Lesser drastic changes are also required, such as making sure that all peers have the same seed
set for pseudo random number generation. Given that the developer is willing to put in the effort to make the simulation
deterministic, there is one other downside to consider. Since game state is not being sent across the network it implies that a
new peer can't join mid-game, unless game state is sent and then they catch up to the current state with the rest of the peers.
This may seems like some big downsides, and they are, but the reward is something that can easily run peer to peer, requires
almost no bandwidth that is constant in relation to the number of entities, and having a deterministic simulation allows for
easier debugging/replay/saving systems. This method is commonly used by realtime strategy games where the game state is too big to
send over the network, and fighting games due to them historically being deterministic because they used to be arcade games
without floating point hardware and they want to allow for easy peer-to-peer (no server) networking.



Snapshot Interpolation



Snapshot interpolation is likely the most commonly used method today. It's a method in which after receiving the game state the
peers also interpolate between states. The interpolation is used to *visually* smooth out state between updates. If a peer is
receiving game state updates at a fixed rate of 20 times per second, then if the peer simply updates their local state with that
new state and displays that state, the entities will by teleporting rather than moving smoothly between points.
Instead the peer keeps a circular buffer of updates received and interpolates between two of them smoothly.
This gives the player the illusion of a smooth experience.

It also implies that the interpolated objects are not the current state of the game, but rather a past version of it, and so
the user is seeing an old, and interpolated, version of the game state. However, if the time between updates is not too large
such that the interpolation time is low, this will be only slightly behind and usually not become a game breaking issue.
But in a fast paced game in which the players need to be precise about how they interact with such interpolated objects,
additional work may be required in the form of *server side lag compensation* to make up for this gap in time, in addition to the
already existing gap of network latency.



Server Side Lag Compensation



To make up for network latency, and added latency of interpolation methods. The server/peers may decide to give some slack to
a player's actions. To explain this, consider a first person shooter game with snapshot interpolation. When a player aims at
another player they are aiming at a past version of that player, the actual player state on the server is ahead in time due to
network latency of sending the player state to the client, and additional latency of interpolation.

Then when the player decides to shoot, there is even more latency from the player's input packet taking time to reach the server.
When the server receives the player's input to shoot, it can take into account this latency and decide to rewind the players by
an estimated amount of latency and then check to see if the player hit the other player, apply the effect of that, and then
play the game forward again (at faster than realtime speed) until it's caught back up to the current time.

This requires the server to keep a circular buffer of past states. And forthe game code to have some convenient way to making such
state backups. It also helps for the server to be *mostly* deterministic such that the replay gives a correct result.
Server side lag compensation is not applied to all game mechanics, typically only things that are instantaneous, such as ray casts.
Something like a rocket launcher that fires a slow moving projectile would not have server side lag compensation applied.

In that case, players with lower ping may have a significant advantage. One of the downsides of server side lag compensation is
that it may punish low latency players as they will not benefit nearly as much from the rewind process as high latency players,
and it can result in common issues such as being shot around corners.

A solution to this problem is to require all players in a match to have similar latencies within some range and to limit how far
back in time a rewind is allowed to go. The concept of rewinding and replaying applies not only to servers, but clients as well,
as they too need to handle latency and responsiveness.



Client Side Prediction



When a player takes an action and sends that input to the server, there is some latency from the network travel time, the server
process time, and return network travel time for the state update. If the client wait for that state update the player will have
a very unpleasant experience in which all of their inputs have latency. Even a small amount of latency is noticeable for this if
the game requires fast actions from the player. To make this experience better clients will apply the input locally immediately
as in a single player game while they wait for future state updates.

This works well until the server and client disagree on the player's state. When the predicted player state and the state received
from the server does not match, the client must use the server's state if it's a server authoritative state setup. However, it's
not as simple as just setting the current state to what was received by the client because that state is in the past.
The round trip time (RTT) latency means that the state is behind the server's current state, and even more behind
the client's predicted state.

To solve this, the client keeps a circular buffer of player inputs (or entire game state). Each input is assigned an integer
*sequence number*, which is just an increasing integer for each frame. When the client sends the player input to the server, it
also sends the sequence number associated with that input. When the server processes client input, it keeps track of which
sequence number it last processed for each client. Then when it sends a game state update to a client, it also sends the last
processed sequence number with it. When the client receives this state update it uses the sequence number to tell how far ahead of
that state update the current predicted state is; the delta between the sequence number and the one received from the server.

The client now sets its state to the one received from the server and replays player inputs in the circular buffer from current
time minus the sequence number delta, to the current time. If the server state update plus replay matches the client's current
prediction then there will be no visible change, which is often the case, but when it does not match, the player may experience
"rubber banding," where their state snaps to the correct one from the server. This can be further improved with interpolation
on misprediction.

This may seem rather complicated, but in practice it turns out to be a small amount of code with multiplayer preparations.
Note that client side prediction only works well for predictable objects. Since the client does not have the other player's
immediate inputs, it can't be applied to other players well. For other players and other unpredictable objects interpolation
is used.



References



Gaffer on Games, highly recommended.

+ https://www.gafferongames.com/post/deterministic_lockstep/
+ https://www.gafferongames.com/post/snapshot_interpolation/
+ https://www.gafferongames.com/post/snapshot_compression/
+ https://www.gafferongames.com/post/state_synchronization/

Valve's networking model.

+ https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
+ https://developer.valvesoftware.com/wiki/Prediction
+ https://developer.valvesoftware.com/wiki/Interpolation
+ https://developer.valvesoftware.com/wiki/Lag_Compensation

A nice article on deterministic lockstep.

+ https://quickgameworld.com/blog/deterministic-lockstep

A highly recommended article on prediction, and interpolation.
Includes a demo that runs in the browser.

+ https://www.gabrielgambetta.com/client-server-game-architecture.html

A talk on Overwatch's networking. This is more advanced.
The first part is on its entity component system, which can be skipped.

+ https://www.youtube.com/watch?v=zrIY0eIyqmI

With time skip to netcode:
+ https://youtu.be/zrIY0eIyqmI?t=1342

A video on "rollback" networking, a form of deterministic lockstep with prediction
used by modern fighting games.

+ https://www.youtube.com/watch?v=0NLe4IpdS1w









~~ Introduction to Ursina Networking ~~




Limitations



Ursina's networking currently uses TCP. Partially because it's much easier to implement, and partially because it makes things
more simple for the user. The general thing to keep in mind is that TCP works well for video games when there is little to no
packet loss, resulting in few or no packet resends. Meaning that the network connection by the users must be stable and reliable.

Network connections around the world have been improving over time making TCP more acceptable as an option. But if you need your
game to work everywhere, for as many users as possible, then you may want to use some other solution that uses UDP.
This does not apply to non-realtime networked games though, as any latency spikes causes by resends doesn't really matter.



Basics



The recommended way to do networking in Ursina is by using *remote procedure calls* (RPCs). This is an abstraction that lets
the user call functions on the server/client/peer. Effectively allowing the user to run functions (aka procedures) that exist on
another machine via a network connection. This hides all the details of packing up data and sending it over TCP to the other
machine, where it is unpacked and then used to call some function. Usage of remote procedure calls can be found in the networking
samples that come with Ursina.

RPC code will look something like this in typical use:
# ... rpc_peer = RPCPeer() @rpc(rpc_peer) def on_connect(connection, time_connected): print("This is run when a new connection is established.") if rpc_peer.is_hosting(): print("This is only run if we are the host (the server).") @rpc(rpc_peer) def on_disconnect(connection, time_disconnected): print("This is run when a disconnect happens.") @rpc(rpc_peer) def say(connection, time_received, message: str) print(message) def update(): # Handle networking events, run this every update. rpc_peer.update() # ...

Then the say function can be called by another peer via their own RPCPeer:
# If connected, this will run the say function on the other peer over the internet. rpc_peer.say(rpc_peer.get_connections()[0], "Hello, World!")

Note that type hints are **required** for user arguments such as the message argument in the example.
The RPC code needs the type information to serialize and deserialize the data. Custom user types can be added.


Samples



It's recommended to read the networking samples included with Ursina. They build on each other and club_bear.py
is a functional realtime multiplayer game tested on a real server. Their order is:

1. hello_world.py which explains how to use the more low level Peer class.
2. hello_world_rpc.py gives an introduction to RPCs.
3. shared_object.py shows how to synchronize two player controlled objects.
4. host_authoritative.py is the same as shared_object.py but the host is in charge of object state.
5. club_bear.py is similar to host_authoritative.py but more fleshed out with multiple players, chat, and more.