Replicated remote gameObject is reset when changing scenes

Hi!

We have a prefab for the ship that the player controls in which we have added the Coherence Sync class.

This prefab also includes other classes which has the DontDestroyOnLoad set.

In our game the players construct their own ships out of build components, and when the remote player prefab gets replicated to the clients each client spawns the corresponding building blocks as children to this gameobject.

This works well in the menu scene where players connect to the server room.

But when transitioning to the game world scene (SceneManager.LoadSceneAsync) we run into a problem. The remote player shows up, but it seems the gameObject for the remote player has been reset, it does no longer include the build components that was spawned in the menu scene.

The bridge is set as MainBridge. And the player ship prefab has the DontDestroyOnLoad set.

Should we do the scene change in some other way?

How do remote clients spawn the building blocks? Is each building block a separate CoherenceSync entity using CoherenceNode?

No the building blocks are not separate CoherenceSync entities, only the parent GameObject has CoherenceSync.

Do you plan on enabling your players to be on different scenes whilst connected to the same room/world?

If thats not the case, you might want to try removing the call to SceneManager.SetClientScene. I’m doing some local tests, I have to investigate more but I think there is a bug there we need to fix when entities get placed as dontdestroyonload then you change scene and update the scene build index.

We currently have the shipyard where the player can rebuild their ship in a separate scene, so when a player reaches an island with a shipyard the player can change scene to rebuild the ship and then change back again to the game world scene.

I’ll try to explain a bit more.

Is not a bug per se, just doesn’t play very well with your setup.

In the scenario where you have two clients:

  1. Both clients connect in the main menu scene (build index 0)
  2. Players construct their ships
  3. Both players transition to game world, but the scene build index change is not synchronized, this means that if client 1 changes scene index, he is updating the scene index for his player entity, if client 2 hasn’t updated his scene index yet, he will lose visibility of the player until he changes index himself, which causes the player entity to get recreated.

My main advice if what I mentioned in the previous post, if you don’t specifically need to update the scene index, is better to not do it since the implementation you have to do is a lot less complex.

There are other options but are a lot more cumbersome, like having a way to serialize and deserialize the building blocks, or perfectly synchronize the scene index change using the client simulation frame or something like that.

Thanks for the explanation!

We are still relatively early in the development so we are flexible when it comes to choosing solution here.

I guess one option would be that the remote players are recreated once they enter the game world scene? I havent quite understood what this SetClientScene function does, what is the consequence if we dont call it? That the spawned entity remains in the menuScene?

On the same topic, so far we are recreating the remote players based on a default building block configuration, but we need to somehow transfer the actual building block configuration from the client where the player is on to all other clients.

The Configuration consists of a list of prefab names and for each item there is also a position and rotation.
I first thought I could add this as global and syncronized variable through the CoherenceSync system, but seems that one doesnt support lists or vectors? One option would be if we encode all the data into a string, is there some restrictions for how large the string can be? Or perhaps there is a better or even built in way of sharing data structs?

So you can think of the SetClientScene as an additional entity filter on top of a LiveQuery. When you change the client scene index, all the entities you create will be created using that scene index, and a second client who is in a different scene index will not see the entities you have created. That is the purpose of the client scene index. Additionally, when you change the scene index, any entities that already exist and you have authority over will also change scene index, meaning that other clients that are in a different index will stop seeing these entities, which is what is happening to you.

This also means that changing scene index is only meant for games with instanced content where several clients can exist in different scenes within the same room/world, which, if I understood correctly, seems like is your case.

We do not support lists of primitives yet, but we do support byte arrays, so you could do a custom serialization for your existing building blocks if needed. You could also make each building block a separate CoherenceSync entity and let coherence handle parenting by using the CoherenceNode component, depending on how many building blocks you allow, this could potentially be a lot more costly bandwidth wise than using a simple byte array to reference the needed building blocks and recreate the hierarchy when you deserialize this byte array remotely.

If you’re pretty early in development, I’d suggest going for making each building block a separate CoherenceSync entity with a CoherenceNode component, and later down the line you can profile the bandwidth usage and decide if you need something lighter or not.

Thanks!

I tried serializing the build configuratin to a byte array now, size is around 5k bytes for a medium sized build, abit more than 511 bytes, so I will try the other method for now and add coherencySync to the building blocks.

Wow that’s a lot, how are you serializing it? If you’re using something like the BinaryFormatter there is a ton of extra data that is serialized in order for the formatter to be able to seamlessly deserialize it back to your class, like information about the assembly and so on.

You can probably get a much much lighter byte array by doing a bit more manual serialization using a memory stream and a binary writer and writing each field manually.

Obviously this is a bit more cumbersome than just using sync on the building blocks so it’s probably safe to use this option for now.

Yes, we are using BinaryFormatter to do the serialization, did some measurements and just like you said its a very large overhead! Need to use a different method then…

I did some compression on the format before serialization and got it down to 1758 bytes and also tried splitting the result up into several byte arrays to be syncronized, but then I hit the limit of the network package size :wink:

Btw, I’m on the discord server and would be great if we could have a quick chat there.

I’m miguel.ferradans on discord, feel free to dm me

1 Like