Client Joining Multiplayer Game Results in Server Becoming Client and Client Hanging

Godot Version

v4.6.stable.official [89cea1439]

Question

I am trying to make a 3d multiplayer game to learn about how multiplayer works. I tried following a tutorial, but I can’t find a link to it :sweat_smile:

The main scene is a Node2D that is being used as a menu. It just has two buttons and is linked to this script:

extends Node2D

var map: String = “res://Maps/YetiBottles/main_game.tscn”

func _on_serverstartbutton_pressed() → void:
HighLevelNetworkHandler.start_server()
get_tree().change_scene_to_file(map)

func _on_clientconnectbutton_pressed() → void:
print(“Client: Client button pressed”)
get_tree().change_scene_to_file(map)
HighLevelNetworkHandler.start_client()

"res:://Maps/YetiBottles/main_game.tscn” has a layout that is:

Node3D
┠╴GamePlatform
┃ ┖╴Ground
┃ ┠╴CollisionShape3D
┃ ┖╴Sprite3D
┠╴Projectile
┃ ┖╴RigidBody3D
┃ ┠╴CollisionShape3D
┃ ┠╴BallDefault
┃ ┖╴HitSoundPlayer
┠╴OmniLight3D
┖╴MultiplayerSpawner

$’Node3D/MultiplayerSpawner’ has its NetworkPlayer set to “res://Player/Player.tscn”, which has the following layout:

PlayerCameraAnchor
┠╴PaddleElevator
┃ ┖╴PaddleBody
┃ ┠╴PlayerCamera
┃ ┃ ┠╴SubViewportContainer
┃ ┃ ┃ ┖╴SubViewport
┃ ┃ ┃ ┖╴MainGameGui
┃ ┃ ┃ ┠╴Control
┃ ┃ ┃ ┖╴ScoreLabel
┃ ┃ ┖╴AimNode_A
┃ ┃ ┖╴AimNode_B
┃ ┃ ┖╴Sprite3D
┃ ┠╴PaddleFixed
┃ ┃ ┖╴Cube
┃ ┠╴CollisionShape3D
┃ ┖╴Unstucker
┃ ┖╴CollisionShape3D
┖╴MultiplayerSynchronizer

The layout is weird because of the WASDEQ movement that it has to support for my game concept. HighLevelNetworkHandler is a Global whose script is:

extends Node

signal host_started()

const PORT: int = 42069 # Below 65535 (16-bit unsigned max value)
const HOST:String = “localhost” # TODO: Add a menu to change this in game.

var peer: ENetMultiplayerPeer

func start_server() → void:
peer = ENetMultiplayerPeer.new()
peer.create_server(PORT)
multiplayer.multiplayer_peer = peer

# Spawn the host player (peer ID 1)
HighLevelNetworkHandler.host_started.emit() 

func start_client() → void:
print(“Client: Client starting”)
peer = ENetMultiplayerPeer.new()
print("Client: peer = ",peer)
peer.create_client(HOST, PORT)
multiplayer.multiplayer_peer = peer 

I tried making a singleplayer version with the MultiplayerSynchronizer and MultiplayerSpawner gone and with just player.tscn inside the map, and setting the map to the main scene. It worked as intended. I’m not sure why it behaves strangely when trying to make it multiplayer, though that’s probably due to my apparent lack of ability to understand how multiplayer works. Instead of working as intended, the server instance joins the game, spawning as a client, and the Client instance just goes blank.

It may sound silly, but try rearranging it in each function separately.


func start_server() → void:
  var peer: ENetMultiplayerPeer
  peer = ENetMultiplayerPeer.new()
  peer.create_server(PORT)
  multiplayer.multiplayer_peer = peer

# Spawn the host player (peer ID 1)
HighLevelNetworkHandler.host_started.emit() 

func start_client() → void:
  var peer: ENetMultiplayerPeer
  print(“Client: Client starting”)
  peer = ENetMultiplayerPeer.new()
  print("Client: peer = ",peer)
  peer.create_client(HOST, PORT)
  multiplayer.multiplayer_peer = peer 

P.s. I hope it works.

When using the high level approach (spawner, authority, rpcs, etc.) your goal is:

  1. Make the initial connection. Here is where you set some info in your global script, set yourself to server or client. This is the important part that everything else flows from.
  2. Make sure you replicate the scene tree… whatever is going to be synchronized needs to be synced. At least to the point that MP Spawners can do the rest. (This is so Godots high level multiplayer can work. Later you can send raw bytes and make it all function on your own.)
  3. Set the authority appropriately. This is up to how you want the game to work over the internet. Eg server authoritative or not. It depends on what tutorial you’re following, but likely it’s easier to start with server authoritative.
  4. Learn to split the way you think about objects/programming in the context of multiplayer. Get used to checking if you’re the authority when scripting.

Think of it like this. We’re both playing your server authoritative game and you are the host. What that really means is that we’re both running your game, and it’s like you are red and I am green. We both have the same objects in our scene, but they are put into red mode or green mode depending on the color that was decided upon connection.

And for each object, it’s script has logic that checks if it’s in red mode or green mode. (Is authority vs not authority.) If we’re the authority, the real object, we carry out the update logic. If we’re not the authority, if we’re a puppet, then we just… set values to whatever the host sent, do nothing, fake stuff for the client, whatever.

With that in mind it should be clearer what you’re doing in the multiplayer tutorials. You’re making things “active” or “passive” based on authority, and that authority was set when it was created, and it was created when the authority over the spawner created it, and the authority of the spawner knew to do that because it’s checking for authority which was set at connection. It all flows from correctly setting the peer and authority.

Hi guys…

I’m kinda embarrassed about how long it took me to find this. The following was in the spawn_player function of the script attached to the multiplayer spawner:

if !multiplayer.is_server():
return

??? I dont even know what i was trying to do with this lol. I changed it to:

if multiplayer.is_server():
return

It works fine now.

1 Like