What are my options?

Latest version.

In my multiplayer game there are different levels that any player can access independently. So players that are not together don’t need to share information.

I noticed that rpc sends data to all clients and there’s no way to change this. So I’ve been keeping an array of users for each area and then using rpc_id to loop through the players in the area that need the data.

I assume this is the correct way to do this. If not please enlighten me.

Next question is, the MultiplayerSynchronizer also doesn’t have any options for which users to sync with.

The two options I can think of are to either write my own synchronizer, which I can’t seem to find any information online about how to sync movement manually because everyone just has tutorials on the built in synchronizer. Any guidance or links to how to do this via code would be appreciated. OR I can have my dedicated server build dynamically create another server instance when a player moves to a new area and dynamically disconnect and then reconnect the player to the new server for the new area. Then all these problems would go away because each server would only contain the players in that area.

The second option seems like it might be the way to go, except there are certain things the players need to share even when they are in different areas and I’m not sure how I would handle data transfer between server instances.

Thoughts and ideas? Thanks!

That is correct, the other option is MultiplayerSynchronizers and their visibility settings.

This isnt true visibility. (Sorry im responding in real time.)

I dont believer there are any guides out there for making your own syncer. But it is a very simple node that interfaces with the SceneMultiplayer class (a MultiplayerAPI implementation, you interact with it as multiplayer).

The spawner and syncer and RPCs register configurations with the SceneMultiplayer to do various checking of things like visibility authority and rpc attributes.

So if you want to write your own syncer you can look to see how visibilty works in the SceneMultiplayer source code. But i bet its very similar as the rpc array method you mentioned.

So just stick that into its own node and make the proper parameter map to sync data and you should be on your way.

I did see visibility and I took it at face value. That it would just hide objects visibly. Based on your response, I would assume turning off visibility also stops it from synching data to that ID.

My concern is this:

Let’s say I have 2 areas. I spawn 10 players in area 1 and 10 players in area 2. Visibility of players in area 1 is turned off for players in area 2 and vise versa, however. From what I understand, this still means I have to send unnecessary updates to all clients in area 2 every time an area 1 player joins. Creating the player object and then marking it invisible. Which also means all clients in this scenario will have 20 player objects, even though only 10 are relevant. Seems like extra overhead. Maybe at small scale is not a big deal, but if I have 10,000 players. Clients handling 10,000 objects when there’s only 10 in their area seems like a bad idea.

Thoughts?

Either direction you go you will have to manage it some how.

As far as i know visibility works hand-in-hand with spawners. and when something becomes invisible it is despawned from the client. so all you would have to do is set the visibility once for the level and it will despawn everything underneath it in the tree for the client changing levels.

only the server will have to keep track of both levels and all players. the clients will only need to know about the level they exist in. of course, any cross information visible to all players needs to be managed outside the levels.

Warning though. I have read issues here with large scenes (like levels) and child spawners, running into replication failures because the level isn’t fully loaded when the child spawners, that dont exist yet, are asked to spawn nodes. So unitl it is fixed you may need to organize sub spawners with care. (i.e. only nest spawners in small scenes and never in large ones.)

OK. I understand based on what you’ve described.

When you say small scenes, what do you mean exactly? Here is an example node structure:

Root
-levels
–level1
—spawner
–level2
—spawner

Upon player join level1, use level1 spawner to spawn player, then loop through all connected clients and if clients level is not this level, set visible to false.

Upon change level from 1 to 2, use level1 spawner to despawn, then use level 2 spawner to spawn and repeat the process.

Or…

Root
-levels
–spawner
–level1
–level2

Upon player join level1, use level1 spawner to spawn player, then loop through all connected clients and if clients level is not this level, set visible to false.

Upon change level from 1 to 2, loop through all connected clients and of the clients level is not this level, set visible to false else set to true.

The second option seems better? But either way I’ll have to loop through every client and set visibility. Let’s assume there are 100 levels, and each level contains 100 synced clients, as well as 100 synced objects. I’m wondering when I’m going to start having performance issues and if I need to think about running each level as it’s own server and just pass the clients between servers and then handling communication between servers when necessary.

Thanks for your guidance, your thoughts are appreciated.

