I am trying to move the position of an object (the player, local player authority included) right after a new scene is loaded. The object has the DontDestroyOnLoad flag and has gotten a “role”. The role is a syncvar and set in the previous scene.
As soon as the scene is loaded, I move the object depending on the role it has. The problem here: For everyone but the host, that role is the default value at that moment, thus the object doesn’t get moved at all. It seems like the object is reinstanced with default values first and then the values get back to what they were in the previous scene after the sceneLoaded event.
Is there an event that I can use that makes sure all syncvars are already correct, but as soon as possible after the scene load?
Is there another way for me to specify the positions of the player owned objects in the new scene?
Here a few code snippets:
[SyncVar (hook = "OnChangeRole")]
public RoleController.Roles role = RoleController.Roles.Unassigned;
Short Answer:
UNET + DontDestroyOnLoad is not really supported (crazy right… I can’t believe it)
Long Answer:
I’ve spent many dealing with this bug. For me, it’s the biggest bug UNET has although it’s not a popular bug (maybe most people don’t have persistent DontDestroyOnLoad UNET objects and do scene changes?). After bug reporting and talking to Unity QA they have added this to their postponed issue list and will hopefully deal with it during the next major pass. But they stated they cannot fix the bug right now the way UNET is currently set up.
The cause for the bug is because NetworkIdentity.observers are removed & re-added during a scene change… even for persistent DontDestroyOnLoad objects. Persistent DontDestroyOnLoad objects are treated just like scene objects. So during scene transition (~10 frames give or take) any UNET messages with SyncVars/RPC’s/etc. are not sent/received properly. When the scene loads everything is reserialized again… almost as if the object is respawned. The game host can even fail to communicate with itself! This causes a variety of bugs.
-The object is deserialized & reserialized each scene change.
-SyncVars hooks can fail to be called (although the value still is changed properly)
-SyncList callbacks can fail (although the value still is changed properly)
-RPC’s can fail
-Network Destroy can fail
I am not 100% sure if your issue is related to this, but probably. To debug what you can do is check the value of the SyncVar in an Update() loop, and I believe you’ll find that the value of the SyncVar does get changed after the new scene is loaded. But the SyncVar hook will not be called. Let me know if this is the case.
There are ways to work around this UNET bug, but they are really nasty.
I remember checking for the SyncVar in the first Update() after the scene change and it was still on default value. Some frames later it was correct. Not sure about the hook, I have not checked if it gets called yet. Since the syncvar shouldn’t have changed and just stayed how it was in the previous scene, I don’t really need to get my hook called anyway.
I might be able to get around my problem by checking every frame after the scene change if my role is still “Unassigned” and only call my placement function once it is back to what it should be. Not a great solution, but I guess it works at least.
I will be back at my PC tomorrow and can investigate a bit further, but it might really be connected to what you are describing. I will check if my hook gets called and when the syncvar approx. gets it’s correct value assigned. Thank you Zullar for a very great answer. I hope this bug is being fixed soon.
Yep. I believe this will work. Although it’s ugly.
The reason the value is not set on the first Update() and it takes a while is because UNET does something like this.
Client’s NetoworkIdentity.observer removed (at start of scene change)
Client changes scene
Client sends message to server saying the scene has been loaded
Client begins performing Update()'s
…after some delay (latency) server receives message saying the client’s scene has been loaded
Client’s NetworkIdentity.observer is re-added
Server pushes an OnSerialize (initial state = true) to the client
…delay (latency)
Client receives serialized message and performs OnDeserialize. This assigns SyncVar value but does NOT call hook.
So that’s why a few updates can go by before the SyncVar value is updated. And that’s also why the SyncVar hook fails to be called (because OnSerialize initialState = true).
Your proposed work-around should fix the issue. There are other workarounds too. Let me know if you have any questions.[/QUOTE]
Unfortunately my workaround did not fix the problem. Turns out trying to move to object as soon as the syncvar gets updated is still too early. I am not sure what actually sets the object back though.
At the start of the new scene, the object is still where it was in the previous scene. I move it to its new position as soon as the syncvar is correct. A few frames later it is back to where it was in the old scene. Moving it again once that happens works as intended and it will stay at the new position.
I checked isLocalPlayer and hasAuthority before moving and they were both true. It somehow seems like after loading the scene the transform is being set by the server once, but very late. Moving it that late is actually visible to the player and not a viable solution. I am pretty much out of ideas right now except for delaying and faking the “loading” of the new scene for the player so that I can wait and set everything up before showing anything to the player. Destroying and completely rebuilding the object in the new scene is another possibility, but a lot of work for something that shouldn’t be that complicated in the first place…
Do you have a NetworkTransform attached? Or is the position being set regardless?
I noticed that when you “spawn” an object it also sends the position (but not rotation) automatically even if there is no NetworkTransform. And during a scene change persistent DontDestrOnLoad objects are almost treated like new scene objects and re-spawned (everything is reserialized/deserialized with initialState = true). I’m not 100% sure this is what is going on, but this seems like the most likely cause, although I do not know if there is any way to fix this.
Maybe you could use a workaround like this (not nice, but might work): You calculate the new position shortly before the actual scene change and transmit it from server to clients. The clients safe this position (or whatever data you need) in a temporary storage which is set to DontDestroyOnLoad and survives the scene change. After the scene change, the clients instantly fetch the data from this storage object and are able to manually set the position/data even if the SyncVars are not ready yet to receive updates.