From ef8553fe451eef3687588bf1c4724ba993513bcd Mon Sep 17 00:00:00 2001 From: Spencer Killen Date: Sat, 25 May 2024 17:15:59 -0600 Subject: [PATCH] add multiplayer lobby system --- level/PlayerSpawner.gd | 15 +++++++++++ level/level.gd | 5 ++++ level/level.tscn | 22 +++++++--------- menu/main_menu.gd | 16 +++++++++++ menu/main_menu.tscn | 51 ++++++++++++++++++++++++++++++++++++ player/player.tscn | 5 ++-- project.godot | 5 ++++ server/LobbyListEntry.gd | 7 +++++ server/MultiplayerEvents.gd | 2 ++ server/client.gd | 15 +++++++++++ server/client.tscn | 9 +++++++ server/client_lobbies.gd | 25 ++++++++++++++++++ server/client_lobbies.tscn | 27 +++++++++++++++++++ server/lobby.gd | 16 +++++++++++ server/lobby.tscn | 9 +++++++ server/lobby_list.gd | 17 ++++++++++++ server/lobby_list.tscn | 21 +++++++++++++++ server/lobby_list_entry.tscn | 6 +++++ server/overview.txt | 4 +++ server/server.gd | 31 ++++++++++++++++++++++ server/server.tscn | 29 ++++++++++++++++++++ 21 files changed, 322 insertions(+), 15 deletions(-) create mode 100644 level/PlayerSpawner.gd create mode 100644 level/level.gd create mode 100644 menu/main_menu.gd create mode 100644 menu/main_menu.tscn create mode 100644 server/LobbyListEntry.gd create mode 100644 server/MultiplayerEvents.gd create mode 100644 server/client.gd create mode 100644 server/client.tscn create mode 100644 server/client_lobbies.gd create mode 100644 server/client_lobbies.tscn create mode 100644 server/lobby.gd create mode 100644 server/lobby.tscn create mode 100644 server/lobby_list.gd create mode 100644 server/lobby_list.tscn create mode 100644 server/lobby_list_entry.tscn create mode 100644 server/overview.txt create mode 100644 server/server.gd create mode 100644 server/server.tscn diff --git a/level/PlayerSpawner.gd b/level/PlayerSpawner.gd new file mode 100644 index 0000000..3d946e3 --- /dev/null +++ b/level/PlayerSpawner.gd @@ -0,0 +1,15 @@ +extends MultiplayerSpawner + +func _ready(): + set_multiplayer_authority(1) + spawn_function = spawn_player + +func spawn_player(owner_id: int): + var player := preload("res://player/player.tscn").instantiate() + player.set_multiplayer_authority(owner_id) + if multiplayer.get_unique_id() == owner_id: + var camera := preload("res://camera/camera.tscn").instantiate() + player.add_child(camera) + player.position.y = 5.0 + return player + diff --git a/level/level.gd b/level/level.gd new file mode 100644 index 0000000..3dee293 --- /dev/null +++ b/level/level.gd @@ -0,0 +1,5 @@ +extends Node3D + +# Called from the server +func server_add_player(id: int): + %PlayerSpawner.spawn(id) diff --git a/level/level.tscn b/level/level.tscn index ce74d25..013c897 100644 --- a/level/level.tscn +++ b/level/level.tscn @@ -1,10 +1,10 @@ [gd_scene load_steps=8 format=3 uid="uid://b00brfkibo5cj"] [ext_resource type="PackedScene" uid="uid://bq654gwim6col" path="res://level/level.glb" id="1_s37in"] -[ext_resource type="PackedScene" uid="uid://do25xvpy80iio" path="res://player/player.tscn" id="2_7ct70"] [ext_resource type="Environment" uid="uid://covjrwmk4rplw" path="res://level/world_environment.tres" id="2_ptkl6"] -[ext_resource type="PackedScene" uid="uid://brgqf2ebuyhuy" path="res://camera/camera.tscn" id="3_yev7j"] +[ext_resource type="Script" path="res://level/level.gd" id="2_s1bx6"] [ext_resource type="Script" path="res://addons/smoother/smoother.gd" id="5_2tyle"] +[ext_resource type="Script" path="res://level/PlayerSpawner.gd" id="6_7ww0m"] [sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_ujmev"] margin = 2.067 @@ -13,6 +13,7 @@ margin = 2.067 size = Vector3(2, 0.1, 2) [node name="level" instance=ExtResource("1_s37in")] +script = ExtResource("2_s1bx6") [node name="WorldEnvironment" type="WorldEnvironment" parent="." index="0"] environment = ExtResource("2_ptkl6") @@ -30,17 +31,14 @@ shape = SubResource("WorldBoundaryShape3D_ujmev") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0) shape = SubResource("BoxShape3D_qp06x") -[node name="Player" parent="." index="3" instance=ExtResource("2_7ct70")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.36667, 2.66437, 0) -floor_max_angle = 1.309 -JUMP_VELOCITY = 5.0 +[node name="Players" type="Node3D" parent="." index="3"] +unique_name_in_owner = true -[node name="Camera" parent="Player" index="2" instance=ExtResource("3_yev7j")] - -[node name="Players" type="Node3D" parent="." index="4"] +[node name="PlayerSpawner" type="MultiplayerSpawner" parent="." index="4"] +unique_name_in_owner = true +_spawnable_scenes = PackedStringArray("res://player/player.tscn") +spawn_path = NodePath("../Players") +script = ExtResource("6_7ww0m") [node name="Smoother" type="Node" parent="." index="5"] script = ExtResource("5_2tyle") - -[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="." index="6"] -spawn_path = NodePath("../Players") diff --git a/menu/main_menu.gd b/menu/main_menu.gd new file mode 100644 index 0000000..8dd713d --- /dev/null +++ b/menu/main_menu.gd @@ -0,0 +1,16 @@ +extends Control + + +func _on_create_server_pressed(): + get_tree().change_scene_to_file("res://server/server.tscn") + + +func _on_connect_to_server_pressed(): + var address: String = %ConnectIP.text + var port := int(%ConnectPort.text) + var parent := get_parent() + queue_free() + var scene := preload("res://server/client_lobbies.tscn").instantiate() + scene.address = address + scene.port = port + parent.add_child(scene) diff --git a/menu/main_menu.tscn b/menu/main_menu.tscn new file mode 100644 index 0000000..5d2a4c6 --- /dev/null +++ b/menu/main_menu.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=2 format=3 uid="uid://b4tgnq0dwf13"] + +[ext_resource type="Script" path="res://menu/main_menu.gd" id="1_q1k1j"] + +[node name="MainMenu" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_q1k1j") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="CreateServer" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "Start Server" + +[node name="Label" type="Label" parent="VBoxContainer"] +layout_mode = 2 +text = "Server IP" + +[node name="ConnectIP" type="TextEdit" parent="VBoxContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 61.195) +layout_mode = 2 +text = "127.0.0.1" + +[node name="Label2" type="Label" parent="VBoxContainer"] +layout_mode = 2 +text = "Server Port" + +[node name="ConnectPort" type="TextEdit" parent="VBoxContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 61.195) +layout_mode = 2 +text = "3000" + +[node name="ConnectToServer" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "Connect To Server" + +[connection signal="pressed" from="VBoxContainer/CreateServer" to="." method="_on_create_server_pressed"] +[connection signal="pressed" from="VBoxContainer/ConnectToServer" to="." method="_on_connect_to_server_pressed"] diff --git a/player/player.tscn b/player/player.tscn index 16af845..42d7e11 100644 --- a/player/player.tscn +++ b/player/player.tscn @@ -7,15 +7,14 @@ properties/0/path = NodePath(".:position") properties/0/spawn = true properties/0/replication_mode = 1 -properties/1/path = NodePath("player:velocity") -properties/1/spawn = true -properties/1/replication_mode = 1 [sub_resource type="SphereShape3D" id="SphereShape3D_t1htn"] radius = 0.986757 [node name="player" instance=ExtResource("1_0u2un")] +floor_max_angle = 1.32121 script = ExtResource("1_gh340") +JUMP_VELOCITY = 8.0 [node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="0"] replication_config = SubResource("SceneReplicationConfig_u4bmc") diff --git a/project.godot b/project.godot index b1449b2..c0e01f0 100644 --- a/project.godot +++ b/project.godot @@ -29,9 +29,14 @@ DSP/dsp_buffer_count=4 [application] config/name="Grounders" +run/main_scene="res://menu/main_menu.tscn" config/features=PackedStringArray("4.2", "Forward Plus") config/icon="res://icon.svg" +[autoload] + +MultiplayerEvents="*res://server/MultiplayerEvents.gd" + [input] move_left={ diff --git a/server/LobbyListEntry.gd b/server/LobbyListEntry.gd new file mode 100644 index 0000000..d076d30 --- /dev/null +++ b/server/LobbyListEntry.gd @@ -0,0 +1,7 @@ +extends Button + +@export var address: String +@export var port: int + +func _ready(): + text = "Connect to " + address + ":" + str(port) diff --git a/server/MultiplayerEvents.gd b/server/MultiplayerEvents.gd new file mode 100644 index 0000000..487b284 --- /dev/null +++ b/server/MultiplayerEvents.gd @@ -0,0 +1,2 @@ +extends Node + diff --git a/server/client.gd b/server/client.gd new file mode 100644 index 0000000..3541273 --- /dev/null +++ b/server/client.gd @@ -0,0 +1,15 @@ +extends Node + +@export var address: String +@export var port: int + +func _ready(): + var m = SceneMultiplayer.new() + get_tree().set_multiplayer(m, get_path()) + var peer := ENetMultiplayerPeer.new() + peer.create_client(address, port) + m.multiplayer_peer = peer + m.connected_to_server.connect(connected_to_server) + +func connected_to_server(): + print("Client connected to server") diff --git a/server/client.tscn b/server/client.tscn new file mode 100644 index 0000000..3590adf --- /dev/null +++ b/server/client.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=3 format=3 uid="uid://cb5t0uw0nr5bg"] + +[ext_resource type="Script" path="res://server/client.gd" id="1_frcvp"] +[ext_resource type="PackedScene" uid="uid://b00brfkibo5cj" path="res://level/level.tscn" id="2_eigcb"] + +[node name="Client" type="Node"] +script = ExtResource("1_frcvp") + +[node name="level" parent="." instance=ExtResource("2_eigcb")] diff --git a/server/client_lobbies.gd b/server/client_lobbies.gd new file mode 100644 index 0000000..0f1ae5a --- /dev/null +++ b/server/client_lobbies.gd @@ -0,0 +1,25 @@ +extends Control + +var address: String +var port: int + +func _ready(): + var m = SceneMultiplayer.new() + get_tree().set_multiplayer(m, %LobbyList.get_path()) + var peer := ENetMultiplayerPeer.new() + var error := peer.create_client(address, port) + if error: + push_error(error) + m.multiplayer_peer = peer + m.connected_to_server.connect(connected_to_server) + %LobbyList.set_multiplayer_authority(1) + +func connected_to_server(): + print("Connected to master server @ " + address + ":" + str(port)) + +func _on_lobby_list_join_lobby(join_addr, join_port): + queue_free() + var client := preload("res://server/client.tscn").instantiate() + client.address = join_addr + client.port = join_port + get_parent().add_child(client) diff --git a/server/client_lobbies.tscn b/server/client_lobbies.tscn new file mode 100644 index 0000000..47d0950 --- /dev/null +++ b/server/client_lobbies.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=3 format=3 uid="uid://d3i1pstq13h2p"] + +[ext_resource type="Script" path="res://server/client_lobbies.gd" id="1_2hyqo"] +[ext_resource type="PackedScene" uid="uid://bdfsbuxqrkq68" path="res://server/lobby_list.tscn" id="2_rktrq"] + +[node name="Server" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_2hyqo") + +[node name="Label" type="Label" parent="."] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 23.0 +text = "Lobby List:" + +[node name="LobbyList" parent="." instance=ExtResource("2_rktrq")] +unique_name_in_owner = true +layout_mode = 1 + +[connection signal="join_lobby" from="LobbyList" to="." method="_on_lobby_list_join_lobby"] + +[editable path="LobbyList"] diff --git a/server/lobby.gd b/server/lobby.gd new file mode 100644 index 0000000..4a9e8d9 --- /dev/null +++ b/server/lobby.gd @@ -0,0 +1,16 @@ +extends Node + +var port: int +var MAX_CLIENTS := 32 + +func _ready(): + var m = SceneMultiplayer.new() + get_tree().set_multiplayer(m, get_path()) + var peer := ENetMultiplayerPeer.new() + peer.create_server(port, MAX_CLIENTS) + m.multiplayer_peer = peer + m.peer_connected.connect(peer_connected) + +func peer_connected(id: int): + print("Client " + str(id) + "connected to lobby " + str(get_path())) + $level.server_add_player(id) diff --git a/server/lobby.tscn b/server/lobby.tscn new file mode 100644 index 0000000..4d2b32d --- /dev/null +++ b/server/lobby.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=3 format=3 uid="uid://tilcdm56kb10"] + +[ext_resource type="Script" path="res://server/lobby.gd" id="1_cpwsy"] +[ext_resource type="PackedScene" uid="uid://b00brfkibo5cj" path="res://level/level.tscn" id="2_mulf5"] + +[node name="Lobby" type="Node"] +script = ExtResource("1_cpwsy") + +[node name="level" parent="." instance=ExtResource("2_mulf5")] diff --git a/server/lobby_list.gd b/server/lobby_list.gd new file mode 100644 index 0000000..0155a9b --- /dev/null +++ b/server/lobby_list.gd @@ -0,0 +1,17 @@ +extends Control + +signal join_lobby(addr: String, port: int) + +@rpc("reliable", "authority", "call_local") +func add(addr: String, port: int): + var lobby_button := preload("res://server/lobby_list_entry.tscn").instantiate() + lobby_button.address = addr + lobby_button.port = port + $Lobbies.add_child(lobby_button, true) + lobby_button.pressed.connect(emit_join_lobby.bind(addr, port)) + +func emit_join_lobby(addr: String, port: int): + join_lobby.emit(addr, port) + +func lobbies(): + return $Lobbies.get_children() diff --git a/server/lobby_list.tscn b/server/lobby_list.tscn new file mode 100644 index 0000000..e0136b3 --- /dev/null +++ b/server/lobby_list.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=2 format=3 uid="uid://bdfsbuxqrkq68"] + +[ext_resource type="Script" path="res://server/lobby_list.gd" id="1_tktrj"] + +[node name="LobbyList" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 67.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_tktrj") + +[node name="Lobbies" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 diff --git a/server/lobby_list_entry.tscn b/server/lobby_list_entry.tscn new file mode 100644 index 0000000..1f405de --- /dev/null +++ b/server/lobby_list_entry.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://mhnsc0w6xt85"] + +[ext_resource type="Script" path="res://server/LobbyListEntry.gd" id="1_su4dr"] + +[node name="LobbyListEntry" type="Button"] +script = ExtResource("1_su4dr") diff --git a/server/overview.txt b/server/overview.txt new file mode 100644 index 0000000..ec2c963 --- /dev/null +++ b/server/overview.txt @@ -0,0 +1,4 @@ +Two types of servers / clients +One for lobby management and one for actual game +server.gd and client_lobbies.gd handle the former +and lobby.gd and client.gd handle the latter diff --git a/server/server.gd b/server/server.gd new file mode 100644 index 0000000..7bfedef --- /dev/null +++ b/server/server.gd @@ -0,0 +1,31 @@ +extends Node + +@export var external_address := "127.0.0.1" +var master_server_port := 3000 +var next_port_num := master_server_port + 1 +const MAX_CLIENTS = 4095 + +func _ready(): + var m = SceneMultiplayer.new() + get_tree().set_multiplayer(m, %LobbyList.get_path()) + var peer := ENetMultiplayerPeer.new() + var error := peer.create_server(master_server_port, MAX_CLIENTS) + if error: + push_error(error) + m.multiplayer_peer = peer + m.peer_connected.connect(peer_connected) + +func peer_connected(id: int): + print("Client " + str(id) + " connected to master server") + for lobby in %LobbyList.lobbies(): + %LobbyList.add.rpc_id(id, lobby.address, lobby.port) + +func create_lobby(): + var lobby := preload("res://server/lobby.tscn").instantiate() + lobby.port = next_port_num + next_port_num += 1 + %Lobbies.add_child(lobby) + %LobbyList.add.rpc(external_address, lobby.port) + +func _on_create_lobby_pressed(): + create_lobby() diff --git a/server/server.tscn b/server/server.tscn new file mode 100644 index 0000000..5327c83 --- /dev/null +++ b/server/server.tscn @@ -0,0 +1,29 @@ +[gd_scene load_steps=3 format=3 uid="uid://cb6qjcj72sfe2"] + +[ext_resource type="Script" path="res://server/server.gd" id="1_0oac5"] +[ext_resource type="PackedScene" uid="uid://bdfsbuxqrkq68" path="res://server/lobby_list.tscn" id="2_mxbok"] + +[node name="Server" type="Node"] +script = ExtResource("1_0oac5") + +[node name="DebugControls" type="Control" parent="."] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="CreateLobby" type="Button" parent="DebugControls"] +layout_mode = 0 +offset_right = 8.0 +offset_bottom = 8.0 +text = "Create Lobby" + +[node name="LobbyList" parent="." instance=ExtResource("2_mxbok")] +unique_name_in_owner = true + +[node name="Lobbies" type="Node" parent="."] +unique_name_in_owner = true + +[connection signal="pressed" from="DebugControls/CreateLobby" to="." method="_on_create_lobby_pressed"]