godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
87.16k stars 19.58k forks source link

"Node not found: MultiplayerSynchronizer" when node doesn't exist on all clients #76894

Open riazey opened 1 year ago

riazey commented 1 year ago

Godot version

4.0.2

System information

Windows 10

Issue description

For nodes with a MultiplayerSynchronizer child:

Originally I had planned to only instance scenes on a client if they were in the same "area" but maybe I am barking up the wrong tree~? >0<

image

Steps to reproduce

OR

OR

Minimal reproduction project

Note: GUI script has a bool/toggle for deleting a node with a MultiSyncy on it after joining

Multiplayer_Test.zip

Calinou commented 1 year ago

cc @Faless

LivelyCarpet87 commented 11 months ago

I'm experiencing the same issue when attempting to delete or remove a node (which contains a MultiSync with local as authority, spawned in on local via MultiSpawner) on the server side, with one error message per node deleted. The error messages do not occur on clients connected to the server.

benjamin-kirkbride commented 10 months ago

Experiencing a similar issue. Any progress on this?

Faless commented 10 months ago

For nodes that gets dynamically added/removed from tree, you want to use a MultiplayerSpawner to ensure proper replication across clients. See this article for example.

You can use the visibility API (public_visibility/set_visibility_for/is_visible_to/add_visibility_filter) to control which player will see and receive updates for a specific node/synchronizer.

PPillau commented 10 months ago

Got the same problem here. I think I set up everything correctly: 1) Got the MultiplayerSpawner in my level, with spawn path to players-container and auto spawn list element 0 to my player.tscn. 2) In my player.tscn got a MultiplayerSynchronizer that synchronizes all my properties with correct visiblity

Getting the same _getnode: Node not found [...]Player/1/MultiplayerSynchronizer error.

