Description
Describe the project you are working on
A server-authoritative multiplayer adventure game
Describe the problem or limitation you are having in your project
Multiplayer in Godot has improved greatly in 4.0, with the addition of the MultiplayerSpawner
and MultiplayerSynchronizer
nodes making it very accessible to set up basic multiplayer functionality.
However, the system currently lacks some high-level management functionality that makes it challenging to create gameplay that requires interaction between multiple entities across a distributed session:
- Nodes lacks a consistent, network-wide identity, making it difficult to reference them in RPCs and synchronize them effectively.
- Authority management is manual and can can get out of sync easily.
- Scene loading does not support multiplayer very well, requiring custom workarounds that are unintuitive and prone to race conditions.
In addition, networking concepts that are core to the idea of a "networked entity" are scattered around:
- Authority is individually set for every Node
and not inherited
- Visibility is split between MultiplayerSpawner
and MultiplayerSynchronizer
Describe the feature / enhancement and how it helps to overcome the problem or limitation
Introduce a new MultiplayerEntity
node that provides built-in network identity and authority handling, and helps support replication across peers.
Add mechanisms to SceneTree
/SceneMultiplayer
to allow for easy handing of these entities spawning, syncing and referencing across peers.
Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
New MultiplayerEntity
node
- Inherits from
Node
- Provides the following API:
Properties
VisibilityUpdateMode visibility_update_mode
Methods
void _network_ready() virtual
int get_network_id()
int get_authority()
bool has_authority()
void set_authority(peer: int)
void request_authority(peer: int)
bool is_visible_to(peer: int)
void add_visibility_filter(filter: Callable)
void remove_visibility_filter(filter: Callable)
Signals
authority_changed(new_authority: int)
authority_request_denied()
visibility_changed(for_peer: int)
Network Identity
Each MultiplayerEntity
will have a unique network_id
that will be generated by the authority, using MultiplayerPeer.generate_unique_id()
and automatically replicated to all other peers. This id will not depend on the node path and will remain the same for the lifetime of the node.
MultiplayerSynchronizer
will support syncing properties of type MultiplayerEntity
via their network_id
. This replaces reliance on fragile node paths or manual synchronization via RPCs.
RPCs will support passing MultiplayerEntity
-derived nodes as parameters and will also use the network ids for sending the target of the RPC. This will improve over the current system, in which each peer has a different id for the same node and broadcasts it to all other peers.
Authority Handling
Authority handling will be made more streamlined and less prone to desyncs and misbehavior from the clients:
- Only
MultiplayerEntity
instances will handle authority. Children of theMultiplayerEntity
will always inherit its authority, so there is no need propagate it manually. - Authority changes are automatically replicated to all clients.
- Authority can only be changed by the server.
set_authority
will fail if called by a client.
Instead, clients (even the current authority) must request authority transfers using the new request_authority
method.
The server can implement it's request handling logic by using a new method in MultiplayerAPI
:
void set_authority_request_handler(handler: Callable)`
and passing a method like this:
bool can_take_authority(entity: MultiplayerEntity, int peer)
If the authority request is approved, all clients will receive the authority_changed
signal. Else, the client that requested the change will receive the authority_request_denied
signal.
Spawning and Scene Integration
There are two ways in which MultiplayerEntity
nodes may be introduced into a the scene tree:
1. Spawned via MultiplayerSpawner
- The peer with authority over the spawner will be responsible for assigning a network id. This will be done automatically as long as the root of the spawned scene is a
MultiplayerEntity
. - Other peers will receive the network id as part of the spawn command, and automatically assign it.
- The network id cannot be changed by using a custom
spawn_command
. Even if the client decides to spawn a different scene, the id will remain the same. If the node passed to the spawner viaspawn_command
is not aMultiplayerEntity
, the spawning will fail.
2. Placed manually in Scenes
- Scenes containing static
MultiplayerEntity
instances (e.g. environment props) will undergo special handling:- When exporting a scene, any
MultiplayerEntities
will be replaced by anInstancePlaceholder
.- If it's an instanced scene, a reference is stored.
- If not, the tree branch is extracted and exported to a new
.scn
file.
- On the server, placeholders are immediately instantiated and assigned a
network_id
. - The server will send spawn commands to tell the clients which placeholders to instantiate and what their most up-to-date state is (based on the data saved on the placeholder's stored values).
- If the entity is not visible to a specific client, the spawn command will be omitted. If the visibility changes later, the entity will be spawned at that time.
- When exporting a scene, any
This mechanism ensures the client does not spawn nodes at incorrect locations, nodes that have been destroyed server-side, or nodes that are not visible to the client.
Scene Loading Support
A new API method will be added to MultiplayerAPI
:
void change_scene_to_file(path: String)
This is intended to replace the use of SceneTree
for multiplayer game, and will work as follows:
- The server will load the scene as normal.
MultiplayerEntity
nodes will be loaded from their placeholders and given an id. - The server will send a load command containing the uid of the scene along with a list of entities to instantiate.
- Clients will send a confirmation to the server upon loading the scene and all placeholders.
- When all clients have loaded the scene, the Server will call a new
_network_ready()
method on all entities of the loaded scene, and also send a command to all clients players to do the same. Other nodes will not have this virtual method to avoid bloat, but they can still react by listening to a new notificationNOTIFICATION_NETWORK_READY
- For late-joining clients, the server will automatically send the load command and
_network_ready()
will be called immediately.
This will allow for the following:
- Avoids pop-in for instantiated entities
- All clients can rely on entities having a valid id upon startup.
- Late-joining clients will load the correct scene with the correct state.
Visibility
The visibility management responsibility is moved from MultiplayerSynchronizer
and its API is simplified: instead of having both filters and individual setters for each peer, only filters are used. This is to make the API simpler (the old behavior can be easily replicated by a filter that checks against a list).
The MultiplayerSynchronizer
and MultiplayerSpawner
nodes will both consume the visibility information from the Entity to perform their own logic.
Deprecations and Compatibility
- The
Node.multiplayer
property will be deprecated (notMultiplayerAPI
itself). - Developers must transition to using
MultiplayerEntity
for authority-aware networking.
Future Considerations
- Debug utilities may be introduced to visualize network ID assignments and authority ownership.
- Potential support for assigning network groups to
MultiplayerEntity
nodes to manage visibility and interest culling.
If this enhancement will not be used often, can it be worked around with a few lines of script?
The network identity part I think it's not feasible to implement in scripts, specially not in a way that RPCs and MultiplayerSynchronizer can benefit from.
Other parts of this proposal can probably be worked around, but not easily. It would probably require implementing a custom MultiplayerAPIExtension, alongside a custom node type. I think this improvements are general and useful enough to guarantee implementing in core.
Is there a reason why this should be core and not an add-on in the asset library?
See above.