MultiplayerSpawner Peer Sync issues in 4.5.1

Godot Version

4.5.1

Question

Hi everyone, as i’m developing a multiplayer game in godot I came across the multiplayer spawner node for peer sync.

As for now i’m already using 2 other spawner for my players and enemies who works fine.
However the 3rd one is giving me some troubles and i’m confused as why it doesn’t work properly and i might use some help for it D:

Basicly i have my level scene with my spawner and spawn path all setup.

I have on my players an Interact System that use rpc to request interact to the host of the level. Then the host fire the interact fonction on an item, for example a PickupItem scene with my interact component as a child as following :

func interact(sender_id: int, player: Node) -> void:
	var player_inventory_manager: InventoryManager = player.get_node("InventoryManager") as InventoryManager
	if not player_inventory_manager:
		push_warning("Player has no inventory_manager")
		return
	
	var is_picked_up: bool = player_inventory_manager.pickup_item(item_data)
	if is_picked_up:
		if sender_id != multiplayer.get_unique_id():
			rpc_id(sender_id, "confirm_pickup", sender_id)
		rpc("update_ui")
		
		var item_spawner: MultiplayerSpawner = Game.level.item_spawner
		
		if not item_spawner:
			push_warning("[ITEM DROP] ItemSpawner not found")
			return
		
		item_spawner.despawn_item(self)

Note : I did register the level in a singleton at tree_enter to access it from my host more easily.

When a client pickup an item, he picks it up, but the item only despawn on the host side. Same for spawning and item.

At first i thought this was an authority problem. But i also have a shop that can spawn item within my level too that also doesn’t work as intended. (simply calling spawn_item with an item data)

The spawner code is the following (nothing fancy) :

func spawn_item(item_data: Item, spawn_transform: Transform3D) -> void:
	if not multiplayer.is_server():
		return
	
	# --- Item instance setup ---
	var instance: ItemPickup = pickup_scene.instantiate()
	instance.item_data = item_data
	instance.global_transform = spawn_transform
	
	var spawn_container: Node3D = get_node_or_null(spawn_path)
	if not spawn_container:
		push_warning("[SPAWN ITEM] spawn_path not found")
		return
	
	spawn_container.add_child(instance, true)
func despawn_item(item_instance: ItemPickup) -> void:
	if not multiplayer.is_server():
		return
	
	var spawn_container: Node3D = get_node_or_null(spawn_path)
	if not spawn_container:
		push_warning("[DESPAWN ITEM] spawn_path not found")
		return
		
	if not spawn_container.is_ancestor_of(item_instance):
		push_warning("[DESPAWN ITEM] Trying to despawn out of spawn path : %s" % item_instance.name)
		return
	
	item_instance.queue_free()

When I spawn an item with my shop within my level i got errors like theses:

E 0:00:12:723 on_spawn_receive: Condition “parent->has_node(name)” is true. Returning: ERR_INVALID_DATA
<C++ Source> modules/multiplayer/scene_replication_interface.cpp:600 @ on_spawn_receive()

For sure it’s a sync problem between my peers, but i thought the multiplayer spawner would sync them if i have the authority.

I’m kinda clueless about this one for days now. Some tips / answers / guidance would be appreciated ! :slight_smile:

MultiplayerSpawner nodes are pretty basic, they will create the scene on connected clients, but any extra data such as these two lines will not be replicated, only the scene instantiation. It’s weird.

However the MultiplayerSpawner does have a way around this by assigning a spawn_function.

func _spawner_item_function(data: Array) -> Node:
	# we could assume the ItemPickup is to be spawned
	# but other data must be passed in

	var instance: ItemPickup = pickup_scene.instantiate()
	instance.item_data = load(data[0])
	instance.global_transform = data[1]

	# do not add the instance as a child, return it
	return instance


func _ready() -> void:
	item_spawner.spawn_function = _spawner_item_function


func spawn_item(item_data: Item, spawn_transform: Transform3D) -> void:
	# may need to convert item_data into a base type, such as string. Anything normally referenced may have issues over the network.
	item_spawner.spawn([item_data.resource_path, spawn_transform])

You still delete the object with queue_free and the MultiplayerSpawner will replicate that.

1 Like

Thanks for your answer !

I tried the custom spawn_function and it works, i just have to switch to a better way to handle my data. I saw a comment regarding the facts that attributes of the scene are not carried over into the client’s newly replicated node on the doc, but i was too focused on the authority !

So basicly if you use a custom spawn, you can “set” data to your instanciated node.
What’s the main difference between using setup a custom instance in a spawn function and setting it up before add_child instanciation ? Do we have more information on how it works internaly ?

This .spawn_function works because every peer must have the same spawn function, then when .spawn is called every peer calls the .spawn_function with the same data. How you use that data is up to the function. It is very similar to most @rpc calls, however the MultiplayerSpawner keeps information about what has been spawned and what has been deleted so new joining peers can be “caught up” sending any actively spawned objects via the same .spawn with data.

Not sure I understand this, I guessing you mean what the difference between using .spawn and adding children is internally? If so the big difference is that .spawn uses the assigned .spawn_function which could do anything because it’s a function. Where adding children is equivalent to using this as a spawn function, only retrieving the added child’s scene_file_path as data

func _basic_spawn_function(data: String) -> Node:
    var scene: PackedScene = load(data)
    return scene.instantiate()
1 Like