Skip to content

Introduce a MultiplayerEntity node for improved identity and authority management #12217

Open
@JoNax97

Description

@JoNax97

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 the MultiplayerEntity 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 via spawn_command is not a MultiplayerEntity, 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 an InstancePlaceholder.
      • 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.

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 notification NOTIFICATION_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 (not MultiplayerAPI 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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions