Remote Calls

Remote calls in GD-Sync rely entirely on Node paths to identify which Node should receive the call or have its variables synchronized. For this reason, the path to the target Node must be exactly the same on every client. If the path differs for even one client, the call or synchronization will fail for that client. Additionally, the Node must already exist in the scene tree before GD-Sync can access it.

When instantiating Nodes such as players, projectiles, or other dynamically spawned objects, ensure they are created in the same location within the scene tree and have the same name on all clients. This consistency allows GD-Sync to correctly match Nodes across the network and apply the intended synchronization or function calls. If you spawn in Nodes using the various methods GD-Sync provides, this is done automatically.

Function Calls

GD-Sync allows you to call functions on other clients within the same lobby. For security, all remote function calls are blocked by default. To allow them, you must explicitly register which functions or Nodes can be accessed remotely. This can be done by using GDSync.expose_func(callable : Callable) to allow a specific function to be called remotely, or GDSync.expose_node(node : Node) to expose all functions on a specific Node.

Once a function is exposed, it can be called on other clients using GDSync.call_func(callable : Callable, parameter_array : Array). All parameters must be passed inside an array. This will execute the function on all clients in the lobby except the one making the call. If you want to target a specific client instead, you can use GDSync.call_func_on(client_id : int, callable : Callable, parameters : Array), which runs the function only on the given client. To execute the function on every client, including yourself, use GDSync.call_func_all(callable : Callable, parameters : Array).

GD-Sync also makes it possible to check which client performed a remote function call. Calling GDSync.get_sender_id will return the client ID of the last remote caller. This feature is disabled by default to reduce data usage but can be enabled in the configuration menu when needed.

Creating a lobby does not automatically place you inside it. After calling this function, you have 5 seconds to join your newly created lobby. If no one joins during this time, the lobby will be automatically deleted.

Below is a simple example showing how to call a function on other clients in the same lobby. For the remote call to work, the Node that the script is attached to must exist on every client in the exact same location within the scene tree and have the same name. This ensures GD-Sync can correctly match the Node across all clients and deliver the function call to the intended target.

# This Node with this script must exist on all clients in order to work
func _ready() -> void:
	# Expose the function so it may be called remotely
	GDSync.expose_func(test)
	
	# Perform the remote call
	GDSync.call_func(test, [1])

func test(test_var : int) -> void:
	print("I got called remotely!")
	print(test_var)
  

Variable Syncing

GD-Sync allows you to synchronize Node variables across all clients in the same lobby. For security, all variable synchronization requests are blocked by default. To allow synchronization, you must explicitly mark which variables or Nodes can be accessed remotely. This can be done by using GDSync.expose_var(variable_name : String) to expose a single variable, or GDSync.expose_node(node : Node) to expose every variable on the given Node.

Once a variable is exposed, it can be synchronized to all other clients by calling GDSync.sync_var(node : Node, variable_name). This sends the current value of the variable from your client to every other client in the lobby, updating their copy of the same Node.

While variables can be synchronized manually, it is highly recommended to use the custom PropertySynchronizer for this task. The PropertySynchronizer automates the process and helps ensure consistency across all clients without requiring repeated manual calls.

We do not recommend calling sync_var every frame (in _process or _physics_process). If you want to synchronize a variable every frame use the PropertySynchronizer instead as it is optimized for lower transfer usage.

Below is a simple example showing how to synchronize a variable across clients. In this example, the test_var variable is modified locally, and the updated value is then synchronized so that all other clients in the lobby receive the change.

# This Node with this script must exist on all clients in order to work

var test_var : int = 0

func _ready() -> void:
	# Expose the function so it may be synced remotely
	GDSync.expose_var(self, "test_var")
	
	# Edit the variable
	test_var = 10
	
	# Sync the edit to other clients
	GDSync.sync_var(self, "test_var")
	# test_var is now 10 on all clients!
  

Node Instantiation

GD-Sync provides a straightforward way to spawn Nodes across all clients in the same lobby. This can be done using either the NodeInstantiator class or the GDSync.multiplayer_instantiate() function. Both approaches ensure that the Node is instantiated on every client so it can be synchronized correctly.

The NodeInstantiator is ideal for scenarios where a Node is spawned frequently, such as bullets fired from a weapon. Add the NodeInstantiator to your scene, set which scene you want to spawn in, and then use instantiate_node() to spawn in Nodes. This method returns the instantiated Node locally while also creating it on all other clients. When a Node is successfully created, the node_instantiated(node : Node) signal is emitted.

The second option is to use the global function GDSync.multiplayer_instantiate(scene : PackedScene, parent : Node, sync_starting_changes : bool, excluded_properties : PackedStringArray, replicate_on_join : bool = true). This method works in the same way as the NodeInstantiator and offers the same synchronization behavior.

If sync_starting_changes is enabled, any modifications made to the root Node of the instantiated scene within the same frame will automatically be synchronized when the scene is spawned on other clients. Both methods also support scene replication through replicate_on_join, allowing newly instantiated scenes to be sent to clients who join the lobby after the object has been created.

Scripts on Nodes created using either of these methods can optionally override _multiplayer_ready(), which is called after _ready() and after any starting changes have been applied.

Both the NodeInstantiator and GDSync.multiplayer_instantiate() automatically ensure that the NodePath is identical on all clients so GD-Sync can properly synchronize it.

When instantiating Nodes with these methods, _multiplayer_ready() is called after _ready() and after all starting changes are applied.

The order is: _ready() → apply starting changes → _multiplayer_ready().

Node Ownership

GD-Sync provides a built-in way to assign ownership to Nodes. Ownership does not directly change how a Node behaves, but it is a powerful tool for controlling logic in your scripts. Ownership is global across all clients, meaning that when you assign a Node’s owner, it is set for every client in the lobby.

A common use case is in player scripts. If you use the same player scene for all players, you typically only want movement inputs to affect your own character. When spawning a player, you can assign ownership to the correct client so that each player only controls their own instance.

To assign ownership, use GDSync.set_gdsync_owner(node : Node, owner : int), where owner is the client ID of any client in the lobby. Ownership can be removed with GDSync.clear_gdsync_owner(node : Node). You can check if you are the owner of a Node by calling GDSync.is_gdsync_owner(node : Node), and you can get the current owner’s client ID using GDSync.get_gdsync_owner(node : Node). A return value of -1 means the Node has no owner.

Ownership information is stored locally on each client and is not instantly synchronized. This means race conditions can occur if clients temporarily have different ownership data. To avoid this, critical ownership checks should be handled on the host. For example, grabbable object scripts can route all ownership verification through the host to ensure consistency.

You can listen for ownership changes on a specific Node by using GDSync.connect_gdsync_owner_changed(node : Node, callable : Callable). The provided callable will be executed whenever the Node’s ownership changes. To stop receiving these notifications, use GDSync.disconnect_gdsync_owner_changed(node : Node, callable : Callable).

func _ready() -> void:
	# Connect so we can listen for ownership changes
	GDSync.connect_gdsync_owner_changed(self, owner_changed)
	
	# Take ownership
	GDSync.set_gdsync_owner(self, GDSync.get_client_id())

func owner_changed(new_owner : int) -> void:
	print("Ownership changed ", new_owner)