Dragon20C
Dragon20C

Reputation: 361

Godot 4.2.1 Multiplayer, spawning replicated/clients do not spawn in the correct position

Explanation: I have been attempting to make an multiplayer fps game and I am currently trying to make a spawn system, for some reason the replicated/client side of players never follow the position I want, they always go to the world position which is a vector3.Zero, I have been trying everything and I just cant get it to work correctly, and google searching I found one case where someone has the same issue as me but they didn't have any solution.

Network script (is a global script):

extends Node

var server : ENetMultiplayerPeer = null
var client : ENetMultiplayerPeer = null
var port : int = 9898
var ip_address : String = "localhost"
var max_clients : int = 20
var unique_identifier : int = -1
var game_manager : Node = null

var players : Dictionary = {}

func _ready():
    setup_network()

func setup_network() -> void:
    multiplayer.peer_connected.connect(peer_connected)
    multiplayer.peer_disconnected.connect(peer_disconnected)
    multiplayer.connected_to_server.connect(connected_to_server)
    multiplayer.connection_failed.connect(connection_failed)

func host_server() -> void:
    server = ENetMultiplayerPeer.new()
    var error = server.create_server(port,max_clients)
    if error != OK:
        print("Failed to create server. Error: " + error)
        return
    server.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER)
    multiplayer.multiplayer_peer = server
    unique_identifier = server.get_unique_id()
    send_player_info("Host",unique_identifier)
    game_manager.create_player(unique_identifier)

func join_server() -> void:
    client = ENetMultiplayerPeer.new()
    client.create_client(ip_address,port)
    client.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER)
    multiplayer.multiplayer_peer = client
    unique_identifier = client.get_unique_id()

func peer_connected(identifier : int) -> void:
    print("player has connected, ID: " + str(identifier))
    if identifier != 1:
        game_manager.create_player(identifier)

func peer_disconnected(identifier : int) -> void:
    print("player has disconnected, ID: " + str(identifier))
    var player = game_manager.get_node_or_null(str(identifier))
    if player:
        player.queue_free()

func connected_to_server() -> void:
    print("Connected to the server")
    send_player_info.rpc_id(1,"None",multiplayer.get_unique_id())

@rpc("any_peer")
func send_player_info(username : String, identifier : int) -> void:
    if not players.has(identifier):
        players[identifier] = {
            "identifier" : identifier,
            "username" : username
        }
    if multiplayer.is_server():
        for index in players:
            send_player_info.rpc(str(players[index]),index)
func connection_failed() -> void:
    print("Connection failed")

GameZone script(it simply connects to the functions in the network script and also spawns the player at a specific node position):

class_name GameZone extends Node

@export_subgroup("Game Exports")
@export var menu : Control
@export var spawn_points : Node3D
const TestDummyPlayer : PackedScene = preload("res://Game/Objects/TestDummyGame/TestDummyPlayer/TestDummyPlayer.tscn")
var index : int = 0

func _ready():
    Network.game_manager = self

func _on_single_player_pressed():
    Network.host_server()
    menu.hide()

func _on_host_pressed():
    Network.host_server()
    menu.hide()


func _on_join_pressed():
    Network.join_server()
    menu.hide()

func _on_exit_pressed():
    get_tree().quit()


func _on_line_edit_text_changed(ip):
    Network.ip_address = ip


func create_player(identifier : int) -> void:
    if not multiplayer.is_server(): return
    var instance = TestDummyPlayer.instantiate()
    instance.name = str(identifier)
    add_child(instance)
    instance.global_position = spawn_points.global_position
    

The player scene just in case something is wrong with it.

extends CharacterBody3D

@export var synchronizer : MultiplayerSynchronizer
@export var camera : Camera3D
@export var horizontal_n : Node3D
@export var vertical_n : Node3D
@export var sensitivity : float = 0.001
var spawn_point : Vector3

var mouse_motion : Vector2 = Vector2.ZERO

const SPEED = 7.0
const JUMP_VELOCITY = 4.5

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

func _ready():
    if not is_multiplayer_authority(): return
    camera.current = true
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _enter_tree():
    set_multiplayer_authority(str(name).to_int())
    synchronizer.set_multiplayer_authority(str(name).to_int())

func _unhandled_input(event):
    if not is_multiplayer_authority(): return
    if event is InputEventMouseMotion && Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
        mouse_motion = -event.relative * sensitivity
        rotate_y(mouse_motion.x)
        horizontal_n.rotate_x(mouse_motion.y)
        horizontal_n.rotation.x = clamp(horizontal_n.rotation.x,-PI / 2, PI / 2)

func _physics_process(delta):
    if not is_multiplayer_authority(): return
    if Input.is_action_just_pressed("ui_cancel"):
        get_tree().quit()
        
    # Add the gravity.
    if not is_on_floor():
        velocity.y -= gravity * delta

    # Handle jump.
    if Input.is_action_just_pressed("Jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # Get the input direction and handle the movement/deceleration.
    # As good practice, you should replace UI actions with custom gameplay actions.
    var input_dir = Input.get_vector("Left", "Right", "Forward", "Backward")
    var direction = (global_transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
    if direction:
        velocity.x = direction.x * SPEED
        velocity.z = direction.z * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
        velocity.z = move_toward(velocity.z, 0, SPEED)

    move_and_slide()

Pictures: enter image description here

enter image description here

Upvotes: 1

Views: 640

Answers (1)

MeSteve95
MeSteve95

Reputation: 131

This is an older post so you may have already solved it, but thought I'd provide an answer for anyone else who sees this.

It appears that you have conflicting authority checks when a peer joins the game.

In Network's peer_connected method, you have:

func peer_connected(identifier : int) -> void:
    print("player has connected, ID: " + str(identifier))
    if identifier != 1:
        game_manager.create_player(identifier)

Meaning that when a peer connects, all non-server clients will call create_player.

Then in GameZone's create_player method, you have:

func create_player(identifier : int) -> void:
    if not multiplayer.is_server(): 
        return
    var instance = TestDummyPlayer.instantiate()
    instance.name = str(identifier)
    add_child(instance)
    instance.global_position = spawn_points.global_position

This logic will only execute if it is the server (identifier == 1) that called this method.

This means that due to the two contradictory statements, the logic after create_player's authority check will never be run, meaning that peers never actually get spawned. In Godot, if there is no camera available, which occurs when the clients don't get a player spawned for them, a camera is spawned at the origin, which explains what you were experiencing.

To solve this, I would change peer_connected's authority check to:

if identifier == 1:

This way, the logic for spawning players will only be executed on the server, and this should result in the clients being properly spawned!

Upvotes: 0

Related Questions