GoDot 4 Multiplayer: Trying to change client's players names when starting game

Hello, I am taking the Godot 4 Multiplayer: Make your Own Online Game course. I have got through Section 3: Muliplayer Connection up to lecture 22: Authority.

End Goal
My end goal is when the game starts. I want my players who spawn in the game to have their names changed and display correctly for both the host and the client. For right now in my code I have it set it to display the id of the player connected.

Problem
The name change only happens for the host, not the client. When looking on the client app, it does however sync the name change correctly. The code I am specifically trying to work is in the add_player function that is called in the _ready function.

additional comments
I’m am new to using godot and multiplayer so I may not understand fully if the code is just client or server side code for the level.gd script. I can instantiate the player, set it position, set the name of the node correctly, but not change the label.text . It looks like my synchronizers are set up correctly. If I run the game I can edit the text through the inspector and it updates for the client and the host correctly. Any help will be much appreciate it, thank you.

MultiplayerSynchronizer

playerlabel export when running

level.gd

extends Node2D


@export var player_container: Node2D
@export var player: PackedScene
@export var spawn_points: Array[Node2D]

var next_spawn_point_index = 0

func _ready():
	if not multiplayer.is_server():
		return
	
	multiplayer.peer_disconnected.connect(delete_player)
	
	for id in multiplayer.get_peers():
		add_player(id)
		
	add_player(1)

	
func _exit_tree():
	
	if multiplayer.multiplayer_peer == null:
		return
	
	if not multiplayer.is_server():
		return
	
	multiplayer.peer_disconnected.disconnect(delete_player)


func add_player(id):
	var player_instance =  player.instantiate()
	player_instance.position = get_spawn_point()
	player_instance.name = str(id)
	player_instance.player_label.text = player_instance.name
	print(player_instance.player_label.text)
	player_container.add_child(player_instance)
	
	
func delete_player(id):
	if not player_container.has_node(str(id)):
		return
	
	player_container.get_node(str(id)).queue_free()


func get_spawn_point():
	var spawn_point = spawn_points[next_spawn_point_index].position
	next_spawn_point_index += 1
	
	if next_spawn_point_index >= len(spawn_points):
		next_spawn_point_index = 0
	return spawn_point

player.gd

extends CharacterBody2D


var owner_id = 1
var players_connected = 1

@export_category("Movement Speed")
@export var move_speed : float = 200

@export_category("Jumping")
@export var max_jumps: int
@export var jump_count: int
@export var jump_height: float
@export var jump_time_to_peek: float
@export var jump_time_to_descent: float

@export_category("Player Setup")
@export var animated_sprite: AnimatedSprite2D
@export var player_camera: PackedScene
@export var camera_instance: Node
@export var camera_height: float = 132
@export var player_label: Label

@onready var jump_velocity : float = ((2.0 * jump_height) / jump_time_to_peek) * -1.0
@onready var jump_gravity : float = ((-2.0 * jump_height) / (jump_time_to_peek * jump_time_to_peek)) * -1.0
@onready var fall_gravity : float = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0
@onready var init_sprite_scale = animated_sprite.scale 


func _enter_tree():
	owner_id = name.to_int()
	set_multiplayer_authority(owner_id)
	
	if owner_id != multiplayer.get_unique_id():
		return
		
	set_up_camera()


func _process(_delta):
	
	if multiplayer.multiplayer_peer == null:
		return
	
	if owner_id != multiplayer.get_unique_id():
		return
		
	update_camera_position()
	
	
func _physics_process(delta):
	# movement
	if owner_id != multiplayer.get_unique_id():
		return
	var direction : float = get_movement_direction()
	face_movement_direction(direction)
	handle_movement_state(direction)
	
	#set gravity
	apply_gravity(delta)
	
	move_and_slide()
	
	
func get_gravity() -> float:
	return jump_gravity if velocity.y < 0 else fall_gravity
  

func _on_animated_sprite_2d_animation_finished():
	animated_sprite.play("jump")


func set_up_camera():
	camera_instance = player_camera.instantiate()
	camera_instance.global_position.y = camera_height
	get_tree().current_scene.add_child.call_deferred(camera_instance)


func update_camera_position():
	camera_instance.global_position= global_position


func face_movement_direction(direction: float):
	if not is_zero_approx(direction):
		if direction < 0:
			animated_sprite.scale = Vector2(-init_sprite_scale.x, init_sprite_scale.y)
		else:
			animated_sprite.scale = init_sprite_scale


func handle_movement_state(direction: float):
	var is_falling = velocity.y > 0 and not is_on_floor()
	var is_jumping = Input.is_action_just_pressed("jump") and is_on_floor()
	var is_double_jumping = Input.is_action_just_pressed("jump") and is_falling
	var is_jump_cancelled = Input.is_action_just_released("jump") and velocity.y < 0
	var is_idle = is_on_floor() and is_zero_approx(velocity.x)
	var is_walking = is_on_floor() and not is_zero_approx(velocity.x)
	
	#Apply movement
	velocity.x = direction * move_speed
	
	#Animations
	if is_jumping:
		animated_sprite.play("jump_start")
	elif is_double_jumping:
		animated_sprite.play("double_jump_start")
	elif is_walking:
		animated_sprite.play("walk")
	elif is_falling:
		animated_sprite.play("fall")
	elif is_idle:
		animated_sprite.play("idle")

	#Jumping
	if is_jumping:
		jump_count += 1
		velocity.y = jump_velocity
	elif is_double_jumping:
		if jump_count < max_jumps:
			jump_count += 1
			velocity.y = jump_velocity
	elif is_jump_cancelled:
		velocity.y *= 0.5
	elif is_on_floor():
		jump_count = 0


