Multiplayer tutorial 4: real-time game

26

Index

Features on these Courses

Contributors

Stats

47,174 visits, 188,311 views

Tools

License

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.

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

  • 8 Comments

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