Joining Player Position Duplicating Last Player

Godot Version

Godot 4.2.1 stable

Question

I’ve got an interesting bug with my game where a newly joined player’s position is equal to the position of the previous players current position.

Is there something I need to be doing with the multiplayer synchronizer that I’m not already doing? The root path is set to an empty node called “Players”. :person_shrugging: One would think it by default that would be the position of the new player?

1 Like

If you are using MultiplayerSpawners they will add the new node to the parents origin. So it kind of sounds like the new player is being added under the last joined player?

If you don’t use an MP… There could be some funny business if you dynamically modify a replication config. Do you do such a thing?

If you are using MultiplayerSynchronizers only, I suspect there is a bug in your code. Maybe in setting the position of the new player. ( like local vs global position?)

Hi Penny,
No I can definitely comfirm after viewing remote that they are not creating a hierarchy chain. Every added player is the sibling of the previous player.

No, I’m not setting the position of the player programmatically at all. I’m using a MultiplayerSynchronizer to position the players.

How are you adding the player to the scene on the client? As a multiplayer synchronizer will not synchronized instantaneously, and will happen potentially frames after a node is spawned.

I’ve got a function called add_player with a multiplayer.peer_connected.connect(add_player) signal attached.

extends CanvasLayer

@onready var main_menu = $"."
@onready var menu_panel = $Interface/MenuPanel
@onready var join_panel = $Interface/JoinPanel
@onready var message_panel = $Interface/MessagePanel
@onready var message_panel_text = $Interface/MessagePanel/MarginContainer/VBoxContainer/Label
@onready var message_panel_button = $Interface/MessagePanel/MarginContainer/VBoxContainer/ReturnButton
@onready var menu_camera = $MenuCamera
@onready var user_interface = $"../UI"
@onready var address_entry = $Interface/JoinPanel/MarginContainer/VBoxContainer/AddressEntry
@onready var players = $"../Players"

const PLAYER = preload("res://scenes/player/player.tscn")
const PORT = 53301
var enet_peer = ENetMultiplayerPeer.new()


func add_player(peer_id):
	var player = PLAYER.instantiate()
	player.name = str(peer_id)
	players.add_child(player)
	print("Player has joined the game.")


func remove_player(peer_id):
	var player = players.get_node_or_null(str(peer_id))
	if player:
		player.queue_free()
	print("Player has left the game.")


func upnp_setup():
	var upnp = UPNP.new()
	var discover_result = upnp.discover()
	assert(discover_result == UPNP.UPNP_RESULT_SUCCESS, "ERROR: Failed to discover UPNP. Error %s" % discover_result)
	assert(upnp.get_gateway() and upnp.get_gateway().is_valid_gateway(), "UPNP Invalid Gateway!")
	var map_result = upnp.add_port_mapping(PORT)
	assert(map_result == UPNP.UPNP_RESULT_SUCCESS, "ERROR: UPNP port mapping failed. Error %s" % map_result)
	print("Success! Server is running. Join address: %s" % upnp.query_external_address())


func _on_host_button_pressed():
	main_menu.hide()
	user_interface.show()
	enet_peer.create_server(PORT)
	multiplayer.multiplayer_peer = enet_peer
	multiplayer.peer_connected.connect(add_player)
	multiplayer.peer_disconnected.connect(remove_player)
	add_player(multiplayer.get_unique_id())
	upnp_setup()


func _on_connect_button_pressed():
	# show / hide interface items
	main_menu.hide()
	user_interface.show()
	
	# connect failed connection function
	if not multiplayer.connection_failed.is_connected(connection_failed):
		multiplayer.connection_failed.connect(connection_failed)
	
	# try to connect to host
	enet_peer.create_client(address_entry.text, PORT)
	multiplayer.multiplayer_peer = enet_peer


func connection_failed():
	main_menu.show()
	user_interface.hide()
	message_panel.show()
	join_panel.hide()
	message_panel_text.text = "ERROR: Failed to connect to game"

Found the issue and reported what I believe to be a bug to the Godot github repo. MultiplayerSynchronizer and players sharing collider layer causes undesirable spawn location · Issue #91005 · godotengine/godot · GitHub

The issue was that the players were sharing the same collider layer and getting stuck inside eachother on frame 1 of the client joining even if the host isn’t at the start location. This is due to the delay in the multiplayerSynchronizer not updating the player position before the game has loaded the player.

This can be resolved with a MultiplayerSpawner and using the on spawn MultiplayerSynchronizer replication config setting.

Trust me I don’t have this issue, and since you don’t set the position they will spawn at the origin of your players node, and on top of each other. That isn’t a bug that is how add_child works.

I can confirm I was already using the “on spawn” setting.

Screenshot 2024-04-22 225625

1 Like

Now just set a different position before adding the child to the scene.

Or, and I haven’t tied this, multiplayer spawner can spawn nested nodes under the branch it watches. You could make a simple spawner locations by having marker nodes with different positions and the host chooses which spawn node to add the new player. They will be set to that nodes origin. As long as two players aren’t set to the same node you don’t have to worry about spawning on top of each other.

I can confirm I’ve tried that too. Before adding the child, after, during the enter tree, after the enter tree. Nothing works.

It is as if the “spawn” toggle doesn’t actually work.

Idk, I’m not sure… I don’t have this issue with my project. I think I have run into this issue but it’s typically solved by assigning a different position.

By chance, are you using a rigid body as your player? If so you will need to sync the physics body state instead of node position. I typically set position on spawn with never. then have another synchronizern handle the physics state with always.

I have posted my physics synchronizer code here. It is a work in progress, but should be bare bone enough for anyone to tune to there needs.

Another question, what authority do you give to the client? in other words, are you assigning the player node or just an input synchronizer? Is this a server-client architecture or peer-to-peer?

If you give the player node authority to the client this could explain why setting the position from the host isn’t working. Because it would be the responsibility of the client to set its own position.