How to check if everyone except one have died in multiplayer game?

Hi,

In my multiplayer game, players who die don’t get network destroyed, they simply have their character mesh disabled so they can remain in the scene and watch the rest of the battle. When a player dies a boolean becomes true for that player.

I’ve got a game manager with a player database script on it that creates a list of all the players but I don’t know how to check the booleans of each player on that list to see if they’ve died. When there’s only one player left, the next level should load:

This is just in a test function for now (I’m using Photon):

        GameObject gameManager = GameObject.Find("GameManager");
        PlayerDatabase dataScript = gameManager.GetComponent<PlayerDatabase>();


        for (int i = 0; i < dataScript.PlayerList.Count; i++)
        {
            //Check if DeadBool is true, and if there's only one player left on the list where the DeadBool is false,
            //- load next level.

        }

If anybody could tell me how to do this or if there’s a better way, it would be very much appreciated

A couple of approaches you could consider…

At the start of each level, add all the players to a playersAlive list. Then as each player is killed, remove them from the list and check the count of players left in the list, when it’s 1 you can load the next level and repeat.

Or instead of a list you could use an integer variable which you set to the count of players in the game at the start of each level, and then decrement it each time a player is killed, then load then next level when it reaches 1.

You can also decrease the count if an undead player disconnects.

You should probably do this checking only on the master client, and therefore it would probably be best to use the second approach and implement the count variable as a custom room property, so that if the current master client disconnects, the new one will automatically have access to the current dead player count.

You can simply run an isMine check with Photon and have a boolean of some sort to see whether they’re alive or not and then take away 1 from some sort of global integer that all the players can see, that’s what I’ve done and it works fine. You then have the game run an if check on the integer and then have an event happen when it gets to 1.

Absolutely no need to run any checks on the master client for this sort of code, just use photonView.isMine. Just as a reminder for anyone new to Photon and networking in general, use photonView.IsMine for whatever checks you want to do on an individual player then use RPC calls if you want to have something synchronise for all players connected to the game.

1 Like

You don’t want multiple clients deciding to load a new scene at the same time, therefore assigning this task to the MC makes the most sense. Using a custom room property allows each client to update the global ‘alive’ count when they die, without having to resort to using RPCs.

[quote=“Lethn, post:3, topic: 804471, username:Lethn”]
some sort of global integer
[/quote] Not really helpful. This is one of the things that often confuses programmers new to multiplayer programming. A global variable in the sense of a single player game is quite different from a multiplayer global variable in it’s implementation. Hence the custom room property suggestion in this case.

1 Like

I wasn’t suggesting using LoadLevel for every client I was talking about updating the integer each time a player dies. One way or another you’re going to have to have the game recognise when an individual player has died before you can have a reason to load the level, don’t know where you got that idea from you’re jumping to conclusions.

I’m using an RPC call in my own game because I have it sent to all players across the room so they can see the player count updating in real time when something happens by the way which is why I suggested it as an example.

In fact on further consideration, having each client able to decrement a global counter when they die could lead to issues if two players die at almost the same time, so a CAS operation would be required to overcome that. Even using an array of some kind would be susceptible to the same race condition.

With that in mind, I would instead implement a boolean player custom property, indicating alive or dead, and have the player set it to false when they die, this would negate the need for using CAS. Then the MC can monitor the playerpropertiesupdated callback and loop through the list of currently connected players checking that custom property value and incrementing a counter for each one that has a true value. If the final count is only 1, then load the next level.

Each client can reset their alive property to true when the next level loads, maybe using Scenemenager.MapLoaded callbacks, depending on how you approach level loading.

So as every client receives this RPC, how do you decide which one should be responsible for checking the latest count and performing the level loading?

I’ve played with this feature a lot and I haven’t been able to get that to happen.

That’s literally what I suggested in my first post and why I pointed out it would be a good idea to put a boolean in IsMine.

It’s probably better if I just grab you the code that I’m using in my own game hang on.

        if (photonView.IsMine)

        {


            if (Keyboard.current.escapeKey.isPressed)

            {
                PhotonNetwork.Disconnect();
                Application.Quit();
                playerCount -= 1;
                photonView.RPC("RPC_UpdatePlayerCount", RpcTarget.AllBuffered, playerCount);
            }

}
    [PunRPC]

    public void RPC_UpdatePlayerCount(int playerCountSyncInt)

    {
        playerCount = playerCountSyncInt;
        playerCountText.text = playerCount.ToString();
    }

This is a bit of pseudo code but it should give you an idea of how this will work, the player count simply won’t update unless something has happened within the update function and the PhotonView is owned by the player. This means that you should be able to load the level without any issues I would have thought. I could completely agree though with being paranoid and using a master client check for the sake of it but Photon seems to be pretty solid with it’s network synchronisation I haven’t run into any problems as of yet.

