I'm trying to understand the logic and use cases of RPCs in relation to server-authoritative gameplay on a dedicated server

Godot Version

4.3

Questions

I’m trying to understand how RPCs and their arguments work. (i.e. how they transfer information, to who, and why)

I read the docs front to back, however, I’m trying to understand how it applies to server-authoritative gameplay where clients only send inputs to the dedicated server. As the docs seem tailored for peer-to-peer models.

Here are my specific questions:

  1. What does an argument-less RPC do exactly? Base functions, default values/arguments, etc.

  2. With the “mode” argument, what’s the difference between “authority” and “any_peer” in relation to dedicated server authority? Is it better to check for authority via if not multiplayer.is_server(): return?

  3. With the “sync” argument, what’s the difference between the “call_remote” and “call_local” in relation to dedicated server authority? Will “call_remote” still send data to all clients? Will “call_local” allow for cheating?

The last 2 arguments, “transfer_mode” and “transfer_channel” make perfect sense to me and I can easily create tons of use cases.

1 Like

The docs state the default value for @rpc and provide a example

@rpc("authority", "call_remote", "unreliable", 0) # Equivalent to @rpc
func fn_default(): pass

When “authority” (the default) only the authority can execute the rpc to all peers. When “any_peer” any peer can execute the rpc to all peers.

If the authority of the node is kept at it’s default state, then the server will have authority and only the server can execute, similar to your example, but produces an unauthorized error.

In addition to executing the function on other clients, call_local executes on the calling peer. For example an animation that is played for the client and all their peers, like firing a weapon should play the animation for the player firing and everyone else. But third person animations should only play for other clients, so they will not use call_local

In your case with a very strict dedicated server, you may not find much use in call_local, when there is a disconnect between the calling peer (your server) and the accepting clients.

1 Like

@gertkeno
This is amazing information!

I’ll never leave an @rpc blank ever. As I need to know what the RPC is doing and where at all times. Noted.

With the mode argument, I’ll make sure to only use authority as the server needs to broadcast and verify every client input. The least the client will do is call a pre-function that calls the server function. Noted.

I need more information regarding the sync argument. Could I get a more in-depth example? I’m taking notes on all of the use cases so I can apply it.

Also, an unrelated question. For the argument transfer_channel, how many channels can you realistically run at once? I was thinking 3 - 5 channels, but I’d like a second opinion.

call_remote only calls the function on other peers. Thus a call_local rpc is equivalent to this example, because it calls locally in addition to remotely.

func do_the_wave_input() -> void:
    animate_wave() # calling local
    animate_wave.rpc() # calling remote

@rpc("call_remote")
func animate_wave() -> void:
    $AnimationPlayer.play("wave")

I use call_local a ton in my game which expects the host to be just another player. Since call_local synchronizes the local client with the server by using the same functions.

So far I’ve only found call_remote useful for triggering animations, since they do not always need to be synced between the triggering player and others. A first person player will have different animations to trigger for their peers.

I exclusively use the default 0, it automatically uses separate channels for reliable and unreliable_ordered modes. I would profile my game before trying to optimize channel usage past that.

1 Like

@gertkeno
For clarification, using call_remote would replicate the function to all other peers except the peer who called it. Since the server is the peer who called it, it would sync to all other clients including the one who triggered the RPC. Is that logic sound?

Also, I did not know that reliable and reliable_ordered worked on separate channels. That’s great to know.

Also, do you have any other misc multiplayer programming tips from your experiences? Every piece of knowledge is extremely valuable to me.

If the server uses a call_remote rpc, then the server will not be calling the function. It will sync to all other clients.

I am a little confused by “including the one who triggered the rpc” since in this scenario the server is triggering the rpc, not other clients, and again since the server is using (or triggering) the rpc the server won’t call the function.

only on channel 0; in which different modes is largely what you would use channels for anyways.


I’ve worked on popular Roblox games, for network bandwidth and lag reasons you’ll want to trust the client with some information. If they move forward on their device, let them think they’ve moved forward and sync that to the server. Registering each input with the server means the player is waiting round-trip to see each pace and turn go through, like playing with drunk goggles, so the game is only passable at a hard-sell sub 100ms network.

Detecting hackers usually ends up detecting poor network players more than anything. It really stings to be kicked and called a cheater for having a rural connection, roll backs instead of kicks/bans go a long way for player retention. Of course the popularity of Roblox on mobile and tablet platforms so we saw significantly more 4G lte players than hackers.

1 Like

So for the use cases for the sync argument are as follows:

Use call_remote for server actions that replicate to all players.

Use call_local for non-critical local actions such as animations and the like. (So, barely ever)

The sync argument is the only one that still confuses me a lot. I’ll have to test it thoroughly to truly understand when to use each.

Thanks for your help.

I might flip that. Critical server actions should probably also be done on the server; like respawning a player or flag the server should also know about, thus call_local. A dedicated server does need to update relevant players about a explosive barrel damaging them, but doesn’t need to animate the explosion thus the fiery boom part is call_remote.

It is strange, it took me a while to get it; again I mostly end up using call_local.

1 Like

That makes a lot of sense. Because the server is the local peer. Noted.

1 Like

Here’s an extra comment for future readers trying to use RPCs with a dedicated server setup in Godot 4.3. This comment summarizes the thread.

  1. Always specify the RPC’s arguments. NEVER leave it blank. The default parameters are not to be trusted and you must know what every RPC is doing at all times.

The default arguments are:
@rpc("authority", "call_remote", "unreliable", 0)

  1. For the mode argument:

Use authority for most RPCs, with the exception of the client input functions. In which case, use any_peer

  1. For the sync argument:

For critical server actions, use call_local. As the server IS the local peer. So, most functions.

For non-critical server actions, use call_remote. As the server doesn’t need to know about certain details, like animations and the like.

  1. For the transfer_mode argument:

Use reliable for critical actions such as loading, weapon switching, shooting, etc.

Use unreliable_ordered for non-critical actions that require order. Like a chat box.

Use unreliable for client actions that proc constantly, like movement.

  1. For the transfer_channel argument:

Manage each transfer channel with care and diligence.

reliable and unreliable_ordered operate on their own separate channels adjacent to channel 0. So keep that in mind.

Immediate gameplay actions need to be on channel 0.

Secondary server functions like chat and data syncing need to be channel 1+.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.