Construct2 Multiplayer Tutorial

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 6

CONSTRUCT-2 / MULTI-PLAYER TUTORIAL (PONG GAME)

Game events
As with the chat example we have global constants for the game, instance and room name. These allow us to conveniently change the
names on the signalling server without having to hunt through our events. However we also have a new global for the game state. The
first player joining a room needs to wait until someone to play with also joins. Then the game will say "get ready", then "GO!". This
global variable holds the current state.

Message log group
The previous chat example used a Text Box object set to textarea mode as the main chat and information log. However it's an opaque
object so would obscure the layout if used in this example. Instead we use an ordinary Text object called LogText to show log
messages. However normal Text objects don't support scrolling. To make sure messages keep being displayed even as they go off the
bottom of the object, we implement our own scrolling system. This is done by using an array to store a line of text in each array index,
and then deleting the first item to scroll it up a line. This is not specifically related to multiplayer games, but is a useful technique to
cover anyway.
The AddLog function adds a new line by adding it to the end of the array ("push back"). If there are over 20 lines, it deletes the first
line ("pop front") - so the first line will disappear from view. After that we completely reset the content of the LogText object: first it's
cleared to empty, then we iterate the array and add each line in to the object in order. The end result is each time we call AddLog a
new line is added to the end of the LogText object, and it scrolls up when it reaches 20 lines.


Now we can call AddLog in the Function object from anywhere else in the event sheet to conveniently add scrolled messages to
LogText.
Signaling group
This group is similar to the previous chat example in that it deals with connecting to the signaling server, logging in, joining a room
and determining if we are the host. However there are some key differences now that we are dealing with objects and real-time
synchronization.
First of all, in On start of layout we must set up the objects and data that are going to be transmitted. The first action sets up the inputs
that peers send to the host. To prevent cheating, the peers only send their inputs to the host, who performs the actual collision tests
with the paddles and ball. The only input the peer has is the vertical (Y) position of their paddle.


The inputs that peers send to the host are called the client input values. In this case we use one input which we name "y", for the
paddle Y position. It uses a 16-bit integer, since it only uses two bytes and sub-pixel positions don't matter. The input value also uses
Linear interpolation. This means even if updates are coming in to the host infrequently, the multiplayer engine guesses the in-between
values assuming the paddle is moving linearly. This ensures movement of the paddle is smooth. If we chose None for the
interpolation, paddle movement could often look choppy.
The next part of the event sets up which objects and which of their instance variables are synced.


The first action uses the Multiplayer object's Sync object action. This is the key action to indicate which objects are to be sent over the
network. Syncing an object is one-way: it means the host tells peers how many of those objects exist and where they are. Peers only
receive this data and any changes that peers make to these objects will be ignored and overridden! The client input values are the only
ways the peers have of affecting the game, and the only gameplay data that is sent from the peer to the host.
The host has the authoritative version of the game, and the peers are doing their best to display what the host has got. The host is
solely responsible for creating, moving and destroying these objects; as it does so, Sync object will also cause objects to be created,
moved and destroyed on the connected peers. All of this happens internally in the multiplayer engine as a consequence of this action.
Since Sync object will move objects by itself, and only the host is "really" running the game, the peers also disable all the behaviors on
the objects. For example the ball has the Bullet behavior. Only the host should have the behavior enabled. The Bullet behavior should
be disabled when acting as a peer, since Sync object will already be moving it as it moves on the host, and having the behavior enabled
on the peer could cause the behavior to conflict with where Sync object is trying to move it. In this example the Bullet behavior is
initially disabled, and it's enabled in the events when acting as the host.
Sync object can update the object position or angle, or both, or neither. In this case we're only interested in the position, since the
angle of objects is not important to gameplay and it would be a waste of bandwidth to send data about object angles. The Bandwidth
parameter allows the maximum rate of updates from the host to be reduced. This is not normally necessary - refer to the manual entry
to find out more about the option.
The Sync object object by default does not transmit any other data, and instance variables would not be updated. It would be wasteful
to automatically update every instance variable, since some of them might only be used locally. After the Sync object action, we can
optionally use any number of Sync instance variable actions to add instance variables for the host to also send to peers (and as with
Sync object, the instance variables are updated automatically). It's important to note Sync instance variable can only be used after
Sync Object. By itself it does nothing; the action means "also sync this variable to an already-synced object".
The only variable we sync in this case is the points variable of the Paddle object. This is a simple way for everyone to know each
player's score.
The client value tag is used if the instance variable also corresponds to a client input value. This is not necessary in this example, but
is covered in the next tutorial.
The ball object must also be synced, so the peer can see where it is! However there is no need to sync anything other than its position.
Now that all the client inputs and synced objects are set up, the last action in On start of layout connects to the signaling server.
The rest of the Signalling group is more similar to the chat example; however there are still some more differences to come. First of all
as before, once connected we log in with our username.

