Godot High-level Multiplayer

Limitations of Godot's ENet Implementation

  • Although ENet is an excellent low-level multiplayer networking library, Godot implements ENetMultiplayerPeer  in a simplified way, which may not be the best choice for large-scale games like MMOs  due to lack of flexibility in network control, scalability, and customization.

  • Lack of clustering and load distribution support :

    • ENetMultiplayerPeer does not have built-in resources for load balancing between servers or for dynamic player partitioning.

    • Network systems for MMOs usually use a distributed network structure so high-density player areas don’t overload a single server. This is not natively supported in ENet.

  • Persistence and Global State :

    • MMOs generally require maintaining a persistent global state (world information, inventory, player progression, etc.). While ENet provides reliable and ordered communication, it doesn’t handle persistence or state replication.

    • For MMOs, it is common to use a network structure that interacts with databases and robust caching systems. Custom solutions for this kind of persistence are not native to ENet.

  • Security and Moderation :

    • ENet offers little native security against common attacks on MMO servers, such as DDoS or packet manipulation. Protocols developed for MMOs typically implement robust authentication, encryption, and attack mitigation mechanisms, often with additional support for cheat detection.

High-level API

MultiplayerAPI
  • Abstract implementation. Cannot be instantiated.

SceneMultiplayer
  • Concrete implementation of MultiplayerAPI, can be instantiated.

MultiplayerAPIExtended
  • get_tree().set_multiplayer(MultiplayerExtended.new())

    • The pathing is not passed, since I want to override the default multiplayer.

    • By doing this, multiplayer == _game_server == get_tree().get_multiplayer()  for ANY node in the game.

      • This is valid even for nodes that spawned later.

    • This can be done in _enter_tree() , _ready() , or elsewhere, but prefer _enter_tree() , since the earlier the better.

  • Using MultiplayerAPIExtension causes multiplayer.get_remote_sender_id()  to not work.

Synchronization

Authority
  • Authority cannot be set in _ready() .

MultiplayerSynchronizer
  • Serialization :

    • "Also serializes automatically".

  • Synchronization of spawned objects :

    • If MpSync misses the first sync rotation in the scene, it will not receive further signals.

    • Changing visibility to PubliclyVisible outside of _ready()  does nothing, you must use set_visibility(peer_id)  or a visibility_filter to apply visibility changes.

MultiplayerSpawner
  • Synchronization of spawned objects :

    • Spawned objects will copy everything defined between .instantiate()  and add_child() . After add_child() , no more copying occurs.

    • If a client logs into the server after something has been spawned by MpSpawner, the new client will also get these spawns, even if they were not present at the time of the spawn on the Server.

    • Only use _death.rpc()  if you are not using MpSpawner, since MpSpawner WANTS to despawn all instances of the instantiated scene.

    • (2024-11-11) Dynamic spawns do not work well with my CustomMpSync script.

RPC (Remote Procedure Calls)
  • "request / fetch -> receive / retrieve."

  • Functions callable on other peers.

  • RPC or MultiplayerSynchronizer for inputs?

    • Via MultiplayerSynchronizer:

      • input_jump = Input.get_action_strength(&'jump') .

  • Serialization :

    • "If I pass a parameter to an RPC call, is it automatically serialized before sending it to the other peers?"

      • Serialization and deserialization is automatic.

  • Cannot send :

    • Objects.

      • Includes Nodes and Resources.

        • Demonstration of what 'not serialize' looks like .

        • "Is there a reason for RPC calls to not serialize objects (nodes and resources)? Seems to be a way to serialize the object, so I wonder why it isn't used for RPC calls."

          • Objects in Godot have been designed with the ability to contain their own scripts if needed.

          • Which unfortunately means that if an RPC could carry an object, it could also carry a custom malicious script that would run _init  automatically on the other PC and basically turn Godot into a trojan.

          • Also, if you try to send an object, it will convert it to an object ID, which is meaningless on the other PC.

    • Callables.

      • Other peers only receive null  in place of the Callable.

  • Errors :

    • When calling an RPC on the Server, but the Client does not have the node:

      • The Server has node authority; i.e., authority was not changed.

  • Quirks :

    • Same Node Path:

      • For a remote call to be successful, the sending and receiving node need to have the same NodePath , which means they must have the same name.

        • If an RPC resides in a script attached to /root/Main/Node1 , then it must reside in precisely the same path and node on both the client script and the server script.

      • When using add_child()  for nodes expected to use RPCs, set the argument force_readable_name  to true .

    • Declaration on Server and Client for dedicated servers:

      • If a function is annotated with @rpc  on the client script (resp. server script), then this function must also be declared on the server script (resp. client script). Both RPCs must have the same signature which is evaluated with a checksum of all RPCs . All RPCs in a script are checked at once, and all RPCs must be declared on both client and server scripts, even functions that are currently not in use .

  • Points ok :

    • Calling the RPC in the case of a Dedicated Server can be done with either callable.rpc_id(peer_id)  or rpc_id(peer_id, &'callable_name') . The first option is better and safer as it does not involve strings.

    • (I think this is false) Function arguments can be different.

      • Function arguments are not checked for matching between server and client code (example: func sendstuff():  and func sendstuff(arg1, arg2):   will pass  signature matching).

  • Peer id :

    • "The one calling the function is peer 0 and ALWAYS exists, otherwise they could not call the function to begin with. Peer ids are dependent on the perspective of the client.  If you have client A and client B connected to each other. Client A will see client B as peer 1 and vice versa. And each of those will see themselves as peer 0."

Others
Ensuring Connection
  • Port-forwarding

    • If you're hosting a server on your own machine and want non-LAN clients to connect to it, you'll probably have to forward the server port on your router. This is required to make your server reachable from the Internet since most residential connections use NAT.

    • My Public IP .

  • Android

    • When exporting to Android, make sure to enable the INTERNET  permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android.

Channels
  • For example, game chat messages and some core gameplay messages should all be sent reliably, but a gameplay message should not wait for a chat message to be acknowledged. This can be achieved by using different channels.

  • Modern networking protocols support channels, which are separate streams within the connection. This allows for multiple packet streams that do not interfere with each other.