Quick answer: RPC calls in Godot silently fail when the function is missing the @rpc annotation, the node path doesn’t match on all peers, or the multiplayer authority is misconfigured. Add @rpc("any_peer", "reliable") to your function, ensure identical scene trees across peers, and verify the ENet connection is established before calling rpc().

You’ve set up ENetMultiplayerPeer, connected a client to the server, and called my_function.rpc()—but nothing happens on the other side. No error in the console, no crash, just silence. RPC failures in Godot’s multiplayer system are notoriously quiet, which makes them frustrating to debug. The good news is that the problem almost always falls into one of a few categories, and each has a straightforward fix.

The @rpc Annotation

In Godot 4, the @rpc annotation replaced the old remote, puppet, and master keywords. If your function doesn’t have an @rpc annotation, calling .rpc() on it will silently do nothing. There is no runtime error or warning—the call is simply discarded.

The annotation syntax is:

@rpc("mode", "transfer_mode", "call_local_setting", channel)

# Examples:
@rpc("any_peer", "reliable", "call_local")
func take_damage(amount: int):
    health -= amount

@rpc("authority", "unreliable")
func update_position(pos: Vector3):
    global_position = pos

The parameters control who can call the function and how:

Node Path Mismatches

Godot’s RPC system identifies nodes by their path in the scene tree. When you call rpc() on a node, Godot sends the node’s path to the remote peer, which looks up the same path in its own scene tree and executes the function there. If the node doesn’t exist at that path on the remote peer, the RPC is silently dropped.

This is the most common issue when spawning players or objects dynamically. If the server creates a player node at /root/Game/Players/Player_2 but the client hasn’t received the spawn message yet, any RPC sent to that node will fail on the client.

The fix is to use Godot’s MultiplayerSpawner node, which synchronizes spawning across all peers. Add a MultiplayerSpawner as a child of the node that contains your spawnable objects, configure the spawn path and allowed scenes, and let the spawner handle replication:

# Server-side spawning with MultiplayerSpawner
func spawn_player(peer_id: int):
    var player = player_scene.instantiate()
    player.name = str(peer_id)
    # Set the authority to the owning client
    player.set_multiplayer_authority(peer_id)
    $Players.add_child(player)
    # MultiplayerSpawner automatically replicates to clients

If you’re not using MultiplayerSpawner, you need to ensure that the node exists on all peers before sending RPCs. A common pattern is to have the server send an RPC to all clients telling them to create the node, wait for acknowledgment, and only then begin sending RPCs to that node.

Multiplayer Authority Configuration

Every node in a multiplayer scene has a multiplayer authority—the peer ID that “owns” the node. By default, the server (peer ID 1) is the authority for all nodes. If your RPC is annotated with "authority" mode but a client is trying to call it, the call will fail because the client is not the authority.

Set the authority correctly when spawning player-controlled nodes:

func spawn_player(peer_id: int):
    var player = player_scene.instantiate()
    player.name = str(peer_id)
    player.set_multiplayer_authority(peer_id)  # The client owns their player
    $Players.add_child(player)

# In the player script:
func _ready():
    # Only process input if we are the authority
    set_process_input(is_multiplayer_authority())

@rpc("authority", "reliable", "call_local")
func fire_weapon():
    # Only the owning client can call this
    var bullet = bullet_scene.instantiate()
    get_parent().add_child(bullet)

A common mistake is setting authority after adding the node to the scene tree. In some cases, _ready() fires before set_multiplayer_authority() is called, causing early RPCs to use the wrong authority. Always set the authority before calling add_child().

Verifying the Connection

RPCs require an active ENet connection. If you call rpc() before the connection is fully established, the call is lost. Always wait for the appropriate signal before sending RPCs:

var peer = ENetMultiplayerPeer.new()

func host_game():
    peer.create_server(7777)
    multiplayer.multiplayer_peer = peer
    multiplayer.peer_connected.connect(_on_peer_connected)

func join_game(address: String):
    peer.create_client(address, 7777)
    multiplayer.multiplayer_peer = peer
    multiplayer.connected_to_server.connect(_on_connected)

func _on_connected():
    print("Connected! Safe to send RPCs now.")
    # Now RPCs will work
    request_game_state.rpc_id(1)

func _on_peer_connected(id: int):
    print("Peer %d connected" % id)
    # Safe to send RPCs to this specific peer
    send_welcome.rpc_id(id, get_game_state())

Also check the return value of create_server() and create_client(). They return an Error enum—if the port is already in use or the address is invalid, the function returns an error code but doesn’t crash. Always verify: if peer.create_server(7777) != OK: push_error("Failed to create server").

Debugging Tips

When an RPC isn’t working, add print statements at both the call site and the function body to determine which side is failing. If the call site prints but the function body doesn’t, the RPC is being dropped in transit. If neither prints, the calling code isn’t being reached at all.

Check the multiplayer peer state with multiplayer.multiplayer_peer.get_connection_status(). It should return CONNECTION_CONNECTED. If it returns CONNECTION_DISCONNECTED or CONNECTION_CONNECTING, the connection isn’t ready for RPCs.

For complex multiplayer setups, use Godot’s built-in MultiplayerSynchronizer alongside RPCs. The synchronizer handles continuous state replication (positions, rotations, animations) while RPCs handle one-off events (damage, item pickups, chat messages). Mixing the two correctly avoids many of the node path and authority issues that cause RPC failures.

Godot RPCs fail silently by design—check annotations, paths, and authority before assuming the network is broken.