Multiple scenes loading ontop of eachother
The whole answer can be found below.
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).
Multiple scenes loading ontop of eachother
drohack
2016-10-06 04:43:51
Using PUN 1.75
This is an odd bug. In my game there are 3 scenes. The MainMenu, LobbyRoom, and Game. The game boots up in the Main Menu. When a user "Creates a Room" they make a Photon Room and load into the LobbyRoom scene. Once all players ready up it loads into the Game scene. In the Game scene all players can vote to "Restart". If they do they load back into the LobbyRoom scene.
The issue is that if the player(s) "Restart" and a new player joins the Photon Room (or a player leaves and re-joins) all of the players who were in the room before the new player joined "Freeze" (run very slowly) and get messages in their console that "QueueIncomingReliableWarning. This client buffers many incoming messages...".
And the new player (and only the new player) that joins sees both the LobbyRoom scene & Game scene loaded on top of each other. (The "QueueIncomingReliableWarning" might only be happening because we have quite a few objects that spawn in in the same position and are trying to re-orient themselves).
I don't actually see 2 scenes loaded in the "Hierarchy", but all of the objects that were loaded in the Game scene are loaded in.
My first guess was that it was remembering both scenes being loaded and syncing both scenes. But as I'm writing this I have a second guess. All of the objects that we load in we use PhotonNetwork.InstantiateSceneObject(). Is it possible that these objects are still in memory after we switch scenes? (but for some reason when we actually LoadLevel for the players in the room they are "destroyed")
We have this variable set in the MainMenu: PhotonNetwork.automaticallySyncScene = true;
And to transition between scenes only the MasterClient loads the next scene: PhotonNetwork.LoadLevel(SceneNameRoom);
Comments
Checking the forums a bit more closely I've found these 3 post that sound close to what I'm experiencing.
Especially this one here: http://forum.photonengine.com/discussion/comment/14770#Comment_14770
Should I be using a different Room per Scene? This seems un-intuitive to me right now. Since the objects are instantiated per Scene, not Room. (and they are not in the DoNotDestroyOnLoad).
The other post that sounded similar to my issue was this:
http://forum.photonengine.com/discussion/7205/all-network-instantiated-objects-in-the-scene-recreate-upon-player-joining-game
http://forum.photonengine.com/discussion/6285/solved-changing-scenes
Which also mention loading a Room per Scene.
What is the best approach to moving all players in a current Room to a new Room when changing scenes?
As I mentioned this is all a little un-intuitive and I would expect PhotonNetowrk.LoadLevel() to use PhotonNetwork.Destroy() on all objects in the old scene as stated in this very old post:
https://forum.unity3d.com/threads/help-problem-instantiatedobjects-chang-scene-from-lobby-photon.196930/
I've found a workaround. Before changing scenes I just find all GameObjects that have the PhotonView component and run PhotonNetwork.Destroy(go) on it (besides the ones that are tagged with specific names I don't want to destroy. I.E. are the objects that are in Don'tDestoryOnLoad).
GameObject[] objects = GameObject.FindObjectsOfType();
foreach (GameObject o in objects)
{
if (o.GetComponent() != null && !o.tag.Equals("Player") && !o.tag.Equals("MainCamera") && !o.tag.Equals("Console_Text"))
{
PhotonNetwork.Destroy(o);
}
}
PhotonNetwork.LoadLevel(Const.SceneNameGame);
This hasn't seem to slow down changing scenes, but looping through all GameObjects can't be that efficient...
Hi,
Have you experimented with turning your networked gameobjects into some kind of singletons?
your networked gameobjects are instantiated right? they are not sceneObjects, so unloading or loading new scenes won't affect them. One solution is to keep track of them via a manager so that you only create them when needed. What you don't want is destroying a gameobject that you'll recreate right away, isn't it?
So the first thing here is to analyze accuratly the situation. Is the objects that persists are meant to be cleaned up because you load a new scene or not. I think they don't, so the problem has to be tackled programmatically to keep track of everything during the loading process and avoid duplicating.
Maybe as a possible starting point, you could check the Pun Basics demo inside the photon package, it features synched scenes loading yet player remains and is not destroy and recreated, only the level is changing.
Bye,
Jean
Hi Jean,
All networked object's I'm loading get instantiated with PhotonNetwork.InstantiateSceneObject()
. This is so all players can interact with the objects (by taking over the object) and if a player disconnects for any reason these objects persist. And when changing scenes these objects should NOT persist (and they don't stay around for the people already in the Room).
The issue is that the networked objects appear to be Buffered as being instantiated even though we've changed scenes and they are no longer present for the current players in the Room. When a new player joins the Room (after a few scenes have been loaded) ALL of the Buffered instantiated objects get loaded in for that new player.
My "workaround" works in most cases, but I was able to find another case where this bug appears. Lets say there are 2 players (P1 and P2) and P1 makes a game (original MasterClient). P1 loads into the LobbyRoom scene. P2 joins the game (synced into the LobbyRoom scene). They both ready up causing P1 (the current MasterClient) to loop over all GameObjects and PhotonNetwork.Destroy() all GO with PhotonView components. Then loads the Game scene (causing P2 to sync into the Game scene). During the Game scene P1 disconnects causing P2 to become the new MasterClient. P2 "Resets" the game causing P2 (the current MasterClient) to loop over all GameObjects and PhotonNetwork.Destroy() all GO with PhotonView components. Then loads the LobbyRoom scene. Finally P1 re-joins the game syncing into the LobbyRoom scene. BUT when P1 re-joins all networked objects in the LobbyRoom are doubled. The Game scene objects that were destroyed by P2 are not present. My guess is because only P1 ran the loop to destroy all GO with PhotonView components they were the only ones to buffer the Destroy command. So when P1 left during the Game scene (and P2 took over) all of the buffered Destroys were lost, but the instantiation still lingered since they were loaded into P2's game. (This isn't a typical use case, but it can still happen)
So now I'm thinking I need to loop with all players through their GO when changing scenes and destroying all GO with PhotonView so it's buffered correctly if the MasterClient gets switched. But I'm not sure if that's possible as all normal Clients would have to know they are changing scenes and Destroy the GO before going to the new scene (which there doesn't seem to be a function for that. Like a OnBeforeSceneChange()).
Another option would be to clear the Buffer between scenes (except clearing the Don'tDestroyOnLoad objects). This is what I would expect PhotonNetwork.LoadLevel(SceneNameRoom);
would do, but it doesn't seem to be doing it. BUT from what I've read on other posts it's not recommended to manually clear the Buffer. (And I don't know the function call to do that). That is why I'm Destroying the objects instead.
Why does there need to be so much manual cleanup when changing scenes? (isn't that what Object.DontDestroyOnLoad();
is supposed to be used for?)
All of the networked objects in the LobbyRoom scene are different than the networked objects in the Game scene. So they are scene specific (I'm not destroying object's I'll need to re-create right away). The only object's that are not scene specific are what are loaded into the Don'tDestroyOnLoad (my "Player", "MainCamera", and "Console_Text" objects). So having them be singletons wouldn't help. Especially since we're creating multiple networked objects of the same prefab (just in different position/rotation/scale) within a single scene.
Hi,
Thanks for the detailed explanations, make a lot more sense. I am trying to repro this and I'll get back to you on my findings.
Bye,
Jean
Hi,
ok, first thing: I am not able to reproduce the instantiation buffer you mention. I instantiate a sceneObject, and when Loading a new level, it disappear, and it never reappear as other players joins or leave this room.
let's tackle this first. Maybe I am not exactly doing what you do. Could you share a repro scene?
Bye,
Jean
Hi,
Also, in order for you to not get locked with this issue. I would simply not use Photon to load scenes, but instead use Unity directly, and via Rpc call or your own logic for each client, load the appropriate scenes, it may end up being easier in your case. Have you considered this?
Bye,
Jean
Hi Jean,
I was thinking about turning off syncScene and sending an RPC to have the players load into the correct scene to handle all players running the loop on GO to PhotonNetwork.Destroy(). I haven't had a chance to implement that yet. (this note is for myself, I'll have to find a way to determine once all the players have loaded into the new scene, probably just an RPC to the MasterClient saying they have loaded in. My old way probably hasn't actually worked since I moved the Player object to Don'tDestroyOnLoad....)
As for sending you an example project that might be a little tougher. My game is made for VR so I'll have to re-jigger it so it uses the default 2D PhotonLobby (what we've based our MainMenu off of), then add some GUI buttons to the LobbyRoom and Game scene so you can start a game, and restart back to the LobbyRoom (we've kept the "Leave Room" GUI button so I should just be able to expand on that)... This might take me a while, but if I have time hopefully I can get you something after work today. What's the best way to send you an example project? An exported "Custom Package"?
Hi,
Just zip the whole project and pm me. packages for full project isn't ideal, but works of course.
Thanks :)
Bye,
Jean
I didn't read all of this conversation but:
Are you maybe setting autoCleanUpPlayerObjects to false?
Or are you using Object.DontDestroyOnLoad() on all the objects that "survive" loading a scene?
It seems like you somehow break or circumvent the "destroy objects on load" pass, which is the default behaviour of Unity.
In PUN, we only clean up objects when their creators leave. You already worked around that by using InstantiateSceneObject(). This does not affect, however, what the engine does with objects, when it loads a scene.
Maybe Jean can find something in your code / project.
Actually, I just rediscovered a recent fix, which is not yet released.
Please find NetworkingPeer.NewSceneLoaded() and replace it with this:
http://www.hastebin.com/votutuzuto.cs
Maybe that does the trick!
It will be in PUN v1.77
I am not setting autoCleanUpPlayerObjects, so it should be defaulting to "true".
Only the "Player" object and "Console_Text" object are set to Don'tDestroyOnLoad. All other objects are just InstantiateSceneObject().
Unity is destroying the objects as expected. It just seems like Photon isn't clearing them from the Buffer.
I was able to find a "fix" for my "workaround". Along with the MasterClient PhotonNetwork.Destroy() all networked objects when switching scenes. I also send the RPC to "RPCDestroy" the object. The RPC "RPCDestroy" is loaded onto all of our Networked objects that can change ownership. This just asks if "isMine" is true then you PhotonNetwork.Destroy() this object.
if (playerPosOccupied != null && playerPosOccupied.Length == 4)
{
if (((playerPosOccupied[0] && isP1Ready) || !playerPosOccupied[0])
&& ((playerPosOccupied[1] && isP2Ready) || !playerPosOccupied[1])
&& ((playerPosOccupied[2] && isP3Ready) || !playerPosOccupied[2])
&& ((playerPosOccupied[3] && isP4Ready) || !playerPosOccupied[3]))
{
//Destroy all GameObjects that have a PhotonView so they are no longer buffered.
GameObject[] objects = GameObject.FindObjectsOfType();
foreach (GameObject o in objects)
{
if (o.GetComponent() != null && !o.tag.Equals("Player") && !o.tag.Equals("MainCamera") && !o.tag.Equals("Console_Text"))
{
if(!o.GetPhotonView().isMine)
{
o.GetPhotonView().RPC("RPCDestroy", PhotonTargets.Others, null);
}
PhotonNetwork.Destroy(o);
}
}
PhotonNetwork.room.visible = false;
PhotonNetwork.room.open = false;
PhotonNetwork.LoadLevel(PhotonLobby_VR.SceneNameGame); //Start Game
}
}
[PunRPC]
void RPCDestroy()
{
if (photonView.isMine)
PhotonNetwork.Destroy(gameObject);
}
This seems to have fixed all of the issues I was having.
I did also try turning the syncScene = false and having the players load the scenes themselves (with destroying all objects as they changed scenes), but this caused way more problems than I wanted to try and fix. (objects weren't getting instantiated correctly and was making the flow of changing scenes a bit harder).
@Tobias: I'll try and use that updated NetworkingPeer.NewSceneLoaded() and see if that helps.
@Jean: Sorry I wasn't able to make an example project of the issue I'm having last night. I'll also be out of town this weekend so the earliest I can probably get you my project would be on Tuesday next week. (I would really like to get this project to you guys to take a look, but I also have some assets that I'm not allowed to share currently. So not only is the project in VR which could make testing for you a bit difficult, but also I need to strip out those assets.)
MrVentures
2022-09-28 13:10:23
Hey @drohack thanks for posting. I learned a few things from your work here but ultimately I kept having the issue. However, I have since determined the problem.
In my game, I used your method and I even added a call to PhotonNetwork.DestroyAll() then I disconnected. Upon joining a new room my player would keep auto-instantiating any objects I spawned on them in the previous game. I have since determined that since I disconnected on same frame that I sent the destroy messages that those messages were either not sent or not received. At least that is my working theory.
The solution for me was to add a delay of a few seconds between sending out those Destroy calls and disconnecting from the game/room/lobby. There is probably some Photon method to "flush" messages to ensure they are all sent, but I am not aware of one. So this delay hack seems to be my best option at this time. Hope it helps anyone else affected!
When the game logic calls Disconnect
at any point, this will discard any messages still queued for sending and just send the disconnect message (to let the server know the client leaves).
As the client disconnects, repeating reliable messages until acknowledged is not an option. The disconnection should not be delayed by remaining outgoing messages and or repeats.
Back to top