day 04 Multiplayer Game Design

 A multiplayer game played over the network can

be
implemented
using
several
different
approaches, which can be categorized into two
groups: authoritative and non-authoritative.
 In the authoritative group, the most common
approach is the client-server architecture, where a
central entity (the authoritative server) controls the
whole game. Every client connected to the server
constantly receives data, locally creating a
representation of the game state. It's a bit like
watching TV.
 If a client performs an action, such as moving from
one point to another, that information is sent to the
server. The server checks whether the information
is correct, then updates its game state. After that it
propagates the information to all clients, so they

can update their game state accordingly.

Authoritative implementation using client-server architecture

 In the non-authoritative group, there is no central entity

and every peer (game) controls its game state. In a peerto-peer (P2P) approach, a peer sends data to all other
peers and receives data from them, assuming that
information is reliable and correct (cheating-free):
 In this subject, it will present the implementation of a

multiplayer game played over the network using a nonauthoritative P2P approach. The game is a deathmatch
arena where each player controls a ship able to shoot and
drop bombs.
 This

subject focuses on the communication and
synchronization of peer states. The game and the
networking code are abstracted as much as possible for
the sake of simplification.


 Tip: the authoritative approach is more secure against

cheating, because the server fully controls the game state
and can ignore any suspicious message, like an entity
saying it moved 200 pixels when it could only have moved
10.

 A non-authoritative multiplayer game has no

central entity to control the game state, so every
peer must control its own game state,
communicating any changes and important
actions to the others. As a consequence, the player
sees two scenarios simultaneously: his ship
moving according to his input and a simulation of
all other ships controlled by the opponents:
 The player's ship's movement and actions are

guided by local input, so the player's game state is

updated almost instantly. For the movement of all
the other ships, the player must receive a network
message from every opponent informing where
their ships are.

Player's ship is controlled locally.
Opponent ships are simulated based on network communication.



Those messages take time to travel over the
network from one computer to another, so when
the player receives an information saying an
opponent's ship is at (x, y), it's probably not there
any more - that's why it's a simulation:



In order to keep the simulation accurate, every
peer is responsible for propagating only the

information about its ship, not the others. This
means that, if the game has four players say A, B, C and D - player A is the only one able to
inform where ship A is, if it got hit, if it fired a
bullet or dropped a bomb, and so on. All other
players will receive messages from A informing
about his actions and they will react accordingly,
so if A's bullet got C's ship, then C will broadcast a
message informing it was destroyed.


Communication delay caused by the network.

 As

a consequence, each player will see all other ships (and their
actions) according to the received messages. In a perfect world,
there would be no network latency, so messages would come and go
instantly and the simulation would be extremely accurate.

 As


the latency increases, however, the simulation becomes
inaccurate. For example, player A shoots and locally sees the bullet
hitting B's ship, but nothing happens; that's because A's view of B is
delayed due to network lag. When B actually received A's bullet
message,B was at a different position, so no hit was propagated.

 An important step in implementing the game and ensuring that

