Checking when all players have loaded in and then creating an array

The whole answer can be found below.

Please note: The Photon forum is closed permanently. After many dedicated years of service we have made the decision to retire our forum and switch to read-only: we've saved the best to last! And we offer you support through these channels:

Try Our
Documentation

Please check if you can find an answer in our extensive documentation on PUN.

Join Us
on Discord

Meet and talk to our staff and the entire Photon-Community via Discord.

Read More on
Stack Overflow

Find more information on Stack Overflow (for Circle members only).

Write Us
an E-Mail

Feel free to send your question directly to our developers.

Checking when all players have loaded in and then creating an array

xxjon54xx
2021-05-29 01:40:45

Hi everyone,

I've been working on a project using PUN recently, and need to be able to check when all player prefabs have been instantiated in the scene in order to get the player object and script. Below is how I am seeing if all player prefabs have been instantiated or not.

void Start()  
    {  
        StartCoroutine(this.CheckAllPlayersConnected());  
    }

    private IEnumerator CheckAllPlayersConnected()  
    {  
        if (this.allPlayersConnected)  
        {  
            yield return null;  
        }

        yield return new WaitUntil(() => GameObject.FindGameObjectsWithTag("Player").Length == (int)PhotonNetwork.CurrentRoom.PlayerCount);

        if (this.allPlayersConnected)  
        {  
            yield return null;  
        }  
        this.allPlayersConnected = true;  
        globalVariables.allPlayersLoadedIn = true;  
        yield break;  
    }  




  
private void PopulateScripts() {  
		int playerCount = PhotonNetwork.PlayerList.Count();  
		players = GameObject.FindGameObjectsWithTag("Player");  
		temp = GameObject.FindGameObjectsWithTag("Player");  
		for (int i = 0; i < playerCount; i++)  
		{  
			for (int j = 0; j < playerCount; j++)  
			{  
				if (i == temp[j].GetComponent<PlayerMovementScript>().playerIndex)  
				{  
					players[i] = temp[j];  
					views[i] = temp[j].GetComponent<PlayerMovementScript>();  
				}  
			}  
		}  
		huntedPlayer = players[chosenPlayerIndex].GetComponent<Transform>();  
		huntedView = views[chosenPlayerIndex];  
}  

The main issue I'm having is that the check for all players loading in does not work all the time, even when all player prefabs have been instantiated. I'm using a few custom-made scripts from an open-source project that is instantiating the prefab across the network using PhotonNetwork.Instantiate(), so it's possible that one of the issues stems from there.

Comments

Tobias
2021-05-31 15:56:21

There is a callback that is called when players join.
In it, check if all players are there and if you can start.
The Asteroids Demo in PUN 2 uses this together with a "ready" button. This can be used as "loaded" info, too.

xxjon54xx
2021-06-04 22:08:22

Hi Tobias,

Thank you for the response :) I did narrow down the issue to players not always instantiating across clients (but sometimes they do and everything works fine). Below I'll explain how the "Start game" button functions across clients in the main menu scene and how players get to the game scene.

public void StartGame()  
    {  
        PhotonNetwork.CurrentRoom.IsOpen = false;  
        PhotonNetwork.CurrentRoom.IsVisible = false;  
        loadLevelPV.InitLoadLevel();  
    }  

This function is attached to the Start game button I have in Unity, which closes the room being accessible to clients across the network and then calls the function "InitLoadLevel()" for the master client.

public void InitLoadLevel()  
    {  
        if (mapSelected == 1 && this.allPlayersHaveNP)  
        {  
            Debug.Log("Loading Northridge");  
            loadPV.RPC("LoadLevelRPC", RpcTarget.All, 2);  
        }

        else if (mapSelected == 0)  
        {  
            Debug.Log("Loading Riverside");  
            loadPV.RPC("LoadLevelRPC", RpcTarget.All, 1);  
        }

        else  
        {  
            loadPV.RPC("SetNoDLCText", RpcTarget.All);  
        }  
    }


[PunRPC]  
    public void LoadLevelRPC(int lvl)  
    {  
        myAsyncManager.LoadLevel(lvl);  
    }  

InitLoadLevel is a game object attached with a PhotonView, which then calls the RPC towards the script AsyncManager so all players then call the method on their client. AsyncManager is as follows:

public void LoadLevel(int level)  
    {  
        StartCoroutine(this.LoadLevelAsync(level));  
    }

    private IEnumerator LoadLevelAsync(int asyncLevel)  
    {  
        AsyncOperation async = SceneManager.LoadSceneAsync(asyncLevel);  
        while (!async.isDone)  
        {  
            // indicate the game is loading and stop players from leaving the room mid-load  
            yield return null;  
        }  
        yield break;  
    }  