After some debugging I found out what happens in my project: The host correctly gets spawned in (Player 1) on his side. Then when the first client joins (Player 2), on the host-side the client player gets correctly spawned (host now has: Player 1 & Player 2 nodes). But on the client side, only the client Player get spawned in, but not the host player (so client now only has Player 2 node), which means the client throws an error that Player 1 (the Player 1 node or more precisely it's MultiplayerSynchronizer) is missing...

Am I doing something wrong here? I think I followed the tutorial and setup correctly... I can also share my project over a call if somebody wanna take a look with me

igamigo commented 10 months ago

What you are all likely missing is configuring your MultiplayerSpawner's Auto Spawn List to include your player scene, so that when a client joins, the host's player scene is spawned automatically and its MultiplayerSynchronizer node is in your node tree.

olivatooo commented 10 months ago

I'm having the same problem, but I'm using RPC calls to spawn a node in both client and server. Idk if it's relevant but I'm adding a scene into MultiplayerSpawner and instantiating an inherithed scene

cidwel commented 10 months ago

For nodes that gets dynamically added/removed from tree, you want to use a MultiplayerSpawner to ensure proper replication across clients. See this article for example.

Maybe it's not the ideal place to ask, but that article is adding the authority in getset. This seems to be forbidden in latest dev5. The tutorial adds auth like this:

# Set by the authority, synchronized on spawn.
@export var player := 1 :
    set(id):
        player = id
        # Give authority over the player input to the appropriate peer.
        $PlayerInput.set_multiplayer_authority(id)

I tried to do the same in c#

public int _clientId;

    public int clientId {
        get { return _clientId; }
        set {
            _clientId = value;
            GD.Print("SETTER > setmultiplayerauthority");
            SetMultiplayerAuthority(value);
        }
    }

I set clientId just before adding it in the tree, and this is what I get in console.

"/root/Game/ServerStore/Beach/Players/Player2/MultiplayerSynchronizer" is unable to process the pending spawn since it has no network ID. This might happen when changing the multiplayer authority during the "_ready" callback. Make sure to only change the authority of multiplayer synchronizers during "_enter_tree" or the "_spawn_custom" callback of their multiplayer spawner.
  <C++ Error>    Condition "pending_sync_net_ids.is_empty()" is true. Returning: ERR_INVALID_DATA
  <C++ Source>   modules/multiplayer/scene_replication_interface.cpp:243 @ on_replication_start()

Shall I create a github issue for this?

What you are all likely missing is configuring your MultiplayerSpawner's Auto Spawn List to include your player scene, so that when a client joins, the host's player scene is spawned automatically and its MultiplayerSynchronizer node is in your node tree.

Not in my case. The spawner is populated with the player scene.

My strategy is:

Both map and player are inside the Multiplayerspawner

The server:

The client:

This is the remote window from client: image

And this is remote tree from server: image

It gets fixed if you disable the "Public Visibility" of the NetworkSynchroniser located inside each player, which will make the props not to sync.

My guess is that is accurate that the "Map" is not loaded when the sync starts. So I might need to set Public visibility to off and find a way to tell players that only the players inside that map should be able to sync variables (if I understand it correctly)

I will try to set the visibility manually only when the scene is completely loaded as this comment recommends and see if that helps

olivatooo commented 10 months ago

After tinkering a little bit I arrived into the following rule of thumb:

This apparently works well! But I guess for more performative and non prototype projects you should use low level network

But this looks much more like a Server-Client model than a P2P where every player is responsible for its own scenes... Maybe I'm still missing something

But my rule solves this issue problem

Basically you want something similar to this:

@rpc("any_peer", "call_local", "reliable")
func add_node(nodePath: String):
    if not multiplayer.is_server():
        return
    # Always send the nodePath
    var newNode = load(nodePath).instantiate()
    # Here is the MultiplayerSpawner Spawn Path
    # Don't forget this "true" in add child
    get_node("/root/Game/Level").add_child(newNode, true)

@rpc("any_peer", "call_local", "reliable")
func remove_node(nodePath: String):
    if not multiplayer.is_server():
        return
    # Here you MUST receive a node path!
    var e = get_node(nodePath)
    if e:
        e.queue_free()

When calling from server or client you can use

remove_node.rpc("res://your_node")
add_node.rpc("res://your_node")

# or 

remove_node.rpc(yourNode.get_path())
add_node.rpc(yourNode.get_path())
nicholasrobertm commented 9 months ago

Just as another note in case someone ends up here, the issue I ran into to cause something like this was accidentally calling add_child on the wrong node. E.g. if you have your players in a node in your scene called 'players' make sure when you add them to the scene you're not adding them somewhere other than what is listed as the spawn path in the MultliplayerSpawner. An obvious issue but easy to miss if you're new to godot's networking

balassanne commented 9 months ago

Either if I use RPCs or a predefined spawner, and am not missing the parent node, I can confirm this error. Adding a multiplayer synchronizer to dynamically spawned objects will cause this error on despawn. Everything works as intended, but why this error set is shown in client in each despawn? Something is really of here, and I feel that there is some unexpected thing happening. I kind of confirm this problem too... but am also new to godot....

sebastienwood commented 9 months ago

I had the same issue while trying to fiddle around the project mentionned in the above comments (https://github.com/godotengine/tps-demo)

Succinctly, I tried to split the UI node from the menu scene in a loading, setting and login scene. Login hold the loading scene, hidden by default. The loading scene is generalist: it holds functions to load any resource and update itself with a loading bar while waiting for the ResourceLoader, and finally emit a signal when done. Its code is as follow:


var path = "res://scenes/world_screen/world_screen.tscn"

signal loaded(path: String)

@onready var loading_progress = %Progress
@onready var loading_done_timer = %DoneTimer

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta):
    if visible:
        var progress = []
        var status = ResourceLoader.load_threaded_get_status(path, progress)
        if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
            loading_progress.value = progress[0] * 100
        elif status == ResourceLoader.THREAD_LOAD_LOADED:
            loading_progress.value = 100
            set_process(false)
            loading_done_timer.start()
        else:
            print("Error while loading level: " + str(status))
            hide()

func _load(new_path: String, cb: Callable, sub_thread: bool = true):
    show()
    path = new_path
    loaded.connect(cb)
    if ResourceLoader.has_cached(path):
        loaded.emit()
    else:
        ResourceLoader.load_threaded_request(path, "", sub_thread)

func _on_done_timer_timeout():
    loaded.emit(path)

Running it however throws the following set of errors: image

This error do not happen if I do not use the loading scene and directly perform the load_threaded_request in the login scene.

olivatooo commented 5 months ago

There is a comprehensive way to do it? I never found a correct way of using godot multiplayer

Faless commented 5 months ago

There is a comprehensive way to do it? I never found a correct way of using godot multiplayer

@olivatooo There is a comprehensive guide on how to spawn level and players here: https://godotengine.org/article/multiplayer-in-godot-4-0-scene-replication/

@banane42 I can't really tell what's happening, make sure you are not removing the level yourself, or try to make an MRP and open a new issue.

des1redState commented 5 months ago

Reproduced using v4.2.2.rc.custom_build [474589eb8]

banane42 commented 5 months ago

@Faless

@banane42 I can't really tell what's happening, make sure you are not removing the level yourself, or try to make an MRP and open a new issue.

Removed my earlier comment as I discovered I'm an idiot and was removing the level on the connecting client via a signal.

BatteryAcid commented 2 months ago

I ran into the original issue, where a spawned player's MultiplayerSynchronizer was not found when other clients connected. My setup, really for experimenting, was to build a dedicated server, have the server auto-spawn a dummy player, so when clients join they can see them.

However, I was adding the player object (to be spawned) during the server's game_manager ready function, which led to this error because the scene was not ready to add a player. At least that's what I think is happening, because once I added a 3 second timer after the ready was hit, the dummy player was added to the scene as expected, and all clients had it spawned properly.

Maybe there is a bug as this is a slightly different use case, but it's likely something that's expected to be there, some node, isn't.

Kairu1206 commented 2 months ago

I have the same issue, but the only twist is that it works where I am on the same computer, like running multiple instances of the game on the same machine. But when I open the game on two or more computers, the server computer will run into this issue. I don't know what happen at this point.