godotengine / godot

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

MultiplayerSpawner does not function when using WebRTCMultiplayerPeer #80743

Closed chrisl8 closed 1 year ago

chrisl8 commented 1 year ago

Godot version

v4.1.1.stable.official [bd6af8e0e]

System information

Windows 10 - Godot v4.1.1 stable

Issue description

MultiplayerSpawner works fine with ENet multiplayer, but it does not work WebRTC. Using the same code base and switching between the two allows it to work when switching to ENet.

I have not yet found a fully working WebRTC example game that implements MultiplayerSpawner, so it is entirely possible that I am just doing it wrong.

However, it works with ENet, and the game works if I manually spawn the scenes on other devices, including MultiplayerSynchronizers work fine over WebRTC, just not the Spawners.

Steps to reproduce

Build a basic WebRTC game, including signaling server and attempt to add scenes that are tied to MultiplayerSpawner and they do not show up on peer games.

Minimal reproduction project

I have created a repo to demonstrate my findings here:

https://github.com/chrisl8/godot-webrtc-test

It requires running a Node.js based signalling_server to function.

The webrtc-native and node modules are both included in the repo, so it should be easy to pull down and run.

I cut my example down to the bone, so it does not include any MultiplayerSynchronizers. If you need me to add more to the example let me know and I can.

azuloo commented 1 year ago

Hi,

Can you please elaborate a bit more about errors you get? I'm in a process of checking the project you've provided but it would be generally appreciated if we have some starting direction to look at. Thank you!

chrisl8 commented 1 year ago

There are no errors, the scenes are simply not propagated out to the other players who join the game when using WebRTC.

Run the game with 2 or 3 or 4 instances and if you use ENet you will see the scene in every instance, but with WebRTC it will only show up in the server instance and not the others.

Here is what it looks like when using ENet: using-enet

Here is what it looks like using WebRTC: using-webrtc

The MultiplayerSpawner just does not function when using WebRTC.

Thank you.

azuloo commented 1 year ago

A little update. There's a signal in MultiplayerSpawner - spawned - that is emitted once a scene has been replicated. In case when we're using ENet - it's emitted (and the scene has been replicated), no problem there. But when it's WebRTC - nothing happens. I've also rechecked the networking code and, from the first sight, there're no problems/bugs with it, the peers are interconnected. So I suspect the problem might be in the scene replication code itself. I will keep you updated once I know more.

Faless commented 1 year ago

You seem to be using the create_mesh setup, where each peer is connected to each other in mesh form and there is no "server". Since the server (ID = 1) is the default authority of nodes, you will need to set the MultiplayerSpawner authority to the player ID you want to act as server. In this specific case, this patch in your game solves the issue:

diff --git a/main-user-client/User.gd b/main-user-client/User.gd
index 95b1d9a..d114024 100644
--- a/main-user-client/User.gd
+++ b/main-user-client/User.gd
@@ -20,7 +20,10 @@ var game_scene_initialized: bool            =  false
 var level_scene                             := preload("res://scene.tscn")
 var is_server: bool                         =  false
 var server_name: String                     =  ""
-var server_id                               := -1
+var server_id                               := -1 :
+   set(value):
+       server_id = value
+       get_tree().get_root().get_node("Main/LevelSpawner").set_multiplayer_authority(value)
 var server_password: String                 =  ""
 var local_debug_instance_number             := -1
 var mult: SceneMultiplayer                  =  null
chrisl8 commented 1 year ago

@Faless I am testing what you posted now, but it is not changing the behavior of the game at all for me. The results are still the same: no replication of scene to peers/clients.

azuloo commented 1 year ago

@chrisl8 That's weird, it works on my end. Scenes are being replicated but with a slightly bigger delay than when using ENet. Maybe there's a syntax error lurking somewhere? Pasting Faless' code just in case:

var server_id                               := -1 :
    set(value):
        server_id = value
        get_tree().get_root().get_node("Main/LevelSpawner").set_multiplayer_authority(value)
chrisl8 commented 1 year ago

@Faless @azuloo The solution provided works for TWO instances, but running 3+ breaks it. You can see this by setting Run instances to 3 or 4 in Debug.

If I add a greater delay for the startup I can see that it works at first when only 2 are connected, but fails when a 3rd client connects.

Faless commented 1 year ago

@chrisl8 well, this is a matter of timing in your code, not in the replication code. You simply need to set the User.server_id (i.e. set the multiplayer authority) before you add the peer to WebRTCMultiplayerPeer.

chrisl8 commented 1 year ago

@chrisl8 well, this is a matter of timing in your code, not in the replication code. You simply need to set the User.server_id (i.e. set the multiplayer authority) before you add the peer to WebRTCMultiplayerPeer.

It doesn't matter how long I delay things, if there are more than 2 peers, it fails.

I will keep working on it though, perhaps this is not a Godot engine bug after all.

azuloo commented 1 year ago

I didn't quite like the solution where we created new WebRTCMultiplayerPeer, new mesh and established new interconnections between peers each time a new peer had appeared. So I've decided to try to not do that and move the initialization part into a separate func. In init_connection() I've only left the part where we establish a new connection (and adding a new peer essentially) but only for those peers that haven't been already interconnected. And it actually worked! Here's the changes (along with the Faless' code from earlier):

User.gd
func init_rtc_peer():
    rtc_peer = WebRTCMultiplayerPeer.new() # Comment to disable WebRTC
    #rtc_peer = ENetMultiplayerPeer.new() # Comment to disale ENet
    if User.is_server:
        rtc_peer.create_mesh(ID) # Comment to disable WebRTC
        #rtc_peer.create_server(8080)
    else:
        rtc_peer.create_mesh(ID) # Comment to disable WebRTC
        #rtc_peer.create_client('127.0.0.1', 8080)

    get_tree().get_multiplayer().multiplayer_peer = rtc_peer

func init_connection():
    for peer_id in peers.keys():
        if connection_list.has(peer_id):
            continue # This peer has already been initiated, skipping
        var connection := WebRTCPeerConnection.new()
        connection.initialize({"iceServers": [ { "urls": ["stun:stun.l.google.com:19302"]}]})
        connection.session_description_created.connect(session_created.bind(connection))
        connection.ice_candidate_created.connect(ice_created.bind(connection))
        connection_list[peer_id] = connection
        rtc_peer.add_peer(connection, peer_id) # Comment to disable ENet

    network_initialized = true

Client.gd
...
if type == Message.USER_INFO:
        User.user_name = data
        User.ID = id
        print("Received User name = %s ID# %s" %[data, id])
        User.init_rtc_peer()
        user_name_feedback_received.emit()
        return
...

I've tested it with 4 windows and the scene has spawned in all of them. Please let me know if I didn't break any logic in your code, because it was quite a fast solution. P.S. I didn't do it as a diff because it was a little bit messy there, so my apologies if it's not intuitive.

chrisl8 commented 1 year ago

@azuloo and @Faless Thank you so much for your assistance!

You have proven clearly now that MultiplayerSpawner does indeed work with WebRTCMultiplayerPeer along with pushing me in the right direction to make it work.

Each of your contributions was a key to the puzzle.

If anyone else finds themselves here and is in need of help, feel free to check out the repo I used to open this ticket at https://github.com/chrisl8/godot-webrtc-test and please open Issues against that repository if it doesn't work for you and I will be happy to work with you to improve the example to be more clear and usable. I am happy to use that repo of mine as a "general user help forum" on WebRTC multiplayer in Godot.

I am closing this now as it is clear that there is no bug with Godot here. Thank you.