I need to experiment with LoadLevel and the like but I’ve even been able to make a resource system using these methods just to show you it working with something more complicated and I’ve synced animations across the network as well.

    public void ChopWood()

    {

        if (Mouse.current.rightButton.wasPressedThisFrame)

        {
            int ignorePlayerMaskint = (1 << 8);
            LayerMask ignorePlayerMask = ~ignorePlayerMaskint;
            Vector2 mouseScenePosition2D = playerCamera.ScreenToWorldPoint(Mouse.current.position.ReadValue());
            mouseScenePosition2D = new Vector2(mouseScenePosition2D.x, mouseScenePosition2D.y);
            Vector2 playerEmptyPosition = new Vector2(playerEmpty.transform.position.x, playerEmpty.transform.position.y);

            Vector2 mouseClickDirection = mouseScenePosition2D - playerEmptyPosition;

            RaycastHit2D hit = Physics2D.Raycast(playerEmpty.transform.position, mouseClickDirection, 30, ignorePlayerMask);

            if (hit)

            {
                if (hit.transform.tag == "Tree")

                {
                    GameObject localTree = hit.transform.gameObject;
                    playerWood += 10;
                    localTree.GetComponent<TreeResource>().treeWood -= 10;
                    hit.transform.GetComponent<PhotonView>().RPC("RPC_UpdateTreeWood", RpcTarget.AllBuffered, localTree.GetComponent<TreeResource>().treeWood);
                    ChopWoodAnimations();
                }

            }
        }

    }
    public void ChopWoodAnimations()

    {

        if (photonView.IsMine)

        {
            if (Mouse.current.rightButton.wasPressedThisFrame)

            {

                if (isFacingForward == true)

                {
                    animator.SetTrigger("FrontAxeAnimation");
                }

                else

                {
                    animator.ResetTrigger("FrontAxeAnimation");
                }
            }

            if (Mouse.current.rightButton.wasPressedThisFrame)

            {

                if (isFacingBack == true)

                {

                    animator.SetTrigger("BackAxeAnimation");

                }

                else

                {
                    animator.ResetTrigger("BackAxeAnimation");
                }

            }

            if (Mouse.current.rightButton.wasPressedThisFrame)

            {

                if (isFacingLeft == true)

                {

                    animator.SetTrigger("LeftSideAxeAnimation");

                }

                else

                {
                    animator.ResetTrigger("LeftSideAxeAnimation");
                }

            }

            if (Mouse.current.rightButton.wasPressedThisFrame)

            {

                if (isFacingRight == true)

                {
                    animator.SetTrigger("RightSideAxeAnimation");
                }

                else

                {
                    animator.ResetTrigger("RightSideAxeAnimation");
                }
            }
        }
    }

All of this is just simply contained within the photonView.isMine command and it handles any potential conflicts for you which is why I wouldn’t have thought there would be any issues with loading and I think there’s even a build in command that the Photon devs have put in to let you wait for the clients to sync up the level load anyway.

Actually posting this up made me just noticed I duplicated the isMine command unnecessarily so I’ll have to have a look through that and fix some things. This should give you an idea of what I’m talking about though.

The way I would do it is if I were really paranoid maybe you could even implement a small spawn delay just to make sure that all the clients have caught up with each other so I could understand doing those kind of checks if you’re worried about latency.

1 Like

So, if two players die at almost the same time, and every client has the ability to check for and load the next level, there is the potential for both of them to send a network load level command at the same time, the most likely result being that one of the levels will appear to be skipped.

Anything global like end of game checking and level loading really needs to be restricted to a single client using one method or another.

This is still prone to errors if multiple clients try to update the variable/array at the same time, as the contents may change in between them reading the contents and applying the new value. Which is where a CAS operation comes into play.

Using photonView.IsMine only ensures the code only runs on a single script/object on any particular client, it doesn’t protect you from multiple clients running the same script at the same time.

Being ‘paranoid’ (I’d prefer to use the word mindful) about this type of race condition, where even small amounts of network latency is involved, is what makes a multiplayer game work correctly all the time instead of randomly experiencing odd behaviour and bugs.

1 Like

This will work fine as long only one single client calls the RPC at a time, and the playerCount value is updated before other clients try to update its value.

However, In this example, if two separate clients simultaneously call the RPC when the playercount is, for the sake of argument 5, they both decrease it by 1 to it to make it 4 and then send that value in the RPC.

So instead of what you’d want, which is that the final playerCount is reduced to 3, it will only be reduced to 4.

1 Like

Okay I kind of get you now, what you’re talking about is a potential worse case scenario. I did run into this problem actually with simply entering in my username and used IsConnnected to solve it. If I entered in my username and spawned in my player it would throw up an error sometimes if I was too quick and made my connection have some latency but an if check got rid of this problem. I take it this is just something you’re concerned about happening with regards to synchronising map changes.

With your explanation I get what you’re talking about now so that’s something I’ll have to bear in mind with map changes and so on. The problem of an RPC call happening simultaneously could be very real since I’m planning on having something like 30 players playing all at once so I’ll experiment but this is what testing is for I would have thought though Photon would be taking care of issues like that, definitely going to have to poke around.

Unfortunately, it isn’t something that can be automatically accounted for by Photon, but it’s where giving the MC client the responsibility for the operation and/or using CAS for custom properties can be used to prevent such issues.

I’ve spent the last 4 years working on a multiplayer RPG game which supports Photon rooms with 50+ connected clients, so I’ve encountered all these edge case problems and more at some point or another and initially it caused some serious headaches while tried to fathom out was was going on.

Off topic, but In case it helps I should mention that, to facilitate being able to support that many clients and keep the msg count/bandwidth at manageable levels, I dispensed with RPCs and any of the supplied networking components (PhotonTransfornView etc.) and implemented everything using RaiseEvent. Also the only objects in the game that use photonViews are the player’s characters, everything else, mobs,collectable items, NPCs etc. are controlled using RaiseEvent.

2 Likes

dude I am really glad you gave this info, I just wanted to request you, to give a basic syntax with which i can use master client for checking players death i would really appreciate it as i just started using photon to make a multiplayer(3 months)