Custom MultiplayerSynchronizer

Godot Version

4.2.1 (4.1+)

Questions

I’m trying to make custom MultiplayerSynchronizer and I have a few questions

  • 1 Am I doing it right?
    Here is my players spawn function:

    func add_player(peer_id: int) -> Node3D:
    	var new_player := PLAYER_SCENE.instantiate() as CharacterBody3D
    	if !new_player:	printerr('Player scene error');	return
    	new_player.peer_id = peer_id
    	new_player.name = str(peer_id)
    	new_player.set_multiplayer_authority(peer_id)
    	new_player.get_node('multiplayer_synchronizer').set_multiplayer_authority(peer_id)
    	players_node_3d.add_child(new_player)
    
    	return new_player
    

    And this is the synchronization script:

    extends MultiplayerSynchronizer
    
    @onready var player: CharacterBody3D = $'..'
    var player_head: Node3D
    
    
    var is_ready := false
    var _lerped_position := Vector3.ZERO
    var _lerped_rotation := Vector3.ZERO
    
    @rpc('any_peer', 'call_remote', 'unreliable')
    func rpc_sync(pos: Vector3, rotation: Vector3) -> void:
    	if !is_ready:		return
    	_lerped_position = lerp(_lerped_position, pos, 0.85)
    	player.position = _lerped_position
    	
    	_lerped_rotation = lerp(_lerped_rotation, rotation, 0.9)
    	player_head.rotation.x = _lerped_rotation.x
    	player.rotation.y = _lerped_rotation.y
    
    
    
    
    func _enter_tree() -> void:
    	set_process(false)
    	visibility_update_mode = MultiplayerSynchronizer.VISIBILITY_PROCESS_NONE
    
    
    func start() -> void:
    	_lerped_position = player.position
    	_lerped_rotation = player.rotation
    	visibility_update_mode = MultiplayerSynchronizer.VISIBILITY_PROCESS_IDLE
    	if 1:
    		player_head = player.head
    		if !player_head:	return
    	is_ready = true
    	if player.is_authority:
    		set_process(true)
    
    
    func _exit_tree() -> void:
    	set_process(false)
    	visibility_update_mode = MultiplayerSynchronizer.VISIBILITY_PROCESS_NONE
    
    
    
    
    func _process(_delta: float) -> void:
    	rpc_sync.rpc(player.position, Vector3(player_head.rotation.x, player.rotation.y, 0.0))
    
  • 2 Do I even need to write code above in synchronizer or just in some 3d node? And do I need give it authority?

Everything works great but I have a doubts about synchronization :confused:

1 Am I doing it right?

As long as it does what you want it to do :slight_smile: I’m not sure about the lerped approach though. Anytime you receive position and rotation ( which should be the ground truth ), you don’t apply it but lerp towards it once. This might make motion smoother, but if the network becomes uneven and updates start arriving at random intervals, it will start feeling choppy.

Instead, you could just store the received position and rotation, and move / lerp towards them in _process continuously. That way, when you receive an update the player will pull smoothly towards it and will reach the actual position so you end up seeing the actual player state.


2 Do I even need to write code above in synchronizer or just in some 3d node?

I was going to mention the exact same thing while reading your code! Since you’re manipulating the player node’s transform in this script, you can make script extend any kind of Node. It needs to be a Node for RPC’s to work, but that’s the only requirement.


And do I need give it authority?

Currently the script extends MultiplayerSynchronizer, which uses authority to know which peer owns the node. Basically you can say that e.g. Player 2 owns this node, so whatever Player 2 says is its position, that will by synchronized to other peers. I see that you already set authority this way, and yes, this is needed.

However, you don’t check authority when syncing positions, so not sure if that works currently with multiple players. Anyway, if you’re confused, I recommend reading up on ownership in the docs a bit. But tl;dr, authority is used to denote which node belongs to which peer, which you can use to decide which peer’s state to synchronize, or who has the right to do what with each node ( e.g. player A won’t sell player B’s buildings ).


Not strictly related to the question, but I highly recommend testing your game under different network conditions if you plane to play it online. For LAN, network is usually way more stable with way less latency, so you can get away with more stuff.

For example, the above should work well on LAN, but for that you could possibly just simply use MultiplayerSynchronizer without any extra code. For online play, if you go with every peer owning their nodes, this should be OK, but you open yourself up to cheating. For server-authoritative architecture, you’d need some rollback code.


I’ve built an addon for online multiplayer stuff called netfox, which has some custom synchronizers, specifically StateSynchronizer and RollbackSynchronizer that you can look at if you want, for reference.

1 Like

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