Hello! I was making a little side project that I could play with my friends. But when I began to work on the networking, something scary happened. When the joining client presses “Join” the server gets the glitched playermodel and sees themselves, as if they weren’t already in the game.
player.gd
# game related initialization vars, not needed for this snippet
func _enter_tree() -> void:
set_multiplayer_authority(name.to_int())
func _ready() -> void:
if is_multiplayer_authority():
$bean/Armature/Skeleton3D/Plane_001.set_layer_mask_value(1, false)
$bean/Armature/Skeleton3D/Plane_001.set_layer_mask_value(2, true)
camera.set_cull_mask_value(2, false)
# more game related stuff, not necessary
multiplayer_spawner.gd
extends MultiplayerSpawner
@export var networked_player:PackedScene
func _ready() -> void:
multiplayer.peer_connected.connect(spawn_player)
func spawn_player(id:int):
if !multiplayer.is_server(): return
var player:Node = networked_player.instantiate()
player.name = str(id)
get_node(spawn_path).call_deferred("add_child", player)
menu_special.gd
extends Button
func _on_join_pressed() -> void:
game_autoload.start_client()
func _start_on_pressed() -> void:
game_autoload.start_server()
game_autoload.gd
extends Node
const IP_ADDRESS = "127.0.0.1"
const PORT = 42422
var peer := ENetMultiplayerPeer.new()
func start_server():
var error = peer.create_server(PORT)
if error != OK:
print(error)
multiplayer.multiplayer_peer = peer
get_tree().change_scene_to_file("res://main.tscn")
func start_client():
peer.create_client(IP_ADDRESS, PORT)
multiplayer.multiplayer_peer = peer
I have no idea how to fix this, let alone how I made it. Reach out if unclear or if a solution is available. Thanks!
Sounds like the camera within your player scene is set to current? When loaded in there can be only one current camera so the scene makes changes as needed, picking this new camera to view through, but it’s always the newest player’s camera.
I left the current flag on during testing, but forgot to change it when trying to work on the networking. After the change, there’s no sufficient results.
Do you assign current as part of your authority checks? What does the rest of your camera related script look like?
func _ready() -> void:
if is_multiplayer_authority():
$bean/Armature/Skeleton3D/Plane_001.set_layer_mask_value(1, false)
$bean/Armature/Skeleton3D/Plane_001.set_layer_mask_value(2, true)
camera.set_cull_mask_value(2, false)
camera.current = is_multiplayer_authority()
Well, it’s just a basic character controller with some animation junk for polish.
extends CharacterBody3D
const SPEED = 5.0
const SENS = 0.003
@onready var head = $bean
@onready var camera = $bean/Camera3D
@onready var animtree = $bean/AnimationTree
var walkblend = 0.0
var walkdirblend = 0.0
func _enter_tree() -> void:
set_multiplayer_authority(name.to_int())
func _ready() -> void:
if is_multiplayer_authority():
$bean/Armature/Skeleton3D/Plane_001.set_layer_mask_value(1, false)
$bean/Armature/Skeleton3D/Plane_001.set_layer_mask_value(2, true)
camera.set_cull_mask_value(2, false)
camera.current = is_multiplayer_authority()
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _unhandled_input(event):
if event is InputEventMouseMotion:
head.rotate_y(-event.relative.x * SENS)
camera.rotate_x(event.relative.y * SENS)
camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-80), deg_to_rad(80))
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
var input_dir := Input.get_vector("left", "right", "front", "back")
var direction = (head.transform.basis * -Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
if input_dir.y < 0:
walkdirblend = lerpf(walkdirblend, -1.0, 0.3)
else:
walkdirblend = lerpf(walkdirblend, 1.0, 0.3)
walkblend = lerpf(walkblend, 1.0, 0.3)
animtree.set("parameters/walkdirblend/scale", walkdirblend)
animtree.set("parameters/movement/blend_amount", walkblend)
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
walkblend = lerpf(walkblend, 0.0, 0.3)
animtree.set("parameters/movement/blend_amount", walkblend)
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()
Only other recommendations I’d say is only connect this function on the server
func _ready() -> void:
if multiplayer.is_server():
multiplayer.peer_connected.connect(spawn_player)
And make sure you move the hosting player before joining, the players will spawn at the same location so maybe they are overlapping?
The host gets the generic grey screen, whilst the client remains in the same place. But this time the client is connected via this error message: E 0:00:15:194 game_autoload.gd:16 @ start_client(): The multiplayer instance is already active.
Ah your host needs to spawn itself too
func _ready() -> void:
if multiplayer.is_server():
multiplayer.peer_connected.connect(spawn_player)
spawn_player(1) # create host-player
that client error message is concerning, you should handle errors from functions better, right now you only print an error number if the host fails, but the program continues anyways
func start_client():
var err := peer.create_client(IP_ADDRESS, PORT)
if err != OK:
push_warning("Failed to create client: ", error_string(err)
return
multiplayer.multiplayer_peer = peer
multiplayer.connected_to_server.connect(_client_connect_success)
multiplayer.connection_failed(_client_connect_fail)
func _client_connect_success() -> void:
get_tree().change_scene_to_file("res://main.tscn")
func _client_connect_fail() -> void:
push_warning("Couldn't connect client to server")
Thanks! The host works but now when the client joins, the client mirrors the inputs of the host. Although funny to mess around with, I found nothing to actually fix it.
It doesn’t seem like you’ve programmed anything to prevent that. Your _unhandled_input and _physics_process will react to any input, you need them to only react when they are multiplayer authority. This could be done with set process functions
func _ready() -> void:
var auth: bool = multiplayer.is_multiplayer_authority()
camera.current = auth
# similar to camera.current = , enables/disables the functions based on authority
set_process_unhandled_input(auth)
set_physics_process(auth)
Thanks again! The client’s viewport remains grey. From the server’s perspective, the client seems to walk. That was easy to fix but I have no idea how to fix the viewport issue.
Did you set the camera’s current property off in the editor? Can’t think of anything else, make sure they are both on the same scene
I fixed it. All I did was replace one scene in the auto-spawn. Thanks for the help anyways!