Can I "mask" MultiplayerSpawners & MultiplayerSyncronizers in multiplayer via MultiplayerAPIExtension?

Godot Version

4.7 dev 3

Question

Hello!
I’m working on a multiplayer game where the server can spawn multiple “layers” that contain peer group, net-async-loading and level nodes. The client should then be able to interact with these layers by replication of only that subset of nodes.

The structure is generally so:

/Root/
|- Network
|- MainScene
  |- LayerManager         # SubViewportContainer or Node
     |- Layer_InstId      # Viewport w/ own_world = True (independent physics)
     |  |- ClientGroup    # List of layer-peers & layer-peer allowances. 
     |  |- NetAsyncLoader # Tool to a/sync loading of level across layer-peers
     |  |- LevelInstance
     |     |- Players
     |     |- ... MultiplayerSpawners
     |     |- ... MultiplayerSyncronizers
     |     |- ... ## Additional Nodes
     |- ... #Additional Layers

The layer needs to be spawned on the Manager and a subset of clients, and the downstream effects of all MultiplayerSpawners & MultiplayerSyncronizers should be constrained to the peers of that layer.

I’ve consigned myself to writing my own implementation of each, but I came across MultiplayerApiExtension, which suggests that I can override the some base behavior (or just config) of networking.

Specifically

  • MultiplayerSynchronizer & MultiplayerSpawner via func _object_configuration_add / _remove
  • rpc forwarding via func _rpc

The docs are somewhat scant on the equivalent base_multiplayer behavior in gdscript however, so I am wondering how (or if) the MultiplayerApiExtension can be used to mask Synchronizer and Spawner calls.

I recognize I can change the visibility of the MultiplayerSynchronizer, however MultiplayerSpawner does not have the same allowance, and nested MultiplayerSyncronizer have the possibility being missed.
All this means I would prefer to control layer syncing somewhat centrally.

Does anyone have any experience or more complex examples with MultiplayerAPIExtension?

Thanks for your time!

I think you should just write your own Spawners and Synchronizers. The built in ones actually don’t do that much, the biggest code savings are probably from automatic serialization (which is not very bandwidth efficient…). Then you will have the freedom to write your own visibility system to match what sounds like relatively complicated requirements.

1 Like

Thank you, I will! They are pretty straightforward.
I found a probable solution using a custom spawner and what is probably the intended solution to allow for subsets of clients, which I’m detailing in a following message.

The simplest solution I’ve been able to test the essentials of has been to avoid requiring MultiplayerAPIExtension at all (though it can still be used as required)

as far as I can tell best option is to host multiple servers on different ports with custom paths through SceneTree.set_multiplayer(SceneMultiplayer, NodePath) which assigns a network controller for a specific nodepath and all children while transmitting relative paths *

I found this through this post, which has more details:
Can you make an ENetMultiplayerPeer server listen on more than 1 port?*
*

*Here is a current test of multiple servers & clients in the same node tree, with a test script modified from above.
*
My current plan is

  • have a central authority assign port numbers
  • write a custom MultiplayerSpawner-like to spawn in layers
  • start the client-server connection in the resulting server.

After that I’ll confirm on this post with the results, and hopefully it will help someone else in the future!

—*

** Important details:
As noted in the docs, MultiplayerSpawner and MultiplayerSyncronizer must be created after the function set_multiplayer has already been called on the intended parent. Otherwise they will not work as expected (in one of my tests calling on the incorrect server)

In my the script from the post I traded _ready() for _enter_tree() to make the ordering work for the test. Due to my needs I will instead have start server-client be a function, and ensure that all syncronizers are spawned after the function is called.