Multiplayer tutorial 4: real-time game

Favourite 74 favourites
Tutorial written by AshleyOriginally published on 19th, March 2014 - 4 revisions

Host group

The host is responsible for actually running the game. Sync Object does most of the work keeping the peers up to date. Therefore the Host events are a little more involved than for peers.

First of all when a peer connects, the host creates the Peer object for them at a random place in the layout (so everyone doesn't spawn on top of each other). As in the Peer events, we also set their peerid to the ID of the joining peer and associate the object with them. Remember only the host creates, moves and destroys objects, and Sync object updates them on the peers. After the object is created, it will shortly also be created in all the connected peer's games as well. The peer's On created event will subsequently trigger and also associate the newly created object with the connected peer. Bear in mind that things which happen on the host will often have consequences for the connected peers.

As the host there is no need to worry about input prediction or input delays. The host has the authoritative version of the game and can simply move themselves directly. Sync object will cause the host's position to be updated for all the peers. The only non-obvious thing is when shooting with the left mouse button, we still set the same bit in the inputs variable as the peers set. This is not for any specific reason to do with multiplayer - it just allows our later Common event group to treat everyone the same when they are firing, because setting that bit in inputs always means that peer is firing.

As the host, we are also responsible for moving all the peers. When the peers move themselves, they tell the host what inputs they are pressing and begin to move themselves anyway. It's important that the host moves them in exactly the same way based on their inputs, so the peer's input prediction works.

The Multiplayer.PeerState expression allows us to retrieve the current client input value sent from a peer. We don't want to update our own peer based on this data, so we run a for-each loop for every peer that is not us to update it. We update its instance variables based on the latest client input values. This sets the correct look position and updates inputs. Then in the four subevents, based off which bits are set in inputs we also simulate those controls being pressed for that peer. This results in the peers moving with the same controls they're pressing remotely.

The host is responsible for hit detection. This is where lag compensation becomes relevant. We need to tell who is currently firing, and if they are hitting anyone else from their point of view. First of all to determine who is firing, we test each peer and see whether bit 4 is set in their inputs (meaning they are holding the left mouse button to shoot). We don't want to deal damage every tick while they are firing, only the first time they fire, so we use a FirstShot instance variable to act like a per-instance trigger once. FirstShot is always true, except when firing when it is set to false. If we're firing and FirstShot is true, it's the first tick of firing.

For hit detection, we need to test for overlap between the Peer's AimSpot (already picked because it is in a container), and any other peer. Here Construct 2's event system makes this a little awkward. (This is not specific to the multiplayer engine, this applies to any overlap tests between the same object type.) We can't easily test for an overlap in this event, because we've already picked a Peer object using the for-each loop. The easiest way to handle this is to call a function to do the actual hit detection. In a function call the picked objects are reset, so we have a clean slate to do hit testing with. However we pass the UID of the AimSpot to test for collision with, and the Peer ID of the player who is firing.

Now we reach the function that does the actual hit test. Since the AimSpot is in a family with Peer, again it is difficult to avoid also picking the same peer. To work around this, we have a separate family with just AimSpot in it. Families pick their own objects separately, and don't automatically pick contained instances, so referring to AimSpotFamily gives us a way to pick just the AimSpot independently of everything else. We also copy the firing peer ID in parameter 1 to a local variable for readability. In a subevent we then repeat for every peer other than the peer who is firing. (There's no point checking if a peer shot themselves!)

The way lag compensation works is we know that the firing peer is seeing the game on a delay, so when checking if they hit anything, we move the objects they are aiming at back to where they would have been when they originally fired. The steps to do this are:

1. Save the current position and angle of the peer to test.
2. Move them back to where the firing peer would have seen them.
3. Test for an overlap.
4. Move the peer back to their original position and angle.

Step 2 is handled automatically by the Multiplayer.LagCompensateX/Y/Angle expressions. These calculate the past position and angle for the object given the delay that the firing peer is seeing the game. This ensures they can hit moving targets by aiming directly at them. Event 60 saves the current position to the OldX, OldY and OldAngle local variables so we can put it back afterwards, the re-positions the current peer we're testing for a hit with to its lag-compensated position. Next, event 61 tests for a hit using an ordinary Is overlapping. Finally event 62 puts it back to where it used to be.

