Mixed - Game relevant events combined with multiplayer
[L] Set a position where to go and where to shoot and tell it to every other player
Every player is deciding locally where he wants to send his player object and where it should shoot afterwards. For this we use our Touch object with the On any touch end event. So add this new event together with the conditions Is joined to a room and System -> Compare two values -> Player.count > 1. We only want the user to be able to set positions when he is in a game and when there is more than one player. So whenever the user touches/clicks the screen this event will be triggered.
Create a subevent. To differ between the walk and the shoot position i simply check the amount of available Marker object instances with the Marker.Count variable. If the count is 0 (zero) create the Marker object at the position of Touch.X and Touch.Y. Now our LocalWalkShootArray gets into the game. We defined it with a width of 4 so our first two values (index 0 and 1) will be set to the Touch.X and Touch.Y positions. For debugging and not getting crazy floating point numbers i convert all those values to full integers with int(Touch.X) and int(Touch.Y) but do it as you like. Now our Marker.Count is 1 and when the user touches/clicks the screen again our condition will no longer be met. That's why we need to create an else and check if the Marker.Count equals to 1. If this is true we know it's time to set our shoot position. Create another Marker object at the Touch position and set the LocalWalkShootArray at position 2 and 3 to these values.
Finally we are ready to deliver our hard local work to be shared with the world. For this we gonna use the Raise event action. Add it, set the Code to 2 and encode your LocalWalkShootArray as a JSON string in the Data field. Since we don't want to repeat ourselves we gonna send this event to "All" (including us).
[M] Handling the goto and shoot positions &&Master Client starts the round
Now every local player can set it's positions and send them to every player. To be able to handle the data and even recognize that there is an event we need to create a new On event with the Code 2. From event 1 we still know that the Photon.EventData is holding the information that were sent and those information should be the LocalWalkShootArray of the sending player. Now all players will send this event to every player that's why we defined our WalkShootDictionary to save their local arrays with their individual ids. To do this we gonna save the Photon.ActorNr from the event as a string into our WalkShootDictionary and set the Photon.EventData as the value for this key. Go ahead and use the Add key method from the dictionary for it.
Let's think for a second. Every player is gonna send his position to every player (including the Master Client of course). Now when do we want to start the round? Exactly! When every player has sent his position. To do this the actual Master Client is checking if the amount of keys in the WalkShootDictionary is equal or higher than the Player.Count and the Player.Count is higher than one (just for security). If this is the case the Master Client sends another raised event with the Code number 3 and his local WalkShootDictionary to all players to make sure everyone has the same walk and shoot locations. The cool thing here is even if the actual Master Client would disconnect all the other players still know about every position so every other player can easily become the new Master Client without loosing information.
[M] Start the round, "let them body movaaa"
And to make them move we want a new On event with code number 3. Triggering this event is the most important part in the game. Because now our round starts!
Remember we had our global variable isGameRunning we first gonna set this to 1. Next we will overwrite our local WalkShootDictionary with the one from the Master Client delivered through the Photon.EventData (Optional: Add a debug log saying: "Time to fight"). Now that we have updated our local dictionary every player should have the same walk and shoot positions for every player object in the field. Now let's loop over the WalkShootDictionary, pick the player object that is matching the unique id in the actual key (don't forget to convert to int!) and overwrite our local WalkShootArray with the current value of the key. Our WalkShootArray has a width of 4 and holds x and y position data for the walk and shoot positions. And that's exactly what we want to update for the Player object that we picked. So go ahead and update all the instance variables of the picked Player object and since we are not moving Jackson style by walking sideways or even moonwalk we want our objects to turn to their final walk destination. So Set angle toward for every object.
Now it's time to use rexs awesome moveTo behavior. Add the Move To XY action for the Player object and set the destination for X and Y to the Player objects gotoX and gotoY.
That's it. All objects start moving to their positions now, heart rate risen, ready for the most epic battle in history. Be prepared!
[page="
[L] Use rex_moveTo to determine that players arrived at their positions"]
[L] Use rex_moveTo to determine that players arrived at their positions
Now this is something that can be handled in many different ways but since i'm super lazy and rex already did a great job with his plugin i'm gonna stick to the wheel he invented instead of doing it again. Every time an object arrives at it's final destination triggered by an Move To event, another event called On hit target position is called for that object. So every player will trigger this event for every object in the field that was starting to move. Again here we want every player to do this to make sure the Master Client doesn't get lost on the way and the battle stops forever.
Cause seriously, who wants peace in this world? Me? Pfff... maybe?
Nevermind!
What we do now is whenever this event triggers we pick ALL the objects in the field and check with another condition of rex called Is moving if the object is still in motion or not. This will be true as long as there is still one object moving what means this object did not yet trigger this event. You can add a debug log saying "Objects still moving" or whatever you like. More important here is to add another else like we did already in case that the last event triggered is also saying that nobody is moving anymore. Now it is time for our Master Client again to tell anyone that all players arrived. Check if the local Player has the MasterActorNr and send out event 4 to all other players!
[M] Master Client tells everyone that players arrived and the fun begins!
I recommend googling "Client Prediction" cause this is (very very little) what we will do now. Players that receive event number 4 know that the Master Client decided that all the objects arrived at their position. Hopefully our local game is telling the same to us what will actually be in most cases. But in case that we have a delay or the game was lagging or any other reason we want to make sure that we still do the same as everyone else should do. That's why we first tell ourselves with a debug log that "All players arrived". And now we gonna set the Player position to it's Self.gotoX and Self.gotoY and it's angle towards the shooting position with the Set angle toward and the values Self.shootX and Self.shootY. Even if that means that the objects could jump from their actual position to their final position.
Now why am i doing this?
Simply because in this event now every player object is picked and the defining of the position and angle will affect all the objects on the field.
The next step is to wait at least half a second (0.5 seconds) to not fire at the same time the objects arrive this is just make up art (or how do you call that?). Now it's time to fire our bullets, weeeeeee! To do this we gonna Spawn another object from the player objects on the field. We gonna pick the Bullet object and use the first (1) (not 0 (zero)) image point that we defined in the beginning for our player objects (the nose). Finally it is important to set the Bullets instance variable id to the spawning objects variable to identify who's bullet it is.
! Remember what happens here is happening locally on every clients computer. Basically they all do the same now but those bullets are not created or synced over the internet. They are only available local on every machine. !
[page="
[L] Handle the impact of bullets on players"]
[L] Handle the impact of bullets on players
So here comes the tricky part of creating a multiplayer. Not that it is too hard to do but really hard to decide what to do. We have two possibilities here and i wanna talk about both even tough i decided to take the second one.
1. The local solution
Every player shoots a bullet and our local event On collision with Player will trigger on every clients computer (at different times, maybe at slightly different locations). What we can say now is: Okay my local game tells me that Player 2 hit Player 3. So lets destroy Player 3. But do not give Player 2 a point because someone else should decide if Player 2 deserves this point.
Well you see the point here. i see myself killing another player but i don't get a point for it? WTF? Why and how? But hey at least the bullet got destroyed at the perfect time. When the other Player was hit.
Well, simply because the Master Client didn't see me killing the other player. On the Master Clients machine the pixel where i hit the other player was one pixel lower than in my local game which lead to the bullet to miss the other Player.
Sad, eh? Let's see what solution two brings..
2. The authoritative ("server") solution
Instead of triggering an event for all the players the On collision event is only triggered for the actual Master Client. He decides that someone was hit and what he does now is telling that to the other people.
"You know, like back in school when Peter hit James in the stomach and Fritz was going to tell the teacher. The teacher didn't see Peter hitting James in realtime he was just informed and could replay the scene in his head."
You see the problem here as well? Yes, you do!
The Master Client may be in time with the destruction of the bullet object but other players may see the bullet fly through Player 3 and suddenly a bit after Player 3 and the bullet get destroyed. Well not the nicest experience but at least here 100% proof that every other player will see the same.
[L] Master Client decides the real impact state and informs players
Enough theory let's go back to do something!
Create a new On collision with another object event for the Bullet and pick the Player as the other object. Make sure the Player object id does NOT match the Bullets id otherwise every player would kill himself in the moment he shoots the bullet. Finally check if the local player has the MasterActorNr because we only want the Master Client to trigger that event. In side of the event we fill out HitArray that has a width of 2 with values of the Bullets id and the Player id. We raise another event with Code number 5 and send the HitArray to all players with encoded as JSON.
[M] Players destroy the objects based on Master Clients decision
And that's exactly what we gonna do. We sent the event number 5 from the Master Client and that's why we create another On event with the same number. As usual we update our local HitArray with the Photon.EventData. This Array holds the id information about the Bullet and the Player that needs to be destroyed. So let's pick the bullet by comparing it's id to the HitArray at position 0 and the Players id to the position 1. If we find something let's destroy it.
That's the way!
[page="
[L] I want and end of all of this!"]
[L] I want and end of all of this!
Mimimi i would say, but yeah sometimes the end of something doesn't mean the world's going under. In our case the world even restarts, get a new touch and is ready to be... DESTROYED AGAIN!
Since we destroy our Bullet objects now we can catch the Construct 2 event for it. We wanna do this to check if all Bullets are gone from the field (either by flying out of the field or hitting another player) what means that the round came to an end. So let's add an On destroyed event for the Bullet. Pick all the bullets in the field and then Wait 0 (to the end of all calculations) to make sure that we not counting in the destroyed object. Now count the number of available Bullets in the field and if this one is 0 (zero) and the local player is the Master Client send out the event number 6 to inform anyone that the round has ended.
[M] Okay guys, battle's over. For now...
I'm glad you are still with me here. I know this is a lot of stuff to read and remember but i want to make sure you just don't fall into the same issues and questions that got me stuck as i did.
So let's create our final Photon event number 6 to bring all of this to an end.
Add a debug log saying "Round ended! Restart in 1 second". Add a Wait 1 to wait at least one second. i just don't want the round to restart directly after the last Bullet got destroyed. Give the players some time to think about their consequences :)
What we wanna do first is to recreate all the players that got killed in this round. To do this we loop over our WalkShootDictionary and check if we can find a Player object matching the id in the dictionary. If not we gonna update our WalkShootArray and call the "CreatePlayer" function to recreate the player objects. I'm using the WalkShootDictionary because it holds the most recent position data of the players.
Next we gonna spawn all the players that joined during the round and waited for the end of it (this is not happening often but it can). So let's iterate over the JoinDataDictionary, update our JoinDataArray and check if the value at position x is 0. This is the same as we already did but now we just the the value at position 4 to 1 then we update that value back into the actual key of the dictionary.
Why? And WTF did you just do?
That's easy. Every player is triggering this event and since every player is a potential Master Client (that could become one) i want everyone to do the same things with the data that everyone has. That's why we gonna proceed with the next step:
Sending the updated JoinDataDictionary as a JSON to everyone. And what are we gonna use for this? Right! Raise event with number 1. Cause in event number 1 we check if the value at position 4 is 1 and then we create the player.
Finally (i swear this is the last time you see me writing finally x,D) we gonna reset our global variables the WalkShootDictionary and destroy the local markers by calling the "ResetRound" function.
Thank You
For going all the way trough this tutorial. I hope i could help you and get an insight into Multiplayer and Photon together with the real awesome Construct 2 engine.
I consider adding some additions like what happens in Multiplayer, What did you not think of? But for now we are done here.
So have fun and enjoy creating your own round based mutliplayer and have a nice evening.
Ralph Segi