func get_movement_direction() -> float:
	var direction = (
		Input.get_action_strength("move_right") 
		- Input.get_action_strength("move_left")
	)
	
	return direction


func apply_gravity(delta):
	velocity.y += get_gravity() * delta


This is a highly-detailed Ask topic; well done!

Although I haven’t done the multiplayer course yet, I actually do have one thought on this: because everything else works and it’s just the player label text that appears not to be changing, I suspect this is caused by a timing issue. Maybe things are completely different here in the multi-verse, I have no idea, but this is worth a shot, especially because I was able to reproduce this in a minimum repro singleplayer project.

Try moving player_container.add_child(player_instance) to somewhere before setting the label text. As a good practice, I would actually put it immediately after the instantiate() line. If that works, I’ll explain.

After moving the player_container.add_child(player_instance) to the top after the player.instantiate(). Now I am getting both visual and script errors from both the host and client. I think you are right about some kind of timing issue going on :frowning: I just don’t know what to do.
level.gd

extends Node2D


@export var player_container: Node2D
@export var player: PackedScene
@export var spawn_points: Array[Node2D]

var next_spawn_point_index = 0

func _ready():
	if not multiplayer.is_server():
		return
	
	multiplayer.peer_disconnected.connect(delete_player)
	
	for id in multiplayer.get_peers():
		add_player(id)
		
	add_player(1)

	
func _exit_tree():
	
	if multiplayer.multiplayer_peer == null:
		return
	
	if not multiplayer.is_server():
		return
	
	multiplayer.peer_disconnected.disconnect(delete_player)


func add_player(id):
	var player_instance =  player.instantiate()
	player_container.add_child(player_instance)
	player_instance.global_position = get_spawn_point()
	player_instance.name = str(id)
	player_instance.player_label.text = player_instance.name
	
	
func delete_player(id):
	if not player_container.has_node(str(id)):
		return
	
	player_container.get_node(str(id)).queue_free()


func get_spawn_point():
	var spawn_point = spawn_points[next_spawn_point_index].global_position
	next_spawn_point_index += 1
	
	if next_spawn_point_index >= len(spawn_points):
		next_spawn_point_index = 0
	return spawn_point

host


client
Makes it to the spawn point but no names are changed at all. Getting a lot of script errors


Wow - wasn’t expecting that! What you’re seeing now though is 100% a multiplayer bug, so I probably won’t be able to do much more. A quick search of the error on the host returned something interesting though. Thiis thread indicates it’s an authority problem and there are a few alternatives suggested (in particular, I like what Cridenour has to say):
https://www.reddit.com/r/godot/comments/10kvjq4/help_multiplayersynchronizer_not_syncing_player/

The original timing problem has to do with the fact that _ready() is being called in a way you didn’t expect. Assuming I was right that this is what you were experiencing, it’s not that your client label wasn’t changing; it was changing and then being changed back. Consider this test project I put together:

~
WTAC scene (When To Add Child), which is just a Node2D with a script:

extends Node2D

var labels_scene = preload("res://When To Add_Child/firstlabel.tscn")
var labels_instance


func _ready() -> void:
	labels_instance = labels_scene.instantiate()
	labels_instance.find_child("SecondLabel").text = "WTAC Set SecondLabel"
	add_child(labels_instance)

The labels scene contains “FirstLabel” and “SecondLabel”, each with Inspector-set text that reads “FirstInspector” and “SecondInspector” respectively.
~

You would expect the result of this to be that the second label reads “WTAC Set SecondLabel” to override the Inspector value, which is analogous to what you were doing, but instead it reads “SecondInspector”. This is because setting the label text does work properly, but add_child() indirectly calls that child’s _ready() function, and only when the code flow reaches this point are the Inspector values actually read and applied. This means it will override any conflicting changes that came before, simply because it’s the next thing in line to be executed.

So basically, I feel the change is needed, but I don’t see why it makes your synchronizers explode XD

Hopefully that thread gives you something to work with and that I’m not just leading you down the garden path with this change.

EDIT: actually, the original code probably works as intended if you just completely remove the Inspector value from the client’s label. For reasons that should be obvious now, I still feel the code is a bit fragile, but that would at least get you back on track with the course. Plenty of time for extensions and refactoring afterwards!

Thanks for the help anyways.

Maybe I am looking at this the wrong way. I know if I have the player script itself change its name, it does work and sync. However I only want it to do it when the server tells itself too and what to name itself. Is there a way I can make a custom signal to do it? I’m not understanding custom signals too much and having a hard way to find out how to connect the signal. I have an example of the text updating fine if the player itself it invoking the name change. https://youtu.be/p0nxfgai5wk

I’m thinking it would work if the flow went like this

  1. Server / Lobby script : Please change ur name to “Name given”. Trigger the signal from the player script
  2. Client / Player script: my signal has been trigger, I’m changing my name that the server told me to.

Did you check the link I posted? While not exactly the same, this sounds pretty similar to what’s described in there.

Privacy & Terms