First working multiplayer implementation #5
@@ -15,6 +15,10 @@ run/main_scene="res://scenes/utils/Menu.tscn"
 | 
			
		||||
config/features=PackedStringArray("4.3", "Forward Plus")
 | 
			
		||||
config/icon="res://icon.svg"
 | 
			
		||||
 | 
			
		||||
[autoload]
 | 
			
		||||
 | 
			
		||||
GameServerManager="*res://scripts/game_server_manager.gd"
 | 
			
		||||
 | 
			
		||||
[display]
 | 
			
		||||
 | 
			
		||||
window/size/viewport_width=1920
 | 
			
		||||
 
 | 
			
		||||
@@ -6,16 +6,33 @@ var player_side: String
 | 
			
		||||
@onready var intro_view_port = $Intro/CameraMount/IntroCamera/SubViewportContainer/SubViewport
 | 
			
		||||
@onready var spawns = $Spawns
 | 
			
		||||
@onready var root = $'.' 
 | 
			
		||||
@onready var players = $Players
 | 
			
		||||
# Called when the node enters the scene tree for the first time.
 | 
			
		||||
func _ready() -> void:
 | 
			
		||||
	var char : Node3D = null
 | 
			
		||||
	var red_spawn: Node3D = $Spawns/Blue/SpawnArea
 | 
			
		||||
	var position := red_spawn.global_position
 | 
			
		||||
	char = ResourceLoader.load("res://scenes/utils/character.tscn").instantiate()
 | 
			
		||||
	char.global_position = position
 | 
			
		||||
	root.add_child(char)
 | 
			
		||||
	pass
 | 
			
		||||
	if multiplayer.is_server():
 | 
			
		||||
		for i in GameServerManager.players:
 | 
			
		||||
			char = ResourceLoader.load("res://scenes/utils/character.tscn").instantiate()
 | 
			
		||||
			char.name = str(GameServerManager.players[i].name)
 | 
			
		||||
			char.global_position = position
 | 
			
		||||
			var my_random_number = RandomNumberGenerator.new().randf_range(-2.0, 2.0)
 | 
			
		||||
			char.global_position = position
 | 
			
		||||
			char.global_position.x += my_random_number
 | 
			
		||||
			$MultiplayerSpawner.spawn(char)
 | 
			
		||||
			players.add_child(char)
 | 
			
		||||
 | 
			
		||||
func spawn_player(id: int):
 | 
			
		||||
	var red_spawn: Node3D = $Spawns/Blue/SpawnArea
 | 
			
		||||
	var position := red_spawn.global_position
 | 
			
		||||
	var char = ResourceLoader.load("res://scenes/utils/character.tscn").instantiate()
 | 
			
		||||
	char.name = str(GameServerManager.players[id].name)
 | 
			
		||||
	char.global_position = position
 | 
			
		||||
	var my_random_number = RandomNumberGenerator.new().randf_range(-2.0, 2.0)
 | 
			
		||||
	char.global_position = position
 | 
			
		||||
	char.global_position.x += my_random_number
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
 | 
			
		||||
func _process(delta: float) -> void:
 | 
			
		||||
 
 | 
			
		||||
@@ -24,3 +24,10 @@ size = Vector3(0.100647, 1, 6.02112)
 | 
			
		||||
[node name="SpawnArea" type="CSGBox3D" parent="Spawns/Blue"]
 | 
			
		||||
transform = Transform3D(5.70162, 0, 0, 0, 1, 0, 0, 0, 7.97817, 21.2099, 1.78438, 1.23551)
 | 
			
		||||
size = Vector3(0.484497, 1, 5.99213)
 | 
			
		||||
 | 
			
		||||
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="."]
 | 
			
		||||
_spawnable_scenes = PackedStringArray("res://scenes/utils/character.tscn")
 | 
			
		||||
spawn_path = NodePath("../Players")
 | 
			
		||||
spawn_limit = 4
 | 
			
		||||
 | 
			
		||||
[node name="Players" type="Node3D" parent="."]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
[gd_scene load_steps=6 format=3 uid="uid://ccm77j5rkh21w"]
 | 
			
		||||
[gd_scene load_steps=7 format=3 uid="uid://ccm77j5rkh21w"]
 | 
			
		||||
 | 
			
		||||
[ext_resource type="Script" path="res://scripts/character_controller.gd" id="1_sue4n"]
 | 
			
		||||
[ext_resource type="PackedScene" uid="uid://bmqutwuj28san" path="res://scenes/utils/view_model_camera.tscn" id="4_al83x"]
 | 
			
		||||
@@ -9,6 +9,14 @@
 | 
			
		||||
radius = 0.368364
 | 
			
		||||
