Discussion in 'Connected Games' started by kophax, Apr 7, 2018.
I'm working on a multiplayer game and I haven't been able to find an answer for this yet.
A similar outline was posted in this question 2 years ago, though as far as I can tell the APIs haven't really changed.
I have a basic cut down version of what I'm trying to build.
Scene A has 2 NetworkIdentity in it, Scene B has 1 NetworkIdentity.
I have a scene manager class that sends messages from the server to the clients, having each of them perform a SceneManager.LoadScene additively. They send a message back to the server when they are done loading, and the server spawns objects with NetworkServer.SpawnObjects.
The scene loading is working fine, except for when it comes to the NetworkIdenties.
X in this case corresponds to the SceneId (as I understand it this is an incrementing value and not at all reslated to scene build index), which I'm assuming is an auto incremented value for each NetworkIdentity spawned in the scene.
Any additional objects in Scene B, that aren't in Scene A would get a SceneId of 3+ and would work fine.
Am I missing a core concept here that I'm not understanding properly? I can think of a solution whereby I put in some kind of placeholder object that spawns a prefab at runtime (the prefab would have the NetworkIdentity), but this seems like an awkward workaround.
Is there a better way of ensuring the SceneId don't conflict with each other? This seems to be an entirely internal field that shouldn't be touched.
Is there an easier way to additively load scenes, in UNet, with scene objects (there at build time, not spawned at runtime) that are NetworkIdentities?
I am pretty sure unet HLAPI doesnt support additive scenes. I dont think many other unity networking solutions handle it either.
Your workaround of having a placeholder and instantiating at runtime is probably the more simple and elegant solution.
Another possible way, which is prone to breaking and I think only works so long as you never have the same scene loaded multiple times at once, could be to make your own script that sets up the scene ids in a way that they are unique across all scenes.
Notice they use the [PostProccessScene] attribute. You can make your own script that runs after unitys run to overwrite the scene ids to your desire by putting [PostProcessScene(1)]. Youd be limited to uint.MaxValue (4294967295) networked scene objects in all scenes combined though, but that should be more than enough for many. I guess a simple way would be to take uint.MaxValue and divide it by how many scenes you have in your build, the result will be the max allowed of networked scene objects you can have per scene. Then you can just use the scenes build index and multiply it by the max allowed networked scene objects per scene and that should give you a starting point for each of your scenes postprocessor to set the scene ids.
Even with 1000 scenes, youll still be able to have over 4 million networked scene objects per scene. If they ever change it from a uint to ushort then youll have a lot less though.
I am on a tablet, but I will try to give some untested code.
int maxSceneIdSize = (NetworkSceneId.Value.MaxValue - 1) / sceneCount; // we -1 since we dont use 0. Unity might be using 0 as a way to say its not set.
// if we had a [ConflictComponent] attribute that would be better than this check.
// also there is no context about which scene this is in.
The code above might not work due to the bug I explain next to sceneBuildIndex.
If it doesnt work, then maybe this will.
I never tried this so its all theoretical and I think only works if you just want to load additively different scenes at once and not load a single scene additively multiple times at once (so you can have scene1 and scene2 loaded, but not scene1 and scene1 again).
Thanks for the detailed reply HiddenMonk.
You can see where I commented out the code related to building all scenes on play (so I can run this in editor mode). After several attempts, the same problems seem to arise even though I can confirm that the SceneId was properly set on the additive scene I load at runtime. I still get the same error, I even have logs confirming that the NetworkIdentity on that object has a SceneId that matches the server but still no luck.
I've tried delaying the SpawnObjects call until I'm certain the scene is fully loaded, but no luck. It also seems to run inconsistently when testing between clients and the unity editor, but run properly when testing between to built clients.
I'm going to try a runtime approach similar to this, where I ForceSceneId on all NetworkIdentity as soon as I load the scene, instead of at build time. I'll follow up with my results.
Youve said in your original post that scene objects in other scenes would work fine so long as their sceneID was different than the other scenes.
That would lead me to believe that the new sceneID setting code we have is either not actually working, or is running to late.
Remember that unity will also attempt to set the sceneID's when you hit play and build, so setting the sceneIDs in the editor before playing or building is useless unless its just for debugging purposes.
It also seems to run inconsistently when testing between clients and the unity editor, but run properly when testing between to built clients.
You mean that in the editor you get errors, but in an actual build everything is working fine?
If it works in builds but not in the Editor, then we might have a clue as to whats going wrong.
Does any of your code cause scene stuff to happen during Awake or OnEnable? You said you delayed calling SpawnObjects, but I am not sure if there is anything else being called that might cause issues.
OK. I finally got it working the way I wanted.
I moved away from IProcessScene because I found it too restrictive for testing in the editor when doing additive scene loads. Instead, I do it on the server/client right after I load the scenes, before I send my ready message.
There was an extra step where i had to set ready false, then true on all clients after load, after all that my SpawnObjects call worked fine.
Here's the script in case you or anyone else is interested in this. It's basic, but gets the job done. Has a few flaws around just auto decrementing a counter to assume "done", but it's good enough.
/// The NetworkSceneManager class is used by the server to load, unload, and change the scenes players are currently playing through.
/// have been completed on the clients.
/// Event fired when all clients have finished loading a scene asynchronously.
/// Event fired when all clients have finished unloading a scene asynchronously.
/// Event fired when all clients have changed their active scene.
/// Additively loads a scene on all clients. This method can only be called from the server.
/// Unloads a scene on all clients. This method can only be called from the server.
/// Changes the currently active scene. This method can only be called from the server.
/// disconnects while loading. For now this is good enough.
Player.localPlayer is my isLocal player, that's the connection I use to send the ClientScene.Ready message back to the server.
Juruhn, unity_7Mgl4I4SM_R0_Q and HiddenMonk like this.
Do I need to create a NetworkMessages class and declare the message variables there? And from where I do need to receive the messages and send the callback?
Hi @Aupuma, sorry for missing this reply. Yes you are correct.
NetworkMessages was just a class with static short variables like CLIENT_SCENE_LOADED. I made it for any custom messages so I wouldn't accidentally use the same message id (short) for multiple messages.
I use the `GameNetworkManager` to send the message because as far as I know, you need to send your messages via a `NetworkManager.instance`.
EXACTLY what I am looking for. Getting really strange behaviours with the Networkserver.Loadscene combined with VRTK and Singleton patterns with DND flags.
So, do you store the Player reference to the 'isLocalPlayer' instance in all Player instances on the network somehow?
Looks very simple indeed, but it doesn't click in my head yet.
Is it not so that there are multiple 'Player' scripts on a client, only one is isLocalPlayer and therefor runs the OnStartLocalPlayer and therefor HIS localPlayer variable is set.
The other 'Player's are merely representations of other spawned players (clients) and don't have that variable set.
How is Player.localPlayer then the reference to that Player instance that is the local player. What am I missing?
Yes. Only the player assigned to your client runs OnStartLocalPlayer. Other Player behaviour instances will not call this method.
Player.localPlayer is a static variable. So although technically it could be seen as "shared across all player instances" local on this client (in that it belongs to the class), it is only ever assigned once and only ever represents the Player I have local authority over.
I normally use it from other behaviours, such as in this case where I use it to find the right connection to send the message on. I don't want to teach them about how to find the "current player", so the static variable makes that an easy abstraction to use.
The script provided for you above, could help me in my situation?
Yes @Triggeredge that sounds like the same kind of error. In this case, the server loads the scene, and asks other clients to do the same. They respond that they have loaded it and the server then spawns all the NetworkIdentity scene objects (the ones that are there from the start).
I'm struggling with this same problem, and seems you have found a solution.
I've used your script and the additive scene is loading in both server and clients, but the sync objects (with Networ Identity compoennt )in scenes are not ativated with the additive loading.
how do you have managed this?
Did you get it working? Can you make sure that `SetSceneIds` is being called?
Default UNET scene switching is broken. Clients receive network packets for the new scene before the new scene was fully loaded. This was never fixed, your only option is to use Mirror. I fixed it by pausing the update receive loop while a scene load is in progress.
Note that I never tried async scene load though. So there could be more hidden errors.
I think it's a broad statement to say your only option is to use Mirror (your asset), this was functional for the tests I ran with it. The particular issue I encountered had to do with SceneIds conflicting between scenes (restarting at 0). This approach basically just forces sceneids to be unique based on a scene build index offset. The default unity approach is to reload everything, at which point the scene id's starting over isn't a big deal.
From my preliminary testing it was sufficient in loading a scene additively, and identities exhibited the same behaviours they would if the scene was loaded first and not additively. I will be back working on this additive scene loading stuff shortly and can report any problems I encounter that I hadn't noticed.
What sort of messages are being sent by the server that are missed on the client @vis2k ?
It's not your only option.
Your other option is to download the HLAPI source code and fix it yourself.
All of them can be missed. The server happily keeps sending to clients during scene changes. If you have a fast computer then you won't notice it. If you have a slow computer then the client will receive half the messages for the new scene before it was loaded.
OK i'll have to investigate more. Our game/testing has all been LAN so maybe there's bigger issues with latency.
I'm going to be back on the additive scene loads shortly, I'll explore maybe looking at both options (Mirror plus something like this) to make sure the scene id stuff works as well as the messages.