add multiplayer lobby system

This commit is contained in:
Spencer Killen 2024-05-25 17:15:59 -06:00
parent 3eb8b2a718
commit ef8553fe45
Signed by: sjkillen
GPG Key ID: 3AF3117BA6FBB75B
21 changed files with 322 additions and 15 deletions

15
level/PlayerSpawner.gd Normal file
View File

@ -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

5
level/level.gd Normal file
View File

@ -0,0 +1,5 @@
extends Node3D
# Called from the server
func server_add_player(id: int):
%PlayerSpawner.spawn(id)

View File

@ -1,10 +1,10 @@
[gd_scene load_steps=8 format=3 uid="uid://b00brfkibo5cj"] [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://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="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://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"] [sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_ujmev"]
margin = 2.067 margin = 2.067
@ -13,6 +13,7 @@ margin = 2.067
size = Vector3(2, 0.1, 2) size = Vector3(2, 0.1, 2)
[node name="level" instance=ExtResource("1_s37in")] [node name="level" instance=ExtResource("1_s37in")]
script = ExtResource("2_s1bx6")
[node name="WorldEnvironment" type="WorldEnvironment" parent="." index="0"] [node name="WorldEnvironment" type="WorldEnvironment" parent="." index="0"]
environment = ExtResource("2_ptkl6") 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) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
shape = SubResource("BoxShape3D_qp06x") shape = SubResource("BoxShape3D_qp06x")
[node name="Player" parent="." index="3" instance=ExtResource("2_7ct70")] [node name="Players" type="Node3D" parent="." index="3"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.36667, 2.66437, 0) unique_name_in_owner = true
floor_max_angle = 1.309
JUMP_VELOCITY = 5.0
[node name="Camera" parent="Player" index="2" instance=ExtResource("3_yev7j")] [node name="PlayerSpawner" type="MultiplayerSpawner" parent="." index="4"]
unique_name_in_owner = true
[node name="Players" type="Node3D" parent="." index="4"] _spawnable_scenes = PackedStringArray("res://player/player.tscn")
spawn_path = NodePath("../Players")
script = ExtResource("6_7ww0m")
[node name="Smoother" type="Node" parent="." index="5"] [node name="Smoother" type="Node" parent="." index="5"]
script = ExtResource("5_2tyle") script = ExtResource("5_2tyle")
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="." index="6"]
spawn_path = NodePath("../Players")

16
menu/main_menu.gd Normal file
View File

@ -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)

51
menu/main_menu.tscn Normal file
View File

@ -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"]

View File

@ -7,15 +7,14 @@
properties/0/path = NodePath(".:position") properties/0/path = NodePath(".:position")
properties/0/spawn = true properties/0/spawn = true
properties/0/replication_mode = 1 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"] [sub_resource type="SphereShape3D" id="SphereShape3D_t1htn"]
radius = 0.986757 radius = 0.986757
[node name="player" instance=ExtResource("1_0u2un")] [node name="player" instance=ExtResource("1_0u2un")]
floor_max_angle = 1.32121
script = ExtResource("1_gh340") script = ExtResource("1_gh340")
JUMP_VELOCITY = 8.0
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="0"] [node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="0"]
replication_config = SubResource("SceneReplicationConfig_u4bmc") replication_config = SubResource("SceneReplicationConfig_u4bmc")

View File

@ -29,9 +29,14 @@ DSP/dsp_buffer_count=4
[application] [application]
config/name="Grounders" config/name="Grounders"
run/main_scene="res://menu/main_menu.tscn"
config/features=PackedStringArray("4.2", "Forward Plus") config/features=PackedStringArray("4.2", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[autoload]
MultiplayerEvents="*res://server/MultiplayerEvents.gd"
[input] [input]
move_left={ move_left={

7
server/LobbyListEntry.gd Normal file
View File

@ -0,0 +1,7 @@
extends Button
@export var address: String
@export var port: int
func _ready():
text = "Connect to " + address + ":" + str(port)

View File

@ -0,0 +1,2 @@
extends Node

15
server/client.gd Normal file
View File

@ -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")

9
server/client.tscn Normal file
View File

@ -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")]

25
server/client_lobbies.gd Normal file
View File

@ -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)

View File

@ -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"]

16
server/lobby.gd Normal file
View File

@ -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)

9
server/lobby.tscn Normal file
View File

@ -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")]

17
server/lobby_list.gd Normal file
View File

@ -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()

21
server/lobby_list.tscn Normal file
View File

@ -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

View File

@ -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")

4
server/overview.txt Normal file
View File

@ -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

31
server/server.gd Normal file
View File

@ -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()

29
server/server.tscn Normal file
View File

@ -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"]