height = 1.8
 | 
			
		||||
 | 
			
		||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_csl3n"]
 | 
			
		||||
properties/0/path = NodePath(".:position")
 | 
			
		||||
properties/0/spawn = true
 | 
			
		||||
properties/0/replication_mode = 1
 | 
			
		||||
properties/1/path = NodePath("CharacterBody3D:position")
 | 
			
		||||
properties/1/spawn = true
 | 
			
		||||
properties/1/replication_mode = 1
 | 
			
		||||
 | 
			
		||||
[node name="Character" type="Node3D"]
 | 
			
		||||
 | 
			
		||||
[node name="CharacterBody3D" type="CharacterBody3D" parent="."]
 | 
			
		||||
@@ -23,10 +31,12 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.71312, 0)
 | 
			
		||||
radius = 0.001
 | 
			
		||||
 | 
			
		||||
[node name="ViewModelCamera" parent="CharacterBody3D/UpperTorso" instance=ExtResource("4_al83x")]
 | 
			
		||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00122696, 0.093623, -0.179357)
 | 
			
		||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00122696, 0.093623, -0.463804)
 | 
			
		||||
script = ExtResource("4_uwcjh")
 | 
			
		||||
 | 
			
		||||
[node name="Pistol" parent="CharacterBody3D/UpperTorso/ViewModelCamera" instance=ExtResource("5_6k7rq")]
 | 
			
		||||
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, -0.0287516, -0.136104, -0.276055)
 | 
			
		||||
visible = false
 | 
			
		||||
script = null
 | 
			
		||||
 | 
			
		||||
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
 | 
			
		||||
replication_config = SubResource("SceneReplicationConfig_csl3n")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,8 @@
 | 
			
		||||
extends Control
 | 
			
		||||
 | 
			
		||||
# Autoload named Lobby
 | 
			
		||||
 | 
			
		||||
# Called when the node enters the scene tree for the first time.
 | 
			
		||||
func _ready() -> void:
 | 
			
		||||
	multiplayer.peer_connected.connect(_on_player_connected)
 | 
			
		||||
	multiplayer.peer_disconnected.connect(_on_player_disconnected)
 | 
			
		||||
	multiplayer.connected_to_server.connect(_on_connected_ok)
 | 
			
		||||
	multiplayer.connection_failed.connect(_on_connected_fail)
 | 
			
		||||
	multiplayer.server_disconnected.connect(_on_server_disconnected)
 | 
			
		||||
	pass # Replace with function body.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
 | 
			
		||||
func _process(delta: float) -> void:
 | 
			
		||||
	pass
 | 
			
		||||
 | 
			
		||||
var peer = ENetMultiplayerPeer.new()
 | 
			
		||||
 | 
			
		||||
# These signals can be connected to by a UI lobby scene or the game scene.
 | 
			
		||||
signal player_connected(peer_id, player_info)
 | 
			
		||||
signal player_disconnected(peer_id)
 | 
			
		||||
signal server_disconnected
 | 
			
		||||
@@ -25,20 +11,37 @@ const PORT = 7000
 | 
			
		||||
const DEFAULT_SERVER_IP = "127.0.0.1" # IPv4 localhost
 | 
			
		||||
const MAX_CONNECTIONS = 20
 | 
			
		||||
 | 
			
		||||
var players = {}
 | 
			
		||||
var my_random_number = RandomNumberGenerator.new().randf_range(-2.0, 2.0)
 | 
			
		||||
var player_info = {"name": str(my_random_number)}
 | 
			
		||||
var players_loaded = 0
 | 
			
		||||
 | 
			
		||||
var player_info = {"name": "Name"}
 | 
			
		||||
var players = {}
 | 
			
		||||
# Called when the node enters the scene tree for the first time.
 | 
			
		||||
func _ready() -> void:
 | 
			
		||||
	multiplayer.peer_connected.connect(_on_player_connected)
 | 
			
		||||
	multiplayer.peer_disconnected.connect(_on_player_disconnected)
 | 
			
		||||
	multiplayer.connected_to_server.connect(_on_connected_ok)
 | 
			
		||||
	multiplayer.connection_failed.connect(_on_connected_fail)
 | 
			
		||||
	multiplayer.server_disconnected.connect(_on_server_disconnected)
 | 
			
		||||
 | 
			
		||||
func _on_host_pressed() -> void:
 | 
			
		||||
func join_game(address = ""):
 | 
			
		||||
	if address.is_empty():
 | 
			
		||||
		address = DEFAULT_SERVER_IP
 | 
			
		||||
	var peer = ENetMultiplayerPeer.new()
 | 
			
		||||
	var error = peer.create_client(address, PORT)
 | 
			
		||||
	if error:
 | 
			
		||||
		return error
 | 
			
		||||
	multiplayer.multiplayer_peer = peer
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
func create_game():
 | 
			
		||||
	var peer = ENetMultiplayerPeer.new()
 | 
			
		||||
	var error = peer.create_server(PORT, MAX_CONNECTIONS)
 | 
			
		||||
	if error:
 | 
			
		||||
		print(error)
 | 
			
		||||
		return error
 | 
			
		||||
	multiplayer.multiplayer_peer = peer
 | 
			
		||||
 | 
			
		||||
	players[1] = player_info
 | 
			
		||||
	player_connected.emit(1, player_info)
 | 
			
		||||
	player_connected.emit(peer.get_unique_id(), player_info)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func remove_multiplayer_peer():
 | 
			
		||||
@@ -47,7 +50,7 @@ func remove_multiplayer_peer():
 | 
			
		||||
# When the server decides to start the game from a UI scene,
 | 
			
		||||
# do Lobby.load_game.rpc(filepath)
 | 
			
		||||
@rpc("call_local", "reliable")
 | 
			
		||||
func load_game(game_scene_path):
 | 
			
		||||
func load_game():
 | 
			
		||||
	get_tree().change_scene_to_file("res://scenes/maps/el_test.tscn")
 | 
			
		||||
 | 
			
		||||
# Every peer will call this when they have loaded the game scene.
 | 
			
		||||
@@ -55,36 +58,35 @@ func load_game(game_scene_path):
 | 
			
		||||
func player_loaded():
 | 
			
		||||
	if multiplayer.is_server():
 | 
			
		||||
		players_loaded += 1
 | 
			
		||||
		players_loaded = players.size()
 | 
			
		||||
		if players_loaded == players.size():
 | 
			
		||||
			
 | 
			
		||||
			#$/root/Game.start_game()
 | 
			
		||||
			players_loaded = 0
 | 
			
		||||
 | 
			
		||||
func _on_join_pressed() -> void:
 | 
			
		||||
	var address = '127.0.0.1'
 | 
			
		||||
	var peer = ENetMultiplayerPeer.new()
 | 
			
		||||
	var error = peer.create_client(address, PORT)
 | 
			
		||||
	if error:
 | 
			
		||||
		print(error)
 | 
			
		||||
	multiplayer.multiplayer_peer = peer
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_player_connected(id: Variant, player_info: Variant) -> void:
 | 
			
		||||
	print(player_info)
 | 
			
		||||
	_register_player.rpc_id(id, player_info)
 | 
			
		||||
			
 | 
			
		||||
# When a peer connects, send them my player info.
 | 
			
		||||
# This allows transfer of all desired data for each player, not only the unique ID.
 | 
			
		||||
func _on_player_connected(id):
 | 
			
		||||
	player_info = {"name": multiplayer.get_unique_id()}
 | 
			
		||||
	_register_player.rpc_id(1, multiplayer.get_unique_id(), player_info)
 | 
			
		||||
	
 | 
			
		||||
@rpc("any_peer", "reliable")
 | 
			
		||||
func _register_player(new_player_info):
 | 
			
		||||
	var new_player_id = multiplayer.get_remote_sender_id()
 | 
			
		||||
	players[new_player_id] = new_player_info
 | 
			
		||||
	player_connected.emit(new_player_id, new_player_info)
 | 
			
		||||
func _register_player(id: int, new_player_info):
 | 
			
		||||
	var new_player_id: int = 1
 | 
			
		||||
	if not multiplayer.is_server():
 | 
			
		||||
		new_player_id = 1
 | 
			
		||||
	else:
 | 
			
		||||
		new_player_id = multiplayer.get_remote_sender_id()
 | 
			
		||||
	players[id] = new_player_info
 | 
			
		||||
	GameServerManager.players[id] = new_player_info
 | 
			
		||||
	#player_info = {"name": str(multiplayer.get)}
 | 
			
		||||
	player_connected.emit(id, new_player_info)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_player_disconnected(id: Variant) -> void:
 | 
			
		||||
	pass
 | 
			
		||||
	
 | 
			
		||||
func _on_player_disconnected(id):
 | 
			
		||||
	players.erase(id)
 | 
			
		||||
	player_disconnected.emit(id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_connected_ok():
 | 
			
		||||
	var peer_id = multiplayer.get_unique_id()
 | 
			
		||||
	players[peer_id] = player_info
 | 
			
		||||
@@ -93,17 +95,23 @@ func _on_connected_ok():
 | 
			
		||||
 | 
			
		||||
func _on_connected_fail():
 | 
			
		||||
	multiplayer.multiplayer_peer = null
 | 
			
		||||
	
 | 
			
		||||
func _on_server_disconnected() -> void:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _on_server_disconnected():
 | 
			
		||||
	multiplayer.multiplayer_peer = null
 | 
			
		||||
	players.clear()
 | 
			
		||||
	server_disconnected.emit()
 | 
			
		||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
 | 
			
		||||
 | 
			
		||||
func _on_host_pressed() -> void:
 | 
			
		||||
	player_info = {"name":  str(1)}
 | 
			
		||||
	_register_player(1, player_info)
 | 
			
		||||
	create_game()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@rpc("call_local", "reliable", "any_peer")
 | 
			
		||||
func start_game():
 | 
			
		||||
	get_tree().change_scene_to_file("res://scenes/maps/el_test.tscn")
 | 
			
		||||
func _on_join_pressed() -> void:
 | 
			
		||||
	join_game()
 | 
			
		||||
 | 
			
		||||
func _on_start_pressed() -> void:
 | 
			
		||||
	start_game.rpc()
 | 
			
		||||
	load_game.rpc()
 | 
			
		||||
func _process(delta: float) -> void:
 | 
			
		||||
	pass
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,8 @@ surface_material_override/0 = SubResource("StandardMaterial3D_p8o05")
 | 
			
		||||
[node name="RayCast3D" type="RayCast3D" parent="."]
 | 
			
		||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.100459)
 | 
			
		||||
target_position = Vector3(0, 0, -0.5)
 | 
			
		||||
hit_back_faces = false
 | 
			
		||||
collide_with_areas = true
 | 
			
		||||
 | 
			
		||||
[node name="GPUParticles3D" type="GPUParticles3D" parent="."]
 | 
			
		||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.18476)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,6 @@ class_name Player extends CharacterBody3D
 | 
			
		||||
@onready var body: Node3D = $RealBody
 | 
			
		||||
var jumping: bool = false
 | 
			
		||||
var mouse_captured: bool = false
 | 
			
		||||
 | 
			
		||||
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
 | 
			
		||||
 | 
			
		||||
var move_dir: Vector2 # Input direction for movement
 | 
			
		||||
@@ -32,7 +31,11 @@ var is_crouch: bool = false
 | 
			
		||||
@onready var character: Node3D = $"."
 | 
			
		||||
 | 
			
		||||
func _ready() -> void:
 | 
			
		||||
	$"../MultiplayerSynchronizer".set_multiplayer_authority(str($"..".name).to_int())
 | 
			
		||||
	enable_camera()
 | 
			
		||||
	capture_mouse()
 | 
			
		||||
	#print("I am " + str(multiplayer.get_unique_id()) + "I'm controling " + str($"../MultiplayerSynchronizer".get_multiplayer_authority()))
 | 
			
		||||
 | 
			
		||||
func _unhandled_input(event: InputEvent) -> void:
 | 
			
		||||
	if event is InputEventMouseMotion:
 | 
			
		||||
		look_dir = event.relative * 0.001
 | 
			
		||||
@@ -44,12 +47,15 @@ func _unhandled_input(event: InputEvent) -> void:
 | 
			
		||||
	if Input.is_action_just_pressed("crouch"): crouch()
 | 
			
		||||
	if Input.is_action_just_released("crouch"): uncrouch()
 | 
			
		||||
	
 | 
			
		||||
func enable_camera():
 | 
			
		||||
	if str($"..".name).to_int() == multiplayer.get_unique_id():
 | 
			
		||||
		$UpperTorso/ViewModelCamera.make_current()
 | 
			
		||||
 | 
			
		||||
func _physics_process(delta: float) -> void:
 | 
			
		||||
	if mouse_captured: _handle_joypad_camera_rotation(delta)
 | 
			
		||||
	velocity = _walk(delta) + _gravity(delta) + _jump(delta)
 | 
			
		||||
	
 | 
			
		||||
	move_and_slide()
 | 
			
		||||
	if str($"..".name).to_int() == multiplayer.get_unique_id():
 | 
			
		||||
		if mouse_captured: _handle_joypad_camera_rotation(delta)
 | 
			
		||||
		velocity = _walk(delta) + _gravity(delta) + _jump(delta)
 | 
			
		||||
		move_and_slide()
 | 
			
		||||
 | 
			
		||||
func capture_mouse() -> void:
 | 
			
		||||
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								scripts/game_server_manager.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								scripts/game_server_manager.gd
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
extends Node
 | 
			
		||||
 | 
			
		||||
var players = {}
 | 
			
		||||
# Called when the node enters the scene tree for the first time.
 | 
			
		||||
func _ready() -> void:
 | 
			
		||||
	pass # Replace with function body.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
 | 
			
		||||
func _process(delta: float) -> void:
 | 
			
		||||
	pass
 | 
			
		||||
		Reference in New Issue
	
	Block a user