every player will be able to see the same simulation accurately is
the identification of relevant actions. Those actions change the
current game state, such as moving from one point to another,
dropping a bomb, etc.
 In our game, the important actions are:
 shoot (player's ship fired a bullet or a bomb)
 move (player's ship moved)
 die (player's ship was destroyed)
 Every action must be sent over the network, so it's important to

find a balance between the amount of actions and the size of the

network messages they will generate. The bigger the message is
(that is, the more data it contains), the longer it will take to be
transported, because it might need more than one network
package.
 Short messages demand fewer CPU time to pack, send, and

unpack. Small network messages also result in more messages
being sent at the same time, which increases the throughput.


 After the relevant actions are mapped, it's time to

make them reproducible without user input. Even
though that's a principle of good software
engineering, it might not be obvious from a
multiplayer game point of view.

 Using the shooting action of our game as an

example, if it's deeply interconnected with the input

logic, it's not possible to re-use that same shooting
code in different situations:

 When the shooting code is decoupled from the input

logic, for instance, it's possible to use the same code
to shoot the player's bullets and the opponent's
bullets (when such a network message arrives). It
avoids code replication and prevents a lot of
headache.

Performing actions independently

 The Ship class in our game, for instance, has no

multiplayer code; it is completely decoupled. It
describes a ship, be it local or not. The class,
however, has several methods for manipulating
the ship, such as rotate() and a setter for
changing its position. As a consequence, the

multiplayer code can rotate a ship the same way
the user input code does - the difference is that
one is based on local input, while the other is
based on network messages.


 Now that all relevant actions are mapped, it's time

to exchange messages among the peers to create
the simulation. Before exchanging any data, a
communication protocol must be formulated.
Regarding a multiplayer game communication, a
protocol can be defined as a set of rules that
describe how a message is structured, so
everyone can send, read, and understand those
messages.

 The messages exchanged in the game will be

described as objects, all containing a mandatory

property called op (operation code). The op is
used to identify the message type and indicate the
properties the message object has. This is the
structure of all messages:

 The OP_DIE message states that a ship was destroyed.

Its x and yproperties contain the ship's location when it
was destroyed.
 The OP_POSITION message contains the current location

of a peer's ship. Its x and y properties contain the ship's
coordinates on the screen, while angle is the ship's
current rotation angle.
 The OP_SHOT message states that a ship fired something

(a bullet or a bomb). The x and y properties contain the
ship's location when it fired; the dx and dy properties
indicate the ship direction, which ensures the bullet will
be replicated in all peers using the same angle the firing

ship used when it was aiming; and the b property defines
the projectile's type (bullet or bomb).

 In order to organize the multiplayer

code, we create a Multiplayer class. It
is responsible for sending and
receiving messages, as well as
updating the local ships according to
the received messages to reflect the
current state of the game simulation.
 Its initial structure, containing only

the message code, is:

 For every relevant action mapped previously, a network message must be

sent, so all peers will be informed about that action.
 The OP_DIE action should be sent when the player is hit by a bullet or a


bomb explosion. There is already a method in the game code that
destroys the player ship when it is hit, so it's updated to propagate that
information:

 The OP_POSITION action should be sent

every time the player changes its current
position. The multiplayer code is injected
into the game code to propagate that
information, too:

 Finally, the OP_SHOT action must be sent every

time the player fires something. The sent
message contains the bullet type that was fired,
so that every peer will see the correct projectile:

 At this point, each player is able to

control and see their ship. Under the
hood, the network messages are sent
based on relevant actions. The only
missing piece is the addition of the
opponents, so that each player can see
the other ships and interact with them.
 In the game, the ships are organized as

an array. That array had just a single
ship (the player) until now. In order to
create the simulation for all other
players, the Multiplayer class will be
changed to add a new ship to that array
whenever a new player joins the arena:

 The

message exchanging code
automatically provides a unique
identifier
for
every
player
(the user.id in the code above). That
identification is used
by the
multiplayer code to create a new ship
when a player joins the arena; this
way, every ship has a unique
identifier. Using the author identifier
of every received message, it's
possible to look up that ship in the
array of ships.

 Finally, it's time to add the handleGetObject() to the Multiplayer class. This method

is invoked every time a new message arrives:

 When a new message arrives, the handleGetObject() method is invoked with two parameters: the author

ID (unique identifier) and the message data. Analyzing the message data, the operation code is
extracted and, based on that, all other properties are extracted as well.
 Using the extracted data, the multiplayer code reproduces all actions that were received over the

network. Taking the OP_SHOT message as an example, these are the steps performed to update the
current game state:
 Look up the local ship identified by userId.
 Update Ship's position and angle according to received data.
 Update Ship's direction according to received data.
 Invoke the game method responsible for firing projectiles, firing a bullet or a bomb.
 As previously described, the shooting code is decoupled from the player and the input logic, so the

projectile fired behaves exactly like one fired by the player locally.



If the game exclusively moves entities based on network updates, any lost or delayed
message will cause the entity to "teleport" from one point to another. That can be mitigated
with local predictions.



Using interpolation, for instance, the entity movement is locally interpolated from one point
to another (both received by network updates). As a result, the entity will smoothly move
between those points. Ideally, the latency should not exceed the time an entity takes to be
interpolated from one point to another.



Another trick is extrapolation, which locally moves entities based on its current state. It
assumes that the entity will not change its current route, so it's safe to make it move
according to its current direction and velocity, for instance. If the latency is not too high, the
extrapolation accurately reproduces the entity expected movement until a new network
update arrives, resulting in a smooth movement pattern.



Despite those tricks, the network latency can be extremely high and unmanageable
sometimes. The easiest approach to eliminate that is to disconnect the problematic peers. A
safe approach for that is to use a timeout: if the peer takes more than an specified time to
answer, it is disconnected.

 Making a multiplayer game played over the network is a challenging and exciting

task. It requires a different way of seeing things since all relevant actions must be
sent and reproduced by all peers. As a consequence, all players see a simulation of
what is happening, except for the local ship, which has no network latency.
 This tutorial described the implementation of a multiplayer game using a non-

authoritative P2P approach. All the concepts presented can be expanded to
implement different multiplayer mechanics. Let the multiplayer game making
begin!