How Are You Supposed to Use MultiplayerSynchronizer Visibility?

Godot Version

4.6.1.stable

Question

I’m trying to set up a system for a P2P multiplayer game using Steam’s lobbies system (using GodotSteam addon). It seems like server relay isn’t supported with SteamMultiplayerPeer, so I have been having to write some workarounds to send information from the clients to the host and then back to the other clients.

This is not a competitive game and will primarily be played in groups of people who already know each other (think friendslop), so I am simplifying my work by making each player the authority for their own movement (position, rotation, velocity, etc.). I thought the best approach for this would be to just use a MultiplayerSynchronizer to sync these properties from the client to the host, and another MultiplayerSynchronizer to sync them back from the host to the other clients (again, can’t use relay, so one synchronizer doesn’t work). However, just doing that means that the values the host has will also be sent to the player who should be the authority for their own movement, meaning that they effectively can’t move.

I figured I should use visibility to make it so the values are synced from the server to all the clients except for the one that “owns” those values, but the visibility methods aren’t very well documented and so I’m having a difficult time getting it to work. Am I supposed to call set_visibility_for() just server-side? Client-side too? Right now I have this (in the level code, which spawns the players):

## Create a player character for a peer. Register the character as belonging
## to that peer, and spawn it in. The MultiplayerSpawner will ensure the
## character is spawned for all peers as well.
func _create_player_chara(peer: int) -> void:
	var player_chara: Node3D = player_scene.instantiate()
	if multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
		var steam_id := (multiplayer.multiplayer_peer as SteamMultiplayerPeer).get_steam_id_for_peer_id(peer)
		var steam_username := Steam.getFriendPersonaName(steam_id)
		player_chara.get_node("Graphics/Nametag").text = steam_username
	player_chara.peer_id = peer
	player_chara.name = PLAYER_CHARA_NAME_PREFIX + str(peer)
	
	# Filter to disable visibility for only the client who has authority over this
	# player character.
	player_chara.get_node("ServerPosRotVelSynchronizer").add_visibility_filter(func(id: int) -> bool:
		return id != peer
		)
	
	add_child(player_chara, true)

This code is called only from the host, and the spawns are synced with a MultiplayerSpawner. The ServerPosRotVelSynchronizer is the MultiplayerSynchronizer that is supposed to send the info from the server to all the clients except the one who owns that player character, so that’s what the filter is supposed to do. The synchronizer has public_visibility set to false, and visibility_update_mode is Idle. However, when checking visibility on the player character using get_visibility_for(), it returns false.

What am I missing lol…? I would expect visibility to be synced automatically, but do I need to add this filter client-side, too, or something?

authority controls the direction of flow for data. The visibility only is a filter. All nodes default to host authority. So you need to set authority manually on host and client respectively.

And spawners need to be authed to client to hide/unspawn nodes. If that is what you want.

It will make your life easier if you just do a central authority and just buffer and ignore data for the controlling client unless its desynced , if that makes sense. That way you dont have to bother with authority shenanigans and timing for all the clients state. Just need to give authority of input syncher to the client.

I understand this, the authority is set correctly. When the player’s peer_id is set, it sets the authority of the ClientSynchronizer to that peer_id value. The ServerSynchronizer is left as-is since the default authority is the server.

This is what I’ve ended up doing, but I’d still like to better understand how visibility works. Based on how I’ve heard it spoken about, it seems like it’s intended use is for saving bandwidth by only syncing stuff when necessary e.g. no need to sync another player’s rotation when they are out of view, am I wrong? I guess I should simplify my question: What function calls do I use and where do I use them (e.g. server, client, both) in order to make a MultiplayerSynchronizer only replicate properties to certain peers, or under certain conditions? Or am I misunderstanding what visibility does?

Visibility is an all-off/all-on, and per client, aspect for a synchronizer, when paired with a spawner it will despawn the node on the filtered client. You can fiddle with the replication config dynamically on the authority side to control individual property syncing but that is not related to visibility.

The main reason for visibility is to reduce client side hacks to peak at your opponent state. Think of a card game where you hide a players hand, only the client can see thier hand. It could also allow you to place clients in separate worlds/rooms, but that is game specific and a design specific choice. Bandwidth considerations not the primary reason.

As far as what you need to do i would refer to the docs. Or look for resources. Im not helpful in that way anymore. Personally i would recommend to just get your hands dirty and get use to messing with the api yourself.