Hello, im trying to create multiplayer test chamber, but somehow i dont understand why camera3D works only on one player instance.

As you can see the one on the left is host and on the right is +1 player.

Game spawns players both at separate coordinates. Host can control his camera but the client cant.

Also When i spawn (instantiate) new nodes (Items) i add them to the World node as child. But they dont show up in the other player screen.

Also if i pick up item, it dont show up on other players screen. Item picked up and its position is changed to the pickers hand position.

One thing that comes to mind is adding the MultiplayerSync node to the Item Nodes and add the transform path to sync.

But why camera controls not working? I mean the camera is a child of the player node. Also one thing i noticed that the player on the right controls the Top down camera for some reason, it rotates as i move mouse. But it should work on host player too and it does not.

The main camera is a child of "h" node

The topdown camera is child of "UIRoot" control node

"h" node is set top_level true, and i got function that moves it around and that fun is called from _physics_process():

From what i understood is that at the top of the physics_process function i check if the current instnace is the player if not then do not move player. It works for the WASD movements but not camera 🙁.

If i add "h" nodes position/transform as synchronizer property then the host will control both player cameras at the same time, which is not what needs to happen.

  • OK so to recap.

    Player.tscn


    Player.gd

    ## more code
    func _enter_tree() -> void:
    	set_multiplayer_authority(str(name).to_int())
    	
    	pass
    func _ready():
    	if not is_multiplayer_authority(): return
    	
    	## more code
    	camera_3d.current = true
    	
    func _unhandled_input(_event):
    	if not is_multiplayer_authority(): return
    	
    	## more code
    	if Input.is_action_just_pressed("action_throw"):
    		spawn_item.rpc()
    	## more code
    
    func _input(event):
    	if not is_multiplayer_authority(): return
    	## more code
    	
    func _physics_process(delta):
    	if not is_multiplayer_authority(): return
    	## more code
    	
    @rpc("any_peer","call_local")		
    func spawn_item():
    	var basis_z = camera_3d.global_transform.basis.z
    	var g_t = global_transform
    	spawn_item_signal.emit(g_t, basis_z)
    	pass
    
    ## more code

    Note the spawn_item is my own function, so just insert your code there or adjust to your needs. Its just a demonstration how it works.

    MultiplayerSynchronizer

    Had to add Camera3D global_transform to get the spawn_item to receive the correct transform value, otherwise i was getting vec3(0,0,1)


    Box.tscn

    No script in that scene

    MultiplayerSynchronizer


    world.tscn

    I messed up here but it still works. I placed the Players node underMultiplayerSpawner. Players node should be a child of the World node. But if you reference it in the code it still works as you saw in the video above.

    Add scenes to the spawner list. You can add player scene too but i instantiate players on _ready() function of the World node so i dont need it atm. but you will need it once you start adding functionality for when you want players join mid game, when server is running.

    World script just contains code to spawn player -> instantiate player scene, add child to Players node. Also i got code here for spawn_item,

    world.gd

    extends Node3D
    const PLAYER = preload("res://Assets/Player/player.tscn")
    const BOX = preload("res://Assets/Items/box.tscn")
    @onready var players: Node = $MultiplayerSpawner/Players
    const start_pos: Vector3 = Vector3(0,3,0)
    # Called when the node enters the scene tree for the first time.
    func _ready() -> void:
    	var index =0
    	for i in GameManager.players:
    		spawn_player(GameManager.players[i])
    		index +=1
    
    	pass # Replace with function body.
    
    
    # Called every frame. 'delta' is the elapsed time since the previous frame.
    func _process(delta: float) -> void:
    	pass
    
    func spawn_item_world(player_transform:Transform3D, basis_z:Vector3):
    	
    	var new_box = BOX.instantiate()
    	new_box.position = player_transform.origin - (basis_z *  3)
    	add_child(new_box)
    	
    	pass
    
    func spawn_player(player_data:PlayerData):
    	var new_pos:Vector3 
    
    	var new_player = PLAYER.instantiate()
    	new_player.name = str(player_data.id)
    	new_player.player_data=player_data
    	players.add_child(new_player)
    	new_player.spawn_item_signal.connect(spawn_item_world)
    	
    	var player_count = players.get_children().size()
    	
    	if player_count > 1:
    		var last_player = players.get_children()[player_count-1]
    		new_pos = last_player.position + Vector3(3,3,3)
    		new_player.position = new_pos
    	else:
    		new_player.position = start_pos

    MultiplayerUI.tscn <- entry point for the game


    MultiplayerUI.gd

    extends Control
    @export var address = "127.0.0.1"
    @export var port = 8910
    
    var peer:ENetMultiplayerPeer
    
    @onready var host: Button = $VBoxContainer/Host
    @onready var join: Button = $VBoxContainer/Join
    @onready var start: Button = $VBoxContainer/Start
    @onready var player_name: LineEdit = $VBoxContainer/HBoxContainer/PlayerName
    
    const WORLD = preload("res://Assets/World/world.tscn")
    
    # Called when the node enters the scene tree for the first time.
    func _ready() -> 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)
    	
    	pass # Replace with function body.
    
    
    # Called every frame. 'delta' is the elapsed time since the previous frame.
    func _process(delta: float) -> void:
    	pass
    # this gets called on server and clients
    func peer_connected(id):
    	print("Player connected: ", id)
    	pass
    	
    # this gets called on server and clients	
    func peer_disconnected(id):
    	print("Player disconnected: ", id)
    	pass
    	
    # called only from clients	
    func connected_to_server():
    	print("Connected to server!")
    	send_player_info.rpc_id(1,player_name.text,multiplayer.get_unique_id())
    	pass
    
    # called only from clients		
    func connection_failed(id):
    	print("Player connection failed: ", id)
    	pass
    @rpc("any_peer")	
    func send_player_info(name, id):
    	if !GameManager.players.has(id):
    		var new_player_data:PlayerData = PlayerData.new()
    		new_player_data.id=id
    		new_player_data.player_name=name
    		new_player_data.score = 0.0
    		GameManager.players[id]=new_player_data
    		#GameManager.players[id] = {
    			#"name":name,
    			#"id":id, 
    			#"score": 0
    		#}
    	if multiplayer.is_server():
    		for i in GameManager.players:
    			send_player_info.rpc(GameManager.players[i].player_name,i)
    	pass
    	
    @rpc("any_peer","call_local")
    func start_game():
    	var scene = WORLD.instantiate()
    	get_tree().root.add_child(scene)
    	self.hide()
    	pass
    	
    func _on_host_button_down() -> void:
    	peer = ENetMultiplayerPeer.new()
    	var error = peer.create_server(port, 3)
    	if error != OK:
    		print("ERROR - Cannot host: ", error)
    		return
    	peer.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER)
    	multiplayer.set_multiplayer_peer(peer)
    	print("Waiting for players!")
    	send_player_info(player_name.text,multiplayer.get_unique_id())
    	pass # Replace with function body.
    
    
    func _on_join_button_down() -> void:
    	peer = ENetMultiplayerPeer.new()
    	peer.create_client(address,port)
    	peer.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER)
    	multiplayer.set_multiplayer_peer(peer)
    	print("Waiting for players!")
    	pass # Replace with function body.
    
    
    func _on_start_button_down() -> void:
    	start_game.rpc()
    	pass # Replace with function body.

    GameManager.gd <- autoload script to keep player data global

    extends Node
    
    var players = {}
    
    func _ready() -> void:
    	pass
    	
    func _process(_delta: float) -> void:
    	
    	pass

    Tutorials that helped:

So items spawn works from one player to other but not from other to one. Not sure.. also mouse position intersection in the 3d world is not correct, it seems it gets other players position.. not current session players position.

Still need to figure out why.. jeesless christ.

i keep getting these.. not sure..

Rewritten the whole thing from scratch.. almost there..

One thing left to do is to sync the client (other player) mouse position. My item spawns in the direction where the player is looking (camera) so idk why the item on the client (other player) spawns not in the direction of where he looks.

EDIT: fixed it

    OK so to recap.

    Player.tscn


    Player.gd

    ## more code
    func _enter_tree() -> void:
    	set_multiplayer_authority(str(name).to_int())
    	
    	pass
    func _ready():
    	if not is_multiplayer_authority(): return
    	
    	## more code
    	camera_3d.current = true
    	
    func _unhandled_input(_event):
    	if not is_multiplayer_authority(): return
    	
    	## more code
    	if Input.is_action_just_pressed("action_throw"):
    		spawn_item.rpc()
    	## more code
    
    func _input(event):
    	if not is_multiplayer_authority(): return
    	## more code
    	
    func _physics_process(delta):
    	if not is_multiplayer_authority(): return
    	## more code
    	
    @rpc("any_peer","call_local")		
    func spawn_item():
    	var basis_z = camera_3d.global_transform.basis.z
    	var g_t = global_transform
    	spawn_item_signal.emit(g_t, basis_z)
    	pass
    
    ## more code

    Note the spawn_item is my own function, so just insert your code there or adjust to your needs. Its just a demonstration how it works.

    MultiplayerSynchronizer

    Had to add Camera3D global_transform to get the spawn_item to receive the correct transform value, otherwise i was getting vec3(0,0,1)


    Box.tscn

    No script in that scene

    MultiplayerSynchronizer


    world.tscn

    I messed up here but it still works. I placed the Players node underMultiplayerSpawner. Players node should be a child of the World node. But if you reference it in the code it still works as you saw in the video above.

    Add scenes to the spawner list. You can add player scene too but i instantiate players on _ready() function of the World node so i dont need it atm. but you will need it once you start adding functionality for when you want players join mid game, when server is running.

    World script just contains code to spawn player -> instantiate player scene, add child to Players node. Also i got code here for spawn_item,

    world.gd

    extends Node3D
    const PLAYER = preload("res://Assets/Player/player.tscn")
    const BOX = preload("res://Assets/Items/box.tscn")
    @onready var players: Node = $MultiplayerSpawner/Players
    const start_pos: Vector3 = Vector3(0,3,0)
    # Called when the node enters the scene tree for the first time.
    func _ready() -> void:
    	var index =0
    	for i in GameManager.players:
    		spawn_player(GameManager.players[i])
    		index +=1
    
    	pass # Replace with function body.
    
    
    # Called every frame. 'delta' is the elapsed time since the previous frame.
    func _process(delta: float) -> void:
    	pass
    
    func spawn_item_world(player_transform:Transform3D, basis_z:Vector3):
    	
    	var new_box = BOX.instantiate()
    	new_box.position = player_transform.origin - (basis_z *  3)
    	add_child(new_box)
    	
    	pass
    
    func spawn_player(player_data:PlayerData):
    	var new_pos:Vector3 
    
    	var new_player = PLAYER.instantiate()
    	new_player.name = str(player_data.id)
    	new_player.player_data=player_data
    	players.add_child(new_player)
    	new_player.spawn_item_signal.connect(spawn_item_world)
    	
    	var player_count = players.get_children().size()
    	
    	if player_count > 1:
    		var last_player = players.get_children()[player_count-1]
    		new_pos = last_player.position + Vector3(3,3,3)
    		new_player.position = new_pos
    	else:
    		new_player.position = start_pos

    MultiplayerUI.tscn <- entry point for the game


    MultiplayerUI.gd

    extends Control
    @export var address = "127.0.0.1"
    @export var port = 8910
    
    var peer:ENetMultiplayerPeer
    
    @onready var host: Button = $VBoxContainer/Host
    @onready var join: Button = $VBoxContainer/Join
    @onready var start: Button = $VBoxContainer/Start
    @onready var player_name: LineEdit = $VBoxContainer/HBoxContainer/PlayerName
    
    const WORLD = preload("res://Assets/World/world.tscn")
    
    # Called when the node enters the scene tree for the first time.
    func _ready() -> 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)
    	
    	pass # Replace with function body.
    
    
    # Called every frame. 'delta' is the elapsed time since the previous frame.
    func _process(delta: float) -> void:
    	pass
    # this gets called on server and clients
    func peer_connected(id):
    	print("Player connected: ", id)
    	pass
    	
    # this gets called on server and clients	
    func peer_disconnected(id):
    	print("Player disconnected: ", id)
    	pass
    	
    # called only from clients	
    func connected_to_server():
    	print("Connected to server!")
    	send_player_info.rpc_id(1,player_name.text,multiplayer.get_unique_id())
    	pass
    
    # called only from clients		
    func connection_failed(id):
    	print("Player connection failed: ", id)
    	pass
    @rpc("any_peer")	
    func send_player_info(name, id):
    	if !GameManager.players.has(id):
    		var new_player_data:PlayerData = PlayerData.new()
    		new_player_data.id=id
    		new_player_data.player_name=name
    		new_player_data.score = 0.0
    		GameManager.players[id]=new_player_data
    		#GameManager.players[id] = {
    			#"name":name,
    			#"id":id, 
    			#"score": 0
    		#}
    	if multiplayer.is_server():
    		for i in GameManager.players:
    			send_player_info.rpc(GameManager.players[i].player_name,i)
    	pass
    	
    @rpc("any_peer","call_local")
    func start_game():
    	var scene = WORLD.instantiate()
    	get_tree().root.add_child(scene)
    	self.hide()
    	pass
    	
    func _on_host_button_down() -> void:
    	peer = ENetMultiplayerPeer.new()
    	var error = peer.create_server(port, 3)
    	if error != OK:
    		print("ERROR - Cannot host: ", error)
    		return
    	peer.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER)
    	multiplayer.set_multiplayer_peer(peer)
    	print("Waiting for players!")
    	send_player_info(player_name.text,multiplayer.get_unique_id())
    	pass # Replace with function body.
    
    
    func _on_join_button_down() -> void:
    	peer = ENetMultiplayerPeer.new()
    	peer.create_client(address,port)
    	peer.get_host().compress(ENetConnection.COMPRESS_RANGE_CODER)
    	multiplayer.set_multiplayer_peer(peer)
    	print("Waiting for players!")
    	pass # Replace with function body.
    
    
    func _on_start_button_down() -> void:
    	start_game.rpc()
    	pass # Replace with function body.

    GameManager.gd <- autoload script to keep player data global

    extends Node
    
    var players = {}
    
    func _ready() -> void:
    	pass
    	
    func _process(_delta: float) -> void:
    	
    	pass

    Tutorials that helped:

    Very interesting. Perhaps format it as a tutorial?

    2 months later

    kuligs2 Wait a second is that Hogger? Are you trying to make WoW kinda game / engine here eventually? I come from a lot of WoW dev (and more beyond): https://www.deviantart.com/withinamnesia/art/Game-Master-Veldryn-858651305 . So I have some working ARPG / multiplayer Godot 4+ working demos. Yet I'm trying to get closer to a WoW engine / Baldur's Gate / Zelda / Diablo engine that work with multiplayer and is MIT. Godot 4+ in theory should be able to make these kinds of game engines and the community can make BIG style games with them. I have a lot of experience modding Baldur's Gate and WoW but they are both proprietary game engines an I get in trouble if I work with WoW so its a struggle lol. https://github.com/WithinAmnesia/ARPG/discussions/16

    Are you trying to make an M.M.O.R.P.G. sort of engine? I have this kind of goal being worked on but I also want a 2D multiplayer engine that can work like this as well for 2D is much easy to make assets for than 3D and 8 2D games can be made for 1 3D game for MIT / CC0 open source assets of pretty good quality. + https://foozlecc.itch.io/lucifer-exterior-tileset + https://foozlecc.itch.io/lucifer-warrior + https://foozlecc.itch.io/lucifer-lava-dungeon-tileset . Currently there is a lack of a robust Godot 4+ multiplayer ARPG / MMORPG MIT license forever free open source community tool set / example / project / demonstration.

    All thoughts and suggestions are welcome. How to make all these cool multiplayer rpg / game engines eh? Bit by bit but some demos and working zone to zone travel would help lol; what should be done lol?

    Nice stuff, will try to make my borked multiplayer demo work with your improvements when I have time.

    It's really nice to see the community work together to help everyone yay teamwork makes the dreamwork. I hope I can help somehow too. Are there ant MIT github repos around to look at / bug test / tinker with for these multiplayer projects?