This loads the game scene across clients asynchronously, which works fine. The problem then becomes how the player is instantiated in the scene across clients. In the game scene I have a game object called "Game Room Manager," which then makes the call to instantiate the player (this is the last of the process from the main menu scene to instantiating the player character).

private void Awake()  
    {  
        GameRoomManager.Instance = this;  
		this.CreateController();  
		blackScreenLoad.SetActive(false);  
	}

    // Start is called before the first frame update  
    void Start()  
    {

    }

	void CreateController()  
	{  
		Debug.Log("PlayerManager created");  
		GameObject myPlayer;  
		Transform playerSpawn = GameObject.Find("PlayerSpawn").GetComponent<Transform>();  
		Vector3 spawn = new Vector3(playerSpawn.position.x + UnityEngine.Random.Range(0.0f, 4.0f), playerSpawn.position.y, playerSpawn.position.z + UnityEngine.Random.Range(0.0f, 4.0f));  
		if (SettingsMenu.Instance.chosenChar == 0)  
		{  
			myPlayer = PhotonNetwork.Instantiate("CharacterPlayer", spawn, Quaternion.identity);  
		}

		else if (SettingsMenu.Instance.chosenChar == 1)  
		{  
			myPlayer = PhotonNetwork.Instantiate(Path.Combine("PhotonPrefabs", "CharacterPlayerKate"), spawn, Quaternion.identity);  
		}

		else  
		{  
			myPlayer = PhotonNetwork.Instantiate("CharacterPlayer", spawn, Quaternion.identity);  
		}  
		myPlayer.GetComponentInChildren<Camera>().fieldOfView = SettingsMenu.Instance.myFOV;  
		myPlayer.GetComponent<PlayerMovementScript>().isPressToTalkOn = SettingsMenu.Instance.pressToTalk;  
		myPlayer.GetComponent<PlayerMovementScript>().keys = KeyBindScript.Instance.keys;  
		myPlayer.GetComponent<PlayerMovementScript>().mouseSensitivityX = SettingsMenu.Instance.myMouseSens;  
		myPlayer.GetComponent<PlayerMovementScript>().mouseSensitivityY = SettingsMenu.Instance.myMouseSens;  
		myPlayer.GetComponent<PlayerMovementScript>().playerIndex = SettingsMenu.Instance.myPlayerIndex;  
		myPlayer.GetComponent<PlayerMovementScript>().chosenChar = SettingsMenu.Instance.chosenChar;  
	}  

What's odd is this sometimes works for certain players, but not for others. In particular, it becomes more prone to breaking when there is either 3 or 4 clients in the room. I could try and go back to using PhotonNetwork.AutomaticallySyncScene = true; and PhotonNetwork.LoadLevel(), however one of the issues I was having back then is that sometimes the game would freeze for certain clients when attempting to load the game scene. This asynchronous method is more reliable, but seems to drop clients from the room at times. The version of PUN I am currently on is Pun: 2.26 Photon Lib: 4.1.4.8. Appreciate if you read through all of this and are able to get back to me :smile:

xxjon54xx
2021-06-07 02:32:43

Update: It seems that the player instantiation is called across the network on one client for each player that is in the room, but not the other way around (i.e. one client will have both their character instantiate along with the other players in the room, but other clients do not instantiate other players in the scene). It seems whoever loads the game scene first gets all instantiation calls across the network, but those loading when an instantiation call happens do not receive it on their client (and thus certain players do not instantiate within the scene). I am thinking of 2 possible solutions to tackle this issue of instantiation across clients:

  1. Follow the Asteroids demo and create a buffer time of 5 seconds to allow players to load the scene, then have each client call the CreateController() function so that the instantiation call doesn't occur while a client may be loading.

  2. Create an RPC that increments upon a client completing loading of the game scene, and when it is equal to the number of players in the room then CreateController() is called on each client and thus instantiation does not occur while a player may be loading the game scene.

The potential problems of each solution is that one client could take longer than 5 seconds to load the game scene, and the other is that the RPC is not received while a client is loading (bufferedviaserver might help here, but with the current instantiation call being dropped as it is I'm not sure if there is a fool-proof solution). I'll experiment around and see what works best and if the bug remains.

Tobias
2021-06-07 10:31:23

When you load a scene in Unity this will destroy all objects before activating the scene. I assume your clients all create the objects but one client then loads.

This might help:
https://doc.photonengine.com/en-us/pun/v2/gameplay/rpcsandraiseevent#timing_for_rpcs_and_loading_levels

Back to top