no, it should be inverted; So I went to try and prototype this to make sure. So what needs to happen is you set MultiplayerSynchronizers to be NOT public. Then when a player joins, or enters a level, you then loop through all players in a level and set visibly to true.

then yes, when a player leaves a level, or disconnects, all sync nodes need to update visibility. otherwise you will get spawner errors, of spawner not found, for things that still want to be visible to the player.

Any way what i found from my attempt with visibility is that I had the wrong assumption on how visibility works currently. I was hoping if a node had nested players that a top level visibility change would just cascade visibility for free. but it doesn’t. So yes you will have to go through every object and update visibility. but it will only effect the players levels that are transitioning a player.

so for your scenario of :

if one player transitions a level where each level has a 100 players. the total visibility changes would need to be 400 ( 100 for peers existing in the level x2 and 200 for the transitioning client ). To me this is still pretty small, but if there are other random synchronized objects then the number will increase.

Doing this all in GDscript would become a concern at some point.

There is a visibility_filter concept that could be managed and optimized per level. you would want to extend the MultiplayerSynchronizer class to talk with a singleton node and listen for signals on player changes and get the appropriate filter. then it will run through the filter and update visibility for that client. But the performance concern still remains.

I think cascading visibility should be added to the Godot SceneMultiplayer. This could manage visibility like a tree like structure and adjust visibility configs on the fly in native code.

Another issue i contended with in the prototype is world overlap. This problem will exist for the server and introduces a new complexity to collision handling. One way this can be overcome if you displace the position of each level on the server.

But you may want to create viewports so each level has its own world. And if you do this why not just create a multiplayer server for each world. Then you wouldnt have to contend with any visibility.

This can still be achieved in one process with a branched multiplayer setup. (One server for each subviewport branch. Each server with its own port.)

Then when a player changes levels the player disconnects from one server-level and reconnects to another. (The connection transition can happen in an automated fashion with a signal of ip/port details from the server before disconnect.)

This would also allow you to scale your network. Since enet can only handle 32 connections at a time.

I think you might run into performance issues before you even get to ~2000 synced objects. You’re going to need a serious server to process that many objects in one game instance. If you need to separate the levels into different instances you won’t have to worry about the overhead of the visibility system.

Either way it’s ok not to worry about performance until performance issues actually stop you from continuing development.

1 Like

Thanks for the input.

Enet actually has a maximum peer limit of 4095. I found this out when everything stopped working after I set max clients to 10000.

I think in the end I’ll be doing separate servers anyway. So I may as well bake it in from the start.

I haven’t messed with the spawner or synchronizer yet as I’ve just finished account creation and data tracking. Now that I’ve completed that I can work on managing the levels and players within them.

One issue I ran into is that Godots data structures (resources) cannot be sent via rpc.

I tried many options, from turning on allow decoding of objects to recursively inst_to_dict and finally landed on using var_to_bytes_with_objects

If I go with the multiple server option. What are the best options for transport of data between servers? Rpc between servers would require custom work I assume.

1 Like

Yeah there’s no great solution for serializing structured data in GDScript. That said, var_to_bytes_with_objects is genuinely unsafe since it can be used to send any object and objects can run code. It will also waste a lot of bandwidth because it serializes objects generically.
You probably want an Array/PackedByteArray (where bandwidth matters) or a dictionary (where it does not) if you’re using gdscript. I hope the new struct PR helps with this issue.

I made a solution that will not expose you to remote code injection, but im not sure how scalable it is.

It leverages multiplayersynchronizers, with reliable, on-change, updates of resource values.

Because it is a class you can get code completion, and access it via the parent class namespace. ParentClass.ChildClass type

Interesting solution. This is only possible because they added the watch option on synchronize.

If I understand correctly, we can load an empty resource on client and server with a synchronize attached to watch if the variables of that resource change value. Then when the server loads the data it can assign those variables the loaded data and because they changed, it will synch them to all of the clients or just the ones that have visibility.

I like it, I’m moving more and more to using a real database solution though. I was really trying to avoid this. Either way still might use this. Thanks!

I am aware of the vulnerability, unaware of the bandwidth issue. The var to bytes with objects was a temporary solution.