Player joining match sees old destroyed scene objects?

Options
Hello, I'm running into a strange bug and I'd like to see if there's a way to solve it.

We've got a lobby/match setup almost identical to Call of Duty 4. Players press "quick match" and join/create a random game, then sit in a room lobby until enough players have joined and until the various intermission timers have finished. The match then starts by loading the map set by the room. Once the scene is loaded, several scene objects are spawned by the MasterClient to handle things like gametype, score, etc. Once the map has ended and shown the scoreboard, the match loads back out to the main menu. The main menu sees that the players are in a room and jumps them back to the room lobby menu and the timers start again. All of this works great!

A healthy match (log is from client):





The problem is that when a player joins a room lobby still in the main menu but after a match has ended in that room, they load the gametype/score/etc scene objects spawned in the previous game. These objects don't exist on the host machine, but do somehow update themselves as if they were (all of the update logic is hidden behind PhotonNetwork.isMasterClient).

Joining a match in the lobby after a game has concluded:



A diagram to better explain when specifically this is happening:



If a player joins a room that's in the game scene these leftover objects aren't loaded and the appropriate ones are. If a player waits out a bugged lobby they'll load into the new game scene no problem, and when exiting the scene will have the scene objects properly disposed.

Here's how I load into the scene:
private void LoadMatch()
{
	Debug.LogFormat("Loading {0} on {1}", gametype.ProperName, map.ProperName);
	PhotonNetwork.LoadLevel(map.SceneName);
}
Here's how I spawn the scene objects:
/// <summary>
/// Creates a team score object on the network.
/// </summary>
/// <param name="team">Team.</param>
private void CreateTeamScore(Team team)
{
	if (teamScorePrefab)
	{
		Debug.LogFormat("Created TeamScore for team {0}.", team);
		PhotonNetwork.InstantiateSceneObject(teamScorePrefab.name, Vector3.zero, Quaternion.identity, 0, teamScorePrefab.GetInstantiationData(team));
	}
}
Here's how I load out of the scene:
else if (state == State.Finished)
{
	state = State.Loading;
	PhotonNetwork.LoadLevel(lobbyScene); // FIXME: Do we use this, or do we manually go to our own lobby scene using something else? Will this bring along our child clients?
}
PhotonNetwork.automaticallySyncScene is set to true.

Best Answer

Answers

  • TerrasectJeff
    edited July 2018
    Options
    I should add: the 3rd picture showing the log is from a client that joined a match, "played" a game, loaded back out to the menu, left the match (PhotonNetwork.LeaveRoom()), and then rejoined the match. The red line above the exception is where the player has joined back in.

    This will also happen if a client that has never joined any room or loaded into any scene joins a match/room that's completed a match and returned to the menu.

    As soon as they join, the old scene objects are loaded and throw an error:
    PhotonView ID duplicate found: 1. New: View (0)1 on King Of The Hill Match Manager(Clone) (scene) old: View (0)1 on Match Lobby Menu (scene). Maybe one wasn't destroyed on scene load?! Check for 'DontDestroyOnLoad'. Destroying old entry, adding new.
    UnityEngine.Debug:LogError(Object)
    NetworkingPeer:RegisterPhotonView(PhotonView) (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/NetworkingPeer.cs:3601)
    PhotonView:Awake() (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/PhotonView.cs:269)
    UnityEngine.Object:Instantiate(GameObject, Vector3, Quaternion)
    NetworkingPeer:DoInstantiate(Hashtable, PhotonPlayer, GameObject) (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/NetworkingPeer.cs:3279)
    NetworkingPeer:OnEvent(EventData) (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/NetworkingPeer.cs:2597)
    ExitGames.Client.Photon.PeerBase:DeserializeMessageAndCallback(Byte[])
    ExitGames.Client.Photon.EnetPeer:DispatchIncomingCommands()
    ExitGames.Client.Photon.PhotonPeer:DispatchIncomingCommands()
    PhotonHandler:Update() (at Assets/Photon Unity Networking/Plugins/PhotonNetwork/PhotonHandler.cs:157)
    Now that I'm looking at it more, the photonView ID's are conflicted. The lobby has a scene object (the menu) with ID 1.



    The MatchManager that's spawned once a game loads also seems to be assigned an ID of 1, though the prefab is set to "Set at runtime".



    When the new player joins the match, they load the old scene objects (when it shouldn't), the ID conflicts with the menu, and Photon resolves it by destroying the client's photonView with ID1 (the menu object).

    Edit: I was able to resolve the id conflict and prevent the menu from shitting itself by putting the menu's photonView ID to 900, which clears the IDs assigned to the incorrectly spawned objects. it's still spawning the old phantom objects when joining late, though. I'll update this later when I've got the player objects spawning to see if non-scene objects are also loading in improperly. It's also spawning old player-owned non-scene objects, but only the host's.

  • Calling PhotonNetwork.DestroyAll before PhotonNetwork.LoadLevel seemed to solve it.

    From the above code, that'd be
    else if (state == State.Finished)
    {
    	state = State.Loading;
    	PhotonNetwork.DestroyAll();
    	PhotonNetwork.LoadLevel(lobbyScene);
    }
    Does that sound about right? For our solution this seems like it'd work since we don't have any PhotonViews that persist between scenes. Is it possible to DestroyAll views on a specific group or a DestroyAll on all objects spawned with PhotonNetwork.InstantiateSceneObject?
  • Is it possible to DestroyAll views on a specific group or a DestroyAll on all objects spawned with PhotonNetwork.InstantiateSceneObject?


    DestroyAll will remove all PhotonViews and their related RPCs, if they have been network instantiated. So you would have to use PhotonNetwork.Destroy instead and pass each PhotonView (or GameObject) to it. There are no filter options or similar for PhotonNetwork.DestroyAll.