To join the game, one difference is we use Auto-join room instead of Join room. If we only used Join room, then everyone ends up in
the same game. 30 players in a game which only supports 2 players isn't very useful! By using Auto-join room, once a room is full the
next joining players get sent to a new room. In this case 30 joining players get assigned to 15 separate 2-player games. Locking the
room when full also prevents late-joiners; it means once the room is full, even if some players quit nobody else will be added to the
game. If the room stayed unlocked a newly joining peer could be sent to the same room to top it back up, but that means having to
handle late-joiners, which we'll skip for simplicity.

Once joined, we establish if we are the host. We do the same as the chat example: either the Host or Peer group is activated. However
we also need to handle the paddle and ball already in the layout depending on whether we became host. If we're a peer, it's easy: we
just destroy the objects already in the layout. Sync object will automatically create, move and destroy any objects that are present on
the host, including the object that represents our own player. The game will also start just after we join as a peer, since the host is
already there, so we immediately set the game state to "getready".
However when we are the host, we adopt the existing paddle object in the layout as our own object that we control. The paddle object
has a peerid instance variable. This stores the signalling-assigned peer ID, and allows us to pick any paddle instance and know which
player it represents. It must be a text variable, since peer IDs are short sequences of random characters. Our own peer ID is assigned to
this object to indicate it represents ourselves.
The Associate object with peer action is also necessary to indicate to the multiplayer engine that this instance represents a player in the
room. By default Sync object treats objects as neutral, like scenery objects or AI-controlled enemies, and simply makes sure everyone
sees the same number of objects in the same places. Using Associate object means the object is not neutral and is associated with a
connected peer in the same room, and allows it to do more automatically (such as destroying the object when the peer leaves). In this
case we're adopting the object already in the layout, so we associate it with our own ID.


The remaining events in the Signalling group cover error handling in the same way the chat room does. However there's an extra event
that simply displays some connection stats, including the number of inbound and outbound messages sent per second, the total
resulting bandwidth in kilobytes per second, and the latency and PDV to the host. (These will show as 0 for the host, since they do not
need to connect to themselves!)
Peer group
As with the chat example, the Peer event group is a little simpler than that of the host. Remember that Sync object creates, moves and
destroys objects automatically for peers depending on what happens on the host, so we don't need to worry about that here. The Host
group however will have to deal with the logic of how the objects move and interact, which in this case is mostly getting the ball to
bounce off paddles, and keeping score.
There is one thing we need to do when creating objects as a peer, though. Sync object will automatically create and destroy objects,
but when they are created we need to set their peerid instance variable so we can later know which peer the object represents. When
Sync object creates an object, the object's On created event triggers and the Multiplayer.PeerID expression is set to the ID of the peer
for whom the object is being created. This allows us to remember who the peer is. Also, as with all objects representing peers, we use
the Associate object with peer action to indicate this object represents a connected peer. (This must always be done on both the host
and peer sides.)


Next, the peer needs to send its inputs to the host. On client update triggers for peers when the multiplayer engine is about to send
inputs to the host, so we should tell the Multiplayer object the latest values. All we need to do is tell the host our paddle's Y co-
ordinate. The paddle is placed by our mouse cursor, so we just set the "y" input value (which we added on start of layout) to the mouse
Y position.


Finally, the host is in control of the game, and decides when we move from "get ready" to "go!". In order to keep our GameState
global variable synchronised with the host, whenever we receive a "gamestate-changed" message we update the global variable.


That's it for the peer-specific events!
Host group
The host is responsible for actually running the game. Sync Object does most of the work keeping the peers up to date. The host
handles actual game logic, so the Host events are a little more involved than for peers.
First of all, when we are the host we're only waiting for one other player to join. Therefore the first time On peer connected triggers,
we can begin the game. When a peer connects we first create a paddle for them on the right of the screen and associate it with the
joining peer. The game should say "Get ready...", wait 3 seconds, say "Go!" for 1 second, then switch to displaying the score (which is
the "started" game state). When the game starts we also enable the ball's bullet movement and set it off at a random angle. Remember
only the host actually moves the object; Sync object tracks it and updates it for the peers, and the behavior should only be enabled on
the host, since otherwise it could conflict with Sync object on the peer.


The host uses a function to set the game state. This is because simply changing the GameState variable only affects the host, and the
peer would never find out. This function allows us to both change the variable, and send a message to the peer indicating the game
state has changed. The peer receives this and updates its own GameState variable. This keeps the variable updated for everyone in the
game.

Each paddle's peerid variable is set to the ID of the corresponding player, and we can use this to pick the object for individual players.
Since this is a two-player game, the peer ID is either MyID and is our own paddle, or it is different and it's the other player's paddle.
We need to move the other player's paddle to where they have it, so we pick their paddle and set it to their "y" input value. The latest
input value for a peer is retrieved with the PeerState expression.

Only the host handles gameplay logic like collisions. In this case the host is responsible for bouncing the ball off paddles; this happens
automatically because the paddles are solid and the Bullet behavior is set to bounce off solids (remember, peer has the Bullet behavior
disabled so this still only happens on the host). Each time it bounces we also add to its speed so the game gets harder and harder.

Events 29 and 30 track the score. If the ball goes off the left of the screen (X < 0), add 1 to the peer's score (the player on the right),
and reset the ball. If the ball goes off the right of the screen (X > LayoutWidth), add 1 to the host's score (the player on the left), and
reset the ball.

That's all for the host-specific events.
Common group
The Common group has events that are used for both the host and peers.
When peers connect and disconnect, we add a log just like in the chat tutorial.


Regardless of if we are the host or the peer, we always show our own paddle by our own mouse.

Sync object does not transmit which layers objects are on, to save bandwidth. It never changes in this game anyway, but this event
makes sure they always appear on the correct layer.

The host and peers update their GameState variables differently, but we can handle its value the same: if we're in "getready" or "go"
state, display some text in the score display.

Finally when the game is in "started" state, we display the score held in the points variable of each paddle. Both players see the host
score on the left and the peer score on the right, corresponding to the position of the paddles. The host paddle can be picked by
matching its peer ID to the host ID, and the peer paddle can be picked by checking its peer ID is not the host ID.

Conclusion
The general model of a multiplayer real-time game is:
- The host has behaviors enabled and actually runs the game logic.
- Peers have behaviors disabled and just uses "sync object" to passively watch what happens. However they can influence the game
from their client input values, which the host reads and uses to influence the gameplay.
Pong is a simple example, but still involved 40 events. This example has also not covered local input prediction or lag compensation.
Designing multiplayer games can be challenging and can take some time to learn. To move on to the final concepts in a slightly more
complex game, move on to Multiplayer tutorial 4: real-time game.

You might also like