Multiplayer tutorial 4: real-time game



Features on these Courses



41,596 visits, 173,074 views



This tutorial is licensed under CC BY 4.0. Please refer to the license text if you wish to reuse, share or remix the content contained within this tutorial.

Peer group

As with the pong 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.

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.)

You'll notice that event 22 is a sub-event that checks if the object being created represents the local player. If Sync object creates the object representing ourself, by default it is locked to data coming from the host and any changes will be overridden. This would mean our inputs would be laggy as they have to be sent to the host and then new data received back indicating our new position. So when our own object is created, we use the Enable local input prediction action on it. This frees it up, allowing it to move around. However it will constantly still be remembering the past few second's of movement, and checking the data from the host for the object. If it starts to deviate from the host's position - taking in to account the time delay - it will start to apply some correction. Therefore it is important that the peer is using exactly the same movement logic as the host, with the same speed, acceleration and so on.

The next event updates our client input values. Whenever the On client update trigger fires, the peer is about to send its client input values to the host, so it's time to update them. Note we pick the Peer object representing the local player in this event, so references to Peer relate to the object representing us.

The first thing this event does is set the lookx and looky input values to the mouse position. This means the host will know where we are aiming. This updates the client input values we added in On start of layout.

Each subevent then tests if each of the five controls is being pressed, and updates the corresponding bit in the inputs instance variable. Since the inputs client input value is an 8-bit integer, it looks like this sequence of bits:

0 0 0 0 0 0 0 0 (= 0 in decimal)

The Construct setbit system expression is useful for setting each bit individually. The bits are zero-based and the first bit is on the right for the purposes of this example. Therefore setting bit 4 to 1 will result in this number:

0 0 0 1 0 0 0 0 (= 16 in decimal)

We use bit 0 for left, 1 for up, 2 for right, 3 for down and 4 for shooting (holding the left mouse button). So if the player is holding up and right and shooting all at the same time, the number will end up looking like this:

0 0 0 1 0 1 1 0 (= 22 in decimal)

The host can then use the getbit expression to check which of our controls are pressed. This uses bandwidth very efficiently, since with a separate value for each control we'd need at least five bytes, requiring five times as much bandwidth for this data.

Note we're updating the inputs instance variable of the Peer object, so the last event (number 34) copies the instance variable to the client input value. We'll also need the instance variable in the next event. After the trigger event ends, the multiplayer engine will transmit the updated client input values to the host.

Finally, we've enabled local input prediction for ourselves, so we can move our own player ahead of when we move on the host. The last event in the Peer group moves us depending on our own controls. Note an important detail of this event: we check the bits set in the instance variable that we updated in On client update. As the comment describes, this is because we don't want to start moving before we have sent the message to the host saying we're holding that control. 'On client update' triggers 30 times a second by default, or every other frame in a 60 FPS game. If we started moving but sent the client update the next frame, we would make ourselves go one frame out of sync with the host, and force local input prediction to make a correction. So if the player presses a control, we actually wait until the next client update before they start to move. This input latency is hard to notice in practice, and helps ensure the movement occurs as closely as possible in sync with the host.

Note that the Peer object's 8 direction behavior has Default controls set to No. This means none of the objects will be controllable, except where we use Simulate control. This is necessary to avoid the behavior trying to control all the peers in the game, where it would conflict with what Sync object is trying to do.

That concludes the logic specific to peers. There's still the Host and Common groups to go!


  • Order by
Want to leave a comment? Login or Register an account!