Quick answer: MultiplayerSynchronizer broadcasts to every peer by default (public_visibility = true). For area-of-interest or team-only state, set public_visibility = false and call set_visibility_for(peer_id, visible) to scope updates.
You build a multiplayer game with 32 players per match. Every player’s transform is a MultiplayerSynchronizer. Bandwidth usage is terrible, and cheaters on the other side of the map know exactly where your stealth character is hiding. The fix is a visibility filter that only sends state to players who can actually see the object.
Visibility Modes
Each synchronizer has a boolean public_visibility:
true: send to every connected peer. Simple and the default.false: send only to peers explicitly marked visible withset_visibility_for.
AOI Example
extends Node
@onready var sync = $MultiplayerSynchronizer
@onready var aoi_area = $Area3D
func _ready():
sync.public_visibility = false
aoi_area.body_entered.connect(_on_peer_entered)
aoi_area.body_exited.connect(_on_peer_exited)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
func _on_peer_entered(body):
if body.is_in_group("player"):
sync.set_visibility_for(body.get_multiplayer_authority(), true)
func _on_peer_exited(body):
if body.is_in_group("player"):
sync.set_visibility_for(body.get_multiplayer_authority(), false)
func _on_peer_disconnected(peer_id):
sync.set_visibility_for(peer_id, false)
The AOI Area3D is a large sphere around the object. When a player’s body enters, they become visible for this synchronizer. When they leave, they stop receiving updates. When they disconnect, cleanup removes them from the visibility table.
MultiplayerSpawner Visibility
Spawners also have a spawn_function and visibility_changed signal. Use visibility_changed to hide the object’s visual representation on peers who can’t see it, not just stop replicating state. A client with an invisible opponent still sees a stale ghost unless you handle the signal.
Performance
Per-peer visibility has a cost: every tick checks the per-peer table. For 100 synchronizers and 32 peers, that’s 3200 checks per tick. This is still cheaper than sending 3200 pointless packets, but if you have extreme object counts, use spatial partitioning (like grid cells) instead of per-object AOI.
Verifying
Enable multiplayer.debug logging and watch per-peer bandwidth. Before the fix, every client’s download matches every other client. After, bandwidth scales with nearby activity. Stealth-focused game? Test that invisible players don’t appear in a network packet sniffer.
“MultiplayerSynchronizer defaults to the simplest behavior: send to everyone. For anything larger than a 4-player lobby, you need a visibility filter.”
Related Issues
For spawner replication, see Godot MultiplayerSpawner not syncing. For RPC routing, see Godot multiplayer RPC not reaching peers.
Never ship a multiplayer game without per-peer visibility on non-public data. Bandwidth and cheat risk both explode otherwise.