Issue with MultiplayerSpawner:

Godot Version

4.5.1

Question

I am trying to spawn a bullet that is then spawned and synchronized between all players.

This works fine for the host, and clients can see the host’s bullets just fine. However, an error fires every time a bullet is fired on both host and clients, and bullets fired by clients are warped position 0,0,0 and then move through the ground extremely slowly.

Here is the code on the gun responsible for firing the bullet.

extends Node3D

var speed = 200

@onready var spawner: MultiplayerSpawner = $MultiplayerSpawner
@onready var spawn_location = get_tree().root.get_node("Main/GameWorld")

@export var projectile : PackedScene

func _ready() -> void:
	spawner.spawn_path = spawn_location.get_path()

func fire():
	var bullet = projectile.instantiate()	
	var camera = get_parent()
	bullet.global_transform = camera.global_transform
	bullet.speed = camera.global_transform.basis.z * speed
	spawn_location.add_child(bullet, true)

The error it returns is

E 0:01:43:988   test_gun.gd:19 @ fire(): Condition "tobj.spawner != ObjectID()" is true. Returning: ERR_ALREADY_IN_USE
  <C++ Source>  modules/multiplayer/scene_replication_interface.cpp:164 @ on_spawn()
  <Stack Trace> test_gun.gd:19 @ fire()
                player.gd:60 @ _physics_process()

I… don’t really know what that means. Can anyone help me decipher this or tell me what I’m doing wrong?

1 Like
Godot Version 4.6

Hi! Have you been able to sort this issue out? I am now facing it too. The only similarity I manage to see between your setup and mine is that we both, in some way, are affecting a spawn_path property:

extends Player


func _enter_tree() -> void:
	# [...]
	var character_spawner: MultiplayerSpawner = (load("uid://1krxvcsbbbq8") as PackedScene).instantiate()
	character_spawner.set_multiplayer_authority(get_multiplayer_authority())
	world.add_child(character_spawner) # [world] is a global


func _ready() -> void:
	if is_multiplayer_authority():
		add_character()


func add_character() -> void:
	var new_character = Humanoid3D.instantiate() # [Humanoid3D] is a custom class
	new_character.name = name
	world.add_child(new_character, true)

In my case, the exception is only fired on the host and I haven’t really checked if it causes any visible issues. This is how the error is described in the engine source code:

// Spawn state needs to be callected after "ready", but the spawn order follows "enter_tree".

Between the fact that “calleted” seems like a typo and that source comments tend to be ambiguous, I cannot make sense out of this without C++ knowledge.

Okay, after a little of help, I have a better intuition of this error’s nature. Correct me if I’m wrong.

ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE);

This line of code acts as a guard to prevent spawning nodes from already being assigned to a spawner, setting just one spawner as the one in control. This “control” concept is important, remember spawned nodes disappear when their spawner disappears or disconnects from the multiplayer session.

Regarding your specific case, you have, effectively, multiple spawners pointing to a same spawn path: each gun has its own spawner pointing to spawn_location, "Main/Gameworld”. Presuming they all belong to different authorities (the player), that still doesn’t take the fact that all spawners are visible in each client’s SceneTree.

No matter who sends the spawn data, the scenes are still instantiated locally the same way any other would and thus, the engine recognizes that a spawnable scene is being spawned by another spawner, so it shouldn’t try to spawn it too. It’s a guard to prevent a spawning loop, clever :sweat_smile: .

This error appearing is probably a sign that the engine is saving you from making a great mistake but still happens near the end of an important callback, so we should not discard that your warped position and velocity issue might be caused by it. The best solution I envision is creating, from a single spawner owned by the host, containers for each peer and their spawnable scenes. So:

var spawn_location = get_tree().root.get_node("Main/GameWorld/Player[X]")