Note if a hit was registered, we take away some health and also save the ID of the last peer who hit them. This means if they die we can add one to the kills count of the correct player.

When a player dies (their health reaches 0), we reposition them somewhere new in the layout with full health again, and add to their deaths. To add to the kill count, we face the same problem of having to pick different peers to the one already picked, so we call a function again to reset the picked objects. In the function, we find which peer matches the lasthitbypeerid variable, and add to its kills.

Event 66 is the host's chat relay, covered in the previous tutorial. This concludes the host events! The lag compensated hit-testing is probably the most complicated part, since the way the Construct 2 event system works means we need to use functions and a family so we can pick the correct objects. However hopefully this system is something you can carry over to your own games.

Share and Copy this Tutorial

You are free to copy, distribute, transmit and adapt this work with correct attribution. Click for more info.


Tedg 9,849 rep

Awesome tutorial and awesome tool :)

Wednesday, March 19, 2014 at 5:21:07 PM
Jayjay 19.0k rep

Thanks for this tutorial! :D

Wednesday, March 19, 2014 at 5:51:25 PM
superwifibattler 3,322 rep

How do you make this with a drag&drop game or a board game or something?

Wednesday, March 19, 2014 at 7:02:12 PM
exertia 2,625 rep

@ashley - is there a way to restrict # of players to say 2 or a predetermined number, and force additional players into a new room / game.

Wednesday, March 19, 2014 at 8:33:47 PM
SergioRM 6,879 rep

really interesting! thank you!

Wednesday, March 19, 2014 at 9:59:39 PM
rexrainbow 137.5k rep

Like @superwifibattler said, the prediction might not be useful in some cases like turned based game (board game, poker game) , could Ashley provide some hint about that?

Wednesday, March 19, 2014 at 11:15:51 PM
Joskin 6,089 rep

Thanks for this tutorial ! It's not easy !

@rexrainbow, what about using the messages system for cards game ?
I can try to make a simple example.

Thursday, March 20, 2014 at 9:06:27 AM
rexrainbow 137.5k rep


Yes, I know it's possible.
My idea is -
1. [peer] request a command
2. [peer] validate this command (could I run this command?)
3. [peer] if validated pass, execute this command and send this request to host

4. [host] receive command , then validate it
5. [host] if validated pass, execute this command and send this request to other peers
- if validated failed , something with that peer

6. [other peer] get validated command, execute it.

The validation function and execution function will be the same at host and peer. (peer and host will use the same function to validate command)

Thursday, March 20, 2014 at 9:34:26 AM
potpie1010 2,836 rep

Awesome tutorial; great feature.

There's no other game engine that's fun to use, yet is still able to produce actual games.

Multiplayer should be interesting!

Thursday, March 20, 2014 at 1:18:01 PM
bilgekaan 23.4k rep

Great feature and tutorial thank you!

Friday, March 21, 2014 at 11:48:30 PM
dynamiczny 438 rep

Is there any chance of obtaining the code for the signaling server? After all I would not like to infinitely rely on the server which I don't even have access to..

Could you provide the code so that we could set up our own signalling servers?

Tuesday, March 25, 2014 at 2:30:26 PM
qu0y 3,126 rep

Thank you Ashley!

Saturday, March 29, 2014 at 2:09:01 PM
drappdev 2,164 rep

I think i missed the section around the room size.
If i change the "Max Peers" value to 2, and then open 4 separate browser windows to the localhost:50000 address. the first 2 i login are entered into the room, whilst the 3rd & 4th are given the error "signalling error: room full".
If anyone knows how to push the extra players above the max peers into 'new' rooms. I would really appreciate the knowledge share.

Tuesday, April 01, 2014 at 10:08:55 PM
AbelaNET 14.7k rep

Ashley, thanks. Great info.

Wednesday, April 02, 2014 at 8:20:19 AM
Wsoukkachang 1,433 rep

@drappdev The new auto-join feature should help you with your problem.

Thursday, April 10, 2014 at 3:26:15 AM

Leave a comment

Everyone is welcome to leave their thoughts! Register a new account or login.