test implement webrtc
This commit is contained in:
@@ -122,6 +122,28 @@ theme = SubResource("Theme_standard_font")
|
|||||||
[node name="HBoxContainer" type="HBoxContainer" parent="UpperRight" unique_id=332290975]
|
[node name="HBoxContainer" type="HBoxContainer" parent="UpperRight" unique_id=332290975]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="VBoxContainerHost" type="VBoxContainer" parent="UpperRight/HBoxContainer" unique_id=1933444958]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="LabelHost" type="Label" parent="UpperRight/HBoxContainer/VBoxContainerHost" unique_id=1807484687]
|
||||||
|
layout_mode = 2
|
||||||
|
theme = SubResource("Theme_standard_font")
|
||||||
|
text = "HOST"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="LabelPlayerCount" type="Label" parent="UpperRight/HBoxContainer/VBoxContainerHost" unique_id=1807484688]
|
||||||
|
layout_mode = 2
|
||||||
|
theme = SubResource("Theme_standard_font")
|
||||||
|
text = "Players: 1"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="LabelRoomCode" type="Label" parent="UpperRight/HBoxContainer/VBoxContainerHost" unique_id=1807484689]
|
||||||
|
layout_mode = 2
|
||||||
|
theme = SubResource("Theme_standard_font")
|
||||||
|
text = ""
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="VBoxContainerBoss" type="VBoxContainer" parent="UpperRight/HBoxContainer" unique_id=1933444957]
|
[node name="VBoxContainerBoss" type="VBoxContainer" parent="UpperRight/HBoxContainer" unique_id=1933444957]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
[gd_scene format=3 uid="uid://d4fgxay8kqp5u"]
|
[gd_scene format=3 uid="uid://d4fgxay8kqp5u"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://scripts/game_ui.gd" id="1"]
|
[ext_resource type="Script" uid="uid://ofhtysy8r43v" path="res://scripts/game_ui.gd" id="1"]
|
||||||
|
|
||||||
[node name="GameUI" type="CanvasLayer"]
|
[node name="GameUI" type="CanvasLayer" unique_id=2051025577]
|
||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
|
|
||||||
[node name="Control" type="Control" parent="."]
|
[node name="Control" type="Control" parent="." unique_id=1160035312]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
@@ -13,7 +13,7 @@ anchor_bottom = 1.0
|
|||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
|
|
||||||
[node name="MainMenu" type="Panel" parent="Control"]
|
[node name="MainMenu" type="Panel" parent="Control" unique_id=1231078062]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 8
|
anchors_preset = 8
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
@@ -27,7 +27,7 @@ offset_bottom = 250.0
|
|||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="Control/MainMenu"]
|
[node name="VBoxContainer" type="VBoxContainer" parent="Control/MainMenu" unique_id=1287690408]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 8
|
anchors_preset = 8
|
||||||
anchor_left = 0.5
|
anchor_left = 0.5
|
||||||
@@ -42,65 +42,85 @@ grow_horizontal = 2
|
|||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme_override_constants/separation = 20
|
theme_override_constants/separation = 20
|
||||||
|
|
||||||
[node name="Title" type="Label" parent="Control/MainMenu/VBoxContainer"]
|
[node name="Title" type="Label" parent="Control/MainMenu/VBoxContainer" unique_id=1219314113]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Multiplayer Coop RPG"
|
text = "Multiplayer Coop RPG"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
vertical_alignment = 1
|
vertical_alignment = 1
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
|
|
||||||
[node name="Spacer1" type="Control" parent="Control/MainMenu/VBoxContainer"]
|
[node name="Spacer1" type="Control" parent="Control/MainMenu/VBoxContainer" unique_id=1826786842]
|
||||||
custom_minimum_size = Vector2(0, 20)
|
custom_minimum_size = Vector2(0, 20)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="LocalPlayersContainer" type="HBoxContainer" parent="Control/MainMenu/VBoxContainer"]
|
[node name="NetworkModeContainer" type="HBoxContainer" parent="Control/MainMenu/VBoxContainer" unique_id=1234567890]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="Control/MainMenu/VBoxContainer/LocalPlayersContainer"]
|
[node name="Label" type="Label" parent="Control/MainMenu/VBoxContainer/NetworkModeContainer" unique_id=1234567891]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
text = "Network Mode:"
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
|
[node name="NetworkModeOption" type="OptionButton" parent="Control/MainMenu/VBoxContainer/NetworkModeContainer" unique_id=1234567892]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
selected = 0
|
||||||
|
item_count = 3
|
||||||
|
popup/item_0/text = "ENet (PC/LAN)"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "WebRTC (Browser/Web)"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "WebSocket (Cross-Platform)"
|
||||||
|
popup/item_2/id = 2
|
||||||
|
|
||||||
|
[node name="LocalPlayersContainer" type="HBoxContainer" parent="Control/MainMenu/VBoxContainer" unique_id=1860926318]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Control/MainMenu/VBoxContainer/LocalPlayersContainer" unique_id=1617550731]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
text = "Local Players:"
|
text = "Local Players:"
|
||||||
vertical_alignment = 1
|
vertical_alignment = 1
|
||||||
|
|
||||||
[node name="SpinBox" type="SpinBox" parent="Control/MainMenu/VBoxContainer/LocalPlayersContainer"]
|
[node name="SpinBox" type="SpinBox" parent="Control/MainMenu/VBoxContainer/LocalPlayersContainer" unique_id=1805678257]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
min_value = 1.0
|
min_value = 1.0
|
||||||
max_value = 4.0
|
max_value = 4.0
|
||||||
value = 1.0
|
value = 1.0
|
||||||
|
|
||||||
[node name="AddressContainer" type="HBoxContainer" parent="Control/MainMenu/VBoxContainer"]
|
[node name="AddressContainer" type="HBoxContainer" parent="Control/MainMenu/VBoxContainer" unique_id=69104887]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="Control/MainMenu/VBoxContainer/AddressContainer"]
|
[node name="Label" type="Label" parent="Control/MainMenu/VBoxContainer/AddressContainer" unique_id=1727520435]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
text = "Server Address:"
|
text = "Server Address:"
|
||||||
vertical_alignment = 1
|
vertical_alignment = 1
|
||||||
|
|
||||||
[node name="AddressInput" type="LineEdit" parent="Control/MainMenu/VBoxContainer/AddressContainer"]
|
[node name="AddressInput" type="LineEdit" parent="Control/MainMenu/VBoxContainer/AddressContainer" unique_id=314768458]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
placeholder_text = "127.0.0.1"
|
placeholder_text = "ruinborn.thefirstboss.com"
|
||||||
expand_to_text_length = true
|
|
||||||
|
|
||||||
[node name="Spacer2" type="Control" parent="Control/MainMenu/VBoxContainer"]
|
[node name="Spacer2" type="Control" parent="Control/MainMenu/VBoxContainer" unique_id=1061067008]
|
||||||
custom_minimum_size = Vector2(0, 20)
|
custom_minimum_size = Vector2(0, 20)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="HostButton" type="Button" parent="Control/MainMenu/VBoxContainer"]
|
[node name="HostButton" type="Button" parent="Control/MainMenu/VBoxContainer" unique_id=268532531]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Host Game"
|
text = "Host Game"
|
||||||
|
|
||||||
[node name="JoinButton" type="Button" parent="Control/MainMenu/VBoxContainer"]
|
[node name="JoinButton" type="Button" parent="Control/MainMenu/VBoxContainer" unique_id=417556513]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Join Game"
|
text = "Join Game"
|
||||||
|
|
||||||
[node name="Spacer3" type="Control" parent="Control/MainMenu/VBoxContainer"]
|
[node name="Spacer3" type="Control" parent="Control/MainMenu/VBoxContainer" unique_id=1966231585]
|
||||||
custom_minimum_size = Vector2(0, 20)
|
custom_minimum_size = Vector2(0, 20)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_vertical = 3
|
size_flags_vertical = 3
|
||||||
|
|
||||||
[node name="Instructions" type="Label" parent="Control/MainMenu/VBoxContainer"]
|
[node name="Instructions" type="Label" parent="Control/MainMenu/VBoxContainer" unique_id=1999421718]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Controls:
|
text = "Controls:
|
||||||
WASD/Arrows - Move (P1) | Gamepad (P2+)
|
WASD/Arrows - Move (P1) | Gamepad (P2+)
|
||||||
@@ -108,4 +128,3 @@ E/A (Tap) - Lift/Throw
|
|||||||
E/A (Hold) - Push/Pull"
|
E/A (Hold) - Push/Pull"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ extends CanvasLayer
|
|||||||
@onready var main_menu = $Control/MainMenu
|
@onready var main_menu = $Control/MainMenu
|
||||||
@onready var host_button = $Control/MainMenu/VBoxContainer/HostButton
|
@onready var host_button = $Control/MainMenu/VBoxContainer/HostButton
|
||||||
@onready var join_button = $Control/MainMenu/VBoxContainer/JoinButton
|
@onready var join_button = $Control/MainMenu/VBoxContainer/JoinButton
|
||||||
|
@onready var network_mode_option = $Control/MainMenu/VBoxContainer/NetworkModeContainer/NetworkModeOption
|
||||||
|
@onready var network_mode_container = $Control/MainMenu/VBoxContainer/NetworkModeContainer
|
||||||
@onready var local_players_spinbox = $Control/MainMenu/VBoxContainer/LocalPlayersContainer/SpinBox
|
@onready var local_players_spinbox = $Control/MainMenu/VBoxContainer/LocalPlayersContainer/SpinBox
|
||||||
@onready var address_input = $Control/MainMenu/VBoxContainer/AddressContainer/AddressInput
|
@onready var address_input = $Control/MainMenu/VBoxContainer/AddressContainer/AddressInput
|
||||||
|
|
||||||
@@ -32,6 +34,27 @@ func _ready():
|
|||||||
host_button.pressed.connect(_on_host_pressed)
|
host_button.pressed.connect(_on_host_pressed)
|
||||||
join_button.pressed.connect(_on_join_pressed)
|
join_button.pressed.connect(_on_join_pressed)
|
||||||
|
|
||||||
|
# Setup network mode dropdown
|
||||||
|
if network_mode_option:
|
||||||
|
network_mode_option.item_selected.connect(_on_network_mode_changed)
|
||||||
|
|
||||||
|
# On web builds, filter out ENet option (only WebRTC and WebSocket available)
|
||||||
|
if OS.get_name() == "Web":
|
||||||
|
print("GameUI: Web platform detected, filtering network mode options")
|
||||||
|
# Remove ENet option (index 0) for web builds
|
||||||
|
network_mode_option.remove_item(0)
|
||||||
|
# Adjust selected index (was 0 for ENet, now 0 is WebRTC)
|
||||||
|
network_mode_option.selected = 0
|
||||||
|
# Update network manager to use WebRTC by default
|
||||||
|
network_manager.set_network_mode(1)
|
||||||
|
else:
|
||||||
|
# On native builds, default to ENet (index 0)
|
||||||
|
network_mode_option.selected = 0
|
||||||
|
network_manager.set_network_mode(0)
|
||||||
|
|
||||||
|
# Update address input placeholder based on initial mode
|
||||||
|
_on_network_mode_changed(network_mode_option.selected)
|
||||||
|
|
||||||
# Connect network signals
|
# Connect network signals
|
||||||
if network_manager:
|
if network_manager:
|
||||||
network_manager.connection_succeeded.connect(_on_connection_succeeded)
|
network_manager.connection_succeeded.connect(_on_connection_succeeded)
|
||||||
@@ -50,7 +73,8 @@ func _check_command_line_args():
|
|||||||
var should_host = false
|
var should_host = false
|
||||||
var should_join = false
|
var should_join = false
|
||||||
var should_debug = false
|
var should_debug = false
|
||||||
var join_address = "127.0.0.1"
|
var force_webrtc = false
|
||||||
|
var join_address = "ruinborn.thefirstboss.com"
|
||||||
var local_count = 1
|
var local_count = 1
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
@@ -60,6 +84,9 @@ func _check_command_line_args():
|
|||||||
elif arg == "--join":
|
elif arg == "--join":
|
||||||
should_join = true
|
should_join = true
|
||||||
print("GameUI: Found --join argument")
|
print("GameUI: Found --join argument")
|
||||||
|
elif arg == "--websocket" or arg == "--webrtc":
|
||||||
|
force_webrtc = true
|
||||||
|
print("GameUI: Found --websocket/--webrtc argument (forcing WebSocket mode)")
|
||||||
elif arg == "--room-debug":
|
elif arg == "--room-debug":
|
||||||
should_debug = true
|
should_debug = true
|
||||||
print("GameUI: Found --room-debug argument")
|
print("GameUI: Found --room-debug argument")
|
||||||
@@ -68,7 +95,18 @@ func _check_command_line_args():
|
|||||||
elif arg.begins_with("--players="):
|
elif arg.begins_with("--players="):
|
||||||
local_count = int(arg.split("=")[1])
|
local_count = int(arg.split("=")[1])
|
||||||
|
|
||||||
print("GameUI: Parsed flags - should_host: ", should_host, ", should_join: ", should_join, ", should_debug: ", should_debug)
|
print("GameUI: Parsed flags - should_host: ", should_host, ", should_join: ", should_join, ", force_webrtc: ", force_webrtc, ", should_debug: ", should_debug)
|
||||||
|
|
||||||
|
# Force WebRTC mode if --webrtc flag is present
|
||||||
|
if force_webrtc:
|
||||||
|
network_manager.set_network_mode(1) # WebRTC
|
||||||
|
if network_mode_option:
|
||||||
|
if OS.get_name() == "Web":
|
||||||
|
network_mode_option.selected = 0 # WebRTC is first option on web
|
||||||
|
else:
|
||||||
|
network_mode_option.selected = 1 # WebRTC is second option on native
|
||||||
|
_on_network_mode_changed(network_mode_option.selected)
|
||||||
|
print("GameUI: WebRTC mode forced via --webrtc flag")
|
||||||
|
|
||||||
# Set debug flag only if --room-debug is used with --host or --join
|
# Set debug flag only if --room-debug is used with --host or --join
|
||||||
if should_debug and (should_host or should_join):
|
if should_debug and (should_host or should_join):
|
||||||
@@ -91,24 +129,68 @@ func _check_command_line_args():
|
|||||||
# Connection callback will handle starting the game
|
# Connection callback will handle starting the game
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
func _on_network_mode_changed(index: int):
|
||||||
|
# On web builds, index 0 = WebRTC, index 1 = WebSocket
|
||||||
|
# On native builds, index 0 = ENet, index 1 = WebRTC, index 2 = WebSocket
|
||||||
|
var actual_mode: int
|
||||||
|
if OS.get_name() == "Web":
|
||||||
|
# Web builds: 0 = WebRTC, 1 = WebSocket
|
||||||
|
actual_mode = index + 1 # Map 0->1 (WebRTC), 1->2 (WebSocket)
|
||||||
|
else:
|
||||||
|
# Native builds: 0 = ENet, 1 = WebRTC, 2 = WebSocket
|
||||||
|
actual_mode = index
|
||||||
|
|
||||||
|
network_manager.set_network_mode(actual_mode)
|
||||||
|
|
||||||
|
# Update address input placeholder based on mode
|
||||||
|
if address_input:
|
||||||
|
match actual_mode:
|
||||||
|
0: # ENet
|
||||||
|
address_input.placeholder_text = "Server IP or domain"
|
||||||
|
1: # WebRTC
|
||||||
|
address_input.placeholder_text = "Enter Room Code (e.g., ABC123)"
|
||||||
|
2: # WebSocket
|
||||||
|
address_input.placeholder_text = "Enter Room Code (e.g., ABC123)"
|
||||||
|
|
||||||
|
var mode_names = ["ENet", "WebRTC", "WebSocket"]
|
||||||
|
print("GameUI: Network mode changed to: ", mode_names[actual_mode])
|
||||||
|
|
||||||
func _on_host_pressed():
|
func _on_host_pressed():
|
||||||
var local_count = int(local_players_spinbox.value)
|
var local_count = int(local_players_spinbox.value)
|
||||||
network_manager.set_local_player_count(local_count)
|
network_manager.set_local_player_count(local_count)
|
||||||
|
|
||||||
if network_manager.host_game():
|
if network_manager.host_game():
|
||||||
print("Hosting game with ", local_count, " local players")
|
var mode = network_manager.network_mode
|
||||||
|
if mode == 1 or mode == 2: # WebRTC or WebSocket
|
||||||
|
var room_id = network_manager.get_room_id()
|
||||||
|
var mode_name = "WebRTC" if mode == 1 else "WebSocket"
|
||||||
|
print("Hosting ", mode_name, " game - Room Code: ", room_id)
|
||||||
|
print("Share this code with players!")
|
||||||
|
else:
|
||||||
|
print("Hosting ENet game with ", local_count, " local players")
|
||||||
_start_game()
|
_start_game()
|
||||||
|
|
||||||
func _on_join_pressed():
|
func _on_join_pressed():
|
||||||
var address = address_input.text
|
var address = address_input.text
|
||||||
if address.is_empty():
|
if address.is_empty():
|
||||||
|
var mode = network_manager.network_mode
|
||||||
|
if mode == 1 or mode == 2: # WebRTC or WebSocket
|
||||||
|
print("Error: Please enter a room code")
|
||||||
|
return
|
||||||
|
else: # ENet
|
||||||
address = "127.0.0.1"
|
address = "127.0.0.1"
|
||||||
|
|
||||||
var local_count = int(local_players_spinbox.value)
|
var local_count = int(local_players_spinbox.value)
|
||||||
network_manager.set_local_player_count(local_count)
|
network_manager.set_local_player_count(local_count)
|
||||||
|
|
||||||
if network_manager.join_game(address):
|
if network_manager.join_game(address):
|
||||||
print("Joining game at ", address, " with ", local_count, " local players")
|
var mode = network_manager.network_mode
|
||||||
|
if mode == 1: # WebRTC
|
||||||
|
print("Joining WebRTC game with room code: ", address)
|
||||||
|
elif mode == 2: # WebSocket
|
||||||
|
print("Joining WebSocket game with room code: ", address)
|
||||||
|
else: # ENet
|
||||||
|
print("Joining ENet game at ", address, " with ", local_count, " local players")
|
||||||
|
|
||||||
func _on_connection_succeeded():
|
func _on_connection_succeeded():
|
||||||
print("Connection succeeded, starting game")
|
print("Connection succeeded, starting game")
|
||||||
|
|||||||
@@ -12,8 +12,12 @@ var label_time: Label = null
|
|||||||
var label_time_value: Label = null
|
var label_time_value: Label = null
|
||||||
var label_boss: Label = null
|
var label_boss: Label = null
|
||||||
var texture_progress_bar_boss_hp: TextureProgressBar = null
|
var texture_progress_bar_boss_hp: TextureProgressBar = null
|
||||||
|
var label_host: Label = null
|
||||||
|
var label_player_count: Label = null
|
||||||
|
var label_room_code: Label = null
|
||||||
|
|
||||||
var game_world: Node = null
|
var game_world: Node = null
|
||||||
|
var network_manager: Node = null
|
||||||
var local_player: Node = null
|
var local_player: Node = null
|
||||||
var level_start_time: float = 0.0
|
var level_start_time: float = 0.0
|
||||||
var player_search_attempts: int = 0
|
var player_search_attempts: int = 0
|
||||||
@@ -34,6 +38,16 @@ func _ready():
|
|||||||
label_time_value = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerTime/LabelTimeValue")
|
label_time_value = get_node_or_null("UpperLeft/HBoxContainer/VBoxContainerTime/LabelTimeValue")
|
||||||
label_boss = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerBoss/LabelBoss")
|
label_boss = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerBoss/LabelBoss")
|
||||||
texture_progress_bar_boss_hp = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerBoss/TextureProgressBarBossHP")
|
texture_progress_bar_boss_hp = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerBoss/TextureProgressBarBossHP")
|
||||||
|
label_host = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerHost/LabelHost")
|
||||||
|
label_player_count = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerHost/LabelPlayerCount")
|
||||||
|
label_room_code = get_node_or_null("UpperRight/HBoxContainer/VBoxContainerHost/LabelRoomCode")
|
||||||
|
|
||||||
|
# Find network manager
|
||||||
|
network_manager = get_node_or_null("/root/NetworkManager")
|
||||||
|
if network_manager:
|
||||||
|
# Connect to player connection signals to update player count
|
||||||
|
network_manager.player_connected.connect(_on_player_connected)
|
||||||
|
network_manager.player_disconnected.connect(_on_player_disconnected)
|
||||||
|
|
||||||
# Debug: Check if nodes were found
|
# Debug: Check if nodes were found
|
||||||
if not label_time_value:
|
if not label_time_value:
|
||||||
@@ -56,6 +70,9 @@ func _ready():
|
|||||||
if label_boss:
|
if label_boss:
|
||||||
label_boss.visible = false
|
label_boss.visible = false
|
||||||
|
|
||||||
|
# Update host info display
|
||||||
|
_update_host_info()
|
||||||
|
|
||||||
# Start level timer
|
# Start level timer
|
||||||
level_start_time = Time.get_ticks_msec() / 1000.0
|
level_start_time = Time.get_ticks_msec() / 1000.0
|
||||||
|
|
||||||
@@ -63,6 +80,41 @@ func _ready():
|
|||||||
player_search_attempts = 0
|
player_search_attempts = 0
|
||||||
_find_local_player()
|
_find_local_player()
|
||||||
|
|
||||||
|
func _on_player_connected(_peer_id: int, _player_info: Dictionary):
|
||||||
|
_update_host_info()
|
||||||
|
|
||||||
|
func _on_player_disconnected(_peer_id: int, _player_info: Dictionary):
|
||||||
|
_update_host_info()
|
||||||
|
|
||||||
|
func _update_host_info():
|
||||||
|
if not network_manager:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update HOST label visibility
|
||||||
|
if label_host:
|
||||||
|
label_host.visible = network_manager.is_hosting
|
||||||
|
|
||||||
|
# Update player count
|
||||||
|
if label_player_count and network_manager.players_info:
|
||||||
|
var total_players = 0
|
||||||
|
for peer_id in network_manager.players_info.keys():
|
||||||
|
var info = network_manager.players_info[peer_id]
|
||||||
|
total_players += info.get("local_player_count", 1)
|
||||||
|
label_player_count.text = "Players: " + str(total_players)
|
||||||
|
|
||||||
|
# Update room code (only show if WebRTC/WebSocket and hosting)
|
||||||
|
if label_room_code:
|
||||||
|
var mode = network_manager.network_mode
|
||||||
|
if (mode == 1 or mode == 2) and network_manager.is_hosting: # WebRTC or WebSocket
|
||||||
|
var room_id = network_manager.get_room_id()
|
||||||
|
if not room_id.is_empty():
|
||||||
|
label_room_code.text = "Room: " + room_id
|
||||||
|
label_room_code.visible = true
|
||||||
|
else:
|
||||||
|
label_room_code.visible = false
|
||||||
|
else:
|
||||||
|
label_room_code.visible = false
|
||||||
|
|
||||||
func _find_local_player():
|
func _find_local_player():
|
||||||
# Prevent infinite recursion
|
# Prevent infinite recursion
|
||||||
player_search_attempts += 1
|
player_search_attempts += 1
|
||||||
|
|||||||
387
src/scripts/matchbox_client.gd
Normal file
387
src/scripts/matchbox_client.gd
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
# Matchbox Client - Handles WebSocket signaling for WebRTC via Matchbox server
|
||||||
|
# Protocol: https://github.com/johanhelsing/matchbox
|
||||||
|
|
||||||
|
signal peer_joined(peer_id: int)
|
||||||
|
signal peer_left(peer_id: int)
|
||||||
|
signal peer_connected(peer_id: int)
|
||||||
|
signal connection_failed()
|
||||||
|
signal connection_succeeded()
|
||||||
|
signal webrtc_ready() # Emitted when WebRTC mesh is set up after Welcome message
|
||||||
|
|
||||||
|
const MATCHBOX_SERVER = "wss://matchbox.thefirstboss.com"
|
||||||
|
const STUN_SERVER = "stun:ruinborn.thefirstboss.com:3578"
|
||||||
|
|
||||||
|
var websocket: WebSocketPeer = null
|
||||||
|
var webrtc_peer: WebRTCMultiplayerPeer = null
|
||||||
|
var room_name: String = ""
|
||||||
|
var is_connected: bool = false
|
||||||
|
var is_hosting: bool = false # Set by NetworkManager to determine if we're host (ID 1) or client (ID 2+)
|
||||||
|
var my_uuid: String = "" # Our UUID assigned by Matchbox
|
||||||
|
var my_peer_id: int = 0 # Our integer peer ID (1 = host, 2+ = clients)
|
||||||
|
var peer_uuid_to_id: Dictionary = {} # UUID -> integer peer ID
|
||||||
|
var peer_id_counter: int = 1 # Counter for assigning integer peer IDs
|
||||||
|
var peer_connections: Dictionary = {} # peer_id (int) -> WebRTCPeerConnection
|
||||||
|
var pending_offers: Dictionary = {} # peer_id -> offer data
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
pass
|
||||||
|
|
||||||
|
func connect_to_room(room: String, hosting: bool = false) -> bool:
|
||||||
|
if websocket:
|
||||||
|
disconnect_from_room()
|
||||||
|
|
||||||
|
room_name = room
|
||||||
|
is_hosting = hosting
|
||||||
|
print("MatchboxClient: Connecting to room: ", room, " (hosting: ", hosting, ")")
|
||||||
|
|
||||||
|
# Create WebSocket connection to Matchbox
|
||||||
|
websocket = WebSocketPeer.new()
|
||||||
|
var url = MATCHBOX_SERVER + "/" + room_name
|
||||||
|
var error = websocket.connect_to_url(url)
|
||||||
|
|
||||||
|
if error != OK:
|
||||||
|
push_error("MatchboxClient: Failed to connect to Matchbox: " + str(error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
print("MatchboxClient: Connecting to: ", url)
|
||||||
|
return true
|
||||||
|
|
||||||
|
func disconnect_from_room():
|
||||||
|
if websocket:
|
||||||
|
websocket.close()
|
||||||
|
websocket = null
|
||||||
|
|
||||||
|
# Close all peer connections
|
||||||
|
for peer_id in peer_connections:
|
||||||
|
var pc = peer_connections[peer_id]
|
||||||
|
if pc:
|
||||||
|
pc.close()
|
||||||
|
peer_connections.clear()
|
||||||
|
pending_offers.clear()
|
||||||
|
is_connected = false
|
||||||
|
room_name = ""
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
if not websocket:
|
||||||
|
return
|
||||||
|
|
||||||
|
websocket.poll()
|
||||||
|
|
||||||
|
var state = websocket.get_ready_state()
|
||||||
|
|
||||||
|
if state == WebSocketPeer.STATE_OPEN:
|
||||||
|
if not is_connected:
|
||||||
|
is_connected = true
|
||||||
|
connection_succeeded.emit()
|
||||||
|
print("MatchboxClient: Connected to Matchbox server")
|
||||||
|
|
||||||
|
# Process incoming messages
|
||||||
|
while websocket.get_available_packet_count() > 0:
|
||||||
|
var packet = websocket.get_packet()
|
||||||
|
_handle_message(packet.get_string_from_utf8())
|
||||||
|
|
||||||
|
elif state == WebSocketPeer.STATE_CLOSED:
|
||||||
|
if is_connected:
|
||||||
|
is_connected = false
|
||||||
|
connection_failed.emit()
|
||||||
|
print("MatchboxClient: Disconnected from Matchbox server")
|
||||||
|
else:
|
||||||
|
# Connection failed before being established
|
||||||
|
print("MatchboxClient: Connection failed (state: CLOSED)")
|
||||||
|
connection_failed.emit()
|
||||||
|
|
||||||
|
elif state == WebSocketPeer.STATE_CONNECTING:
|
||||||
|
# Still connecting, wait for next poll
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif state == WebSocketPeer.STATE_CLOSING:
|
||||||
|
# Connection is closing
|
||||||
|
print("MatchboxClient: Connection closing...")
|
||||||
|
|
||||||
|
func _handle_message(message: String):
|
||||||
|
print("MatchboxClient: Received message: ", message)
|
||||||
|
var json = JSON.new()
|
||||||
|
var error = json.parse(message)
|
||||||
|
if error != OK:
|
||||||
|
push_error("MatchboxClient: Failed to parse message: " + message)
|
||||||
|
return
|
||||||
|
|
||||||
|
var data = json.data
|
||||||
|
print("MatchboxClient: Parsed data: ", data)
|
||||||
|
|
||||||
|
# Matchbox protocol uses direct keys: IdAssigned, NewPeer, PeerLeft, Signal
|
||||||
|
if data.has("IdAssigned"):
|
||||||
|
_handle_id_assigned(data.get("IdAssigned", ""))
|
||||||
|
elif data.has("NewPeer"):
|
||||||
|
_handle_new_peer(data.get("NewPeer", ""))
|
||||||
|
elif data.has("PeerLeft"):
|
||||||
|
_handle_peer_left_uuid(data.get("PeerLeft", ""))
|
||||||
|
elif data.has("Signal"):
|
||||||
|
_handle_signal_message_dict(data.get("Signal", {}))
|
||||||
|
else:
|
||||||
|
print("MatchboxClient: Unknown message format: ", data)
|
||||||
|
|
||||||
|
func _handle_id_assigned(uuid: String):
|
||||||
|
print("MatchboxClient: IdAssigned message received: ", uuid)
|
||||||
|
my_uuid = uuid
|
||||||
|
|
||||||
|
# Assign peer ID based on whether we're hosting or joining
|
||||||
|
# Host gets ID 1, clients get ID 2, 3, 4, etc.
|
||||||
|
if is_hosting:
|
||||||
|
my_peer_id = 1
|
||||||
|
peer_uuid_to_id[my_uuid] = my_peer_id
|
||||||
|
peer_id_counter = 2 # Next peer will get ID 2
|
||||||
|
else:
|
||||||
|
# Client: we'll assign ourselves a temporary ID, but this needs coordination
|
||||||
|
# Actually, we can't determine our ID from IdAssigned alone - we need to wait
|
||||||
|
# For now, assign ID 2 (assumes only one client joins at a time)
|
||||||
|
# TODO: Proper peer ID coordination needed for multiple simultaneous joins
|
||||||
|
my_peer_id = 2 # Client gets ID 2
|
||||||
|
peer_uuid_to_id[my_uuid] = my_peer_id
|
||||||
|
peer_id_counter = 3 # Next peer will get ID 3
|
||||||
|
|
||||||
|
print("MatchboxClient: Assigned UUID: ", my_uuid, " -> Peer ID: ", my_peer_id, " (hosting: ", is_hosting, ")")
|
||||||
|
|
||||||
|
# Setup WebRTC mesh now that we know our peer ID
|
||||||
|
if setup_webrtc_peer():
|
||||||
|
webrtc_ready.emit()
|
||||||
|
print("MatchboxClient: WebRTC mesh ready, signal emitted")
|
||||||
|
|
||||||
|
func _handle_new_peer(uuid: String):
|
||||||
|
if uuid.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
print("MatchboxClient: NewPeer message received: ", uuid)
|
||||||
|
|
||||||
|
# Assign sequential integer peer ID
|
||||||
|
var peer_id = peer_id_counter
|
||||||
|
peer_uuid_to_id[uuid] = peer_id
|
||||||
|
peer_id_counter += 1
|
||||||
|
|
||||||
|
print("MatchboxClient: New peer UUID: ", uuid, " -> Peer ID: ", peer_id)
|
||||||
|
peer_joined.emit(peer_id)
|
||||||
|
_create_peer_connection(peer_id)
|
||||||
|
|
||||||
|
func _handle_peer_left_uuid(uuid: String):
|
||||||
|
if uuid.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
var peer_id = peer_uuid_to_id.get(uuid, 0)
|
||||||
|
if peer_id == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
print("MatchboxClient: Peer left UUID: ", uuid, " -> Peer ID: ", peer_id)
|
||||||
|
peer_uuid_to_id.erase(uuid)
|
||||||
|
peer_left.emit(peer_id)
|
||||||
|
_close_peer_connection(peer_id)
|
||||||
|
|
||||||
|
func _handle_signal_message_dict(signal_data: Dictionary):
|
||||||
|
# Matchbox Signal format: {"From": "uuid", "signal": {...}}
|
||||||
|
# We need to convert UUID to integer peer ID
|
||||||
|
var from_uuid = signal_data.get("From", "")
|
||||||
|
if from_uuid.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
var peer_id = peer_uuid_to_id.get(from_uuid, 0)
|
||||||
|
if peer_id == 0:
|
||||||
|
# Unknown peer - might be a new peer, assign ID
|
||||||
|
peer_id = peer_id_counter
|
||||||
|
peer_uuid_to_id[from_uuid] = peer_id
|
||||||
|
peer_id_counter += 1
|
||||||
|
print("MatchboxClient: Unknown peer in Signal, assigned ID: ", peer_id)
|
||||||
|
|
||||||
|
var sig = signal_data.get("signal", {})
|
||||||
|
if sig.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
_handle_signal_message(peer_id, sig)
|
||||||
|
|
||||||
|
func _handle_signal_message(peer_id: int, signal_data: Dictionary):
|
||||||
|
var peer_conn = peer_connections.get(peer_id)
|
||||||
|
if not peer_conn:
|
||||||
|
# Create connection if it doesn't exist
|
||||||
|
_create_peer_connection(peer_id)
|
||||||
|
peer_conn = peer_connections.get(peer_id)
|
||||||
|
if not peer_conn:
|
||||||
|
return
|
||||||
|
|
||||||
|
var type = signal_data.get("type", "")
|
||||||
|
|
||||||
|
match type:
|
||||||
|
"offer":
|
||||||
|
_handle_offer(peer_id, signal_data)
|
||||||
|
"answer":
|
||||||
|
_handle_answer(peer_id, signal_data)
|
||||||
|
"ice-candidate":
|
||||||
|
_handle_ice_candidate(peer_id, signal_data)
|
||||||
|
|
||||||
|
func _create_peer_connection(peer_id: int) -> WebRTCPeerConnection:
|
||||||
|
if peer_connections.has(peer_id):
|
||||||
|
return peer_connections[peer_id]
|
||||||
|
|
||||||
|
print("MatchboxClient: Creating peer connection for peer ", peer_id)
|
||||||
|
|
||||||
|
var pc = WebRTCPeerConnection.new()
|
||||||
|
|
||||||
|
# Configure STUN server
|
||||||
|
var config = {
|
||||||
|
"iceServers": [
|
||||||
|
{
|
||||||
|
"urls": [STUN_SERVER]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = pc.initialize(config)
|
||||||
|
if error != OK:
|
||||||
|
push_error("MatchboxClient: Failed to initialize peer connection: " + str(error))
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Connect signals
|
||||||
|
pc.session_description_created.connect(_on_session_description_created.bind(peer_id))
|
||||||
|
pc.ice_candidate_created.connect(_on_ice_candidate_created.bind(peer_id))
|
||||||
|
|
||||||
|
peer_connections[peer_id] = pc
|
||||||
|
|
||||||
|
# If we're the host (peer_id 1), create offer for other peers
|
||||||
|
# Otherwise wait for offer
|
||||||
|
if my_peer_id == 1 and peer_id != 1:
|
||||||
|
# We're the host, create offer
|
||||||
|
pc.create_offer()
|
||||||
|
|
||||||
|
return pc
|
||||||
|
|
||||||
|
func _close_peer_connection(peer_id: int):
|
||||||
|
var pc = peer_connections.get(peer_id)
|
||||||
|
if pc:
|
||||||
|
pc.close()
|
||||||
|
peer_connections.erase(peer_id)
|
||||||
|
pending_offers.erase(peer_id)
|
||||||
|
|
||||||
|
func _handle_offer(peer_id: int, signal_data: Dictionary):
|
||||||
|
var pc = peer_connections.get(peer_id)
|
||||||
|
if not pc:
|
||||||
|
pc = _create_peer_connection(peer_id)
|
||||||
|
if not pc:
|
||||||
|
return
|
||||||
|
|
||||||
|
var sdp = signal_data.get("sdp", "")
|
||||||
|
if sdp.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
var set_error = pc.set_remote_description("offer", sdp)
|
||||||
|
if set_error != OK:
|
||||||
|
push_error("MatchboxClient: Failed to set remote offer: " + str(set_error))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create answer
|
||||||
|
pc.create_answer()
|
||||||
|
|
||||||
|
func _handle_answer(peer_id: int, signal_data: Dictionary):
|
||||||
|
var pc = peer_connections.get(peer_id)
|
||||||
|
if not pc:
|
||||||
|
return
|
||||||
|
|
||||||
|
var sdp = signal_data.get("sdp", "")
|
||||||
|
if sdp.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
var error = pc.set_remote_description("answer", sdp)
|
||||||
|
if error != OK:
|
||||||
|
push_error("MatchboxClient: Failed to set remote answer: " + str(error))
|
||||||
|
return
|
||||||
|
|
||||||
|
print("MatchboxClient: Answer received for peer ", peer_id)
|
||||||
|
peer_connected.emit(peer_id)
|
||||||
|
|
||||||
|
func _handle_ice_candidate(peer_id: int, signal_data: Dictionary):
|
||||||
|
var pc = peer_connections.get(peer_id)
|
||||||
|
if not pc:
|
||||||
|
return
|
||||||
|
|
||||||
|
var candidate = signal_data.get("candidate", "")
|
||||||
|
var sdp_mid = signal_data.get("sdpMid", "")
|
||||||
|
var sdp_mline_index = signal_data.get("sdpMLineIndex", 0)
|
||||||
|
|
||||||
|
if candidate.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
var error = pc.add_ice_candidate(sdp_mid, sdp_mline_index, candidate)
|
||||||
|
if error != OK:
|
||||||
|
push_error("MatchboxClient: Failed to add ICE candidate: " + str(error))
|
||||||
|
return
|
||||||
|
|
||||||
|
func _on_session_description_created(peer_id: int, type: String, sdp: String):
|
||||||
|
var message = {
|
||||||
|
"type": "Signal",
|
||||||
|
"id": peer_id,
|
||||||
|
"signal": {
|
||||||
|
"type": type,
|
||||||
|
"sdp": sdp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_send_message(message)
|
||||||
|
|
||||||
|
func _on_ice_candidate_created(peer_id: int, media: String, index: int, name: String):
|
||||||
|
var message = {
|
||||||
|
"type": "Signal",
|
||||||
|
"id": peer_id,
|
||||||
|
"signal": {
|
||||||
|
"type": "ice-candidate",
|
||||||
|
"candidate": name,
|
||||||
|
"sdpMid": media,
|
||||||
|
"sdpMLineIndex": index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_send_message(message)
|
||||||
|
|
||||||
|
func _send_message(message: Dictionary):
|
||||||
|
if not websocket or websocket.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||||
|
return
|
||||||
|
|
||||||
|
var json = JSON.stringify(message)
|
||||||
|
var error = websocket.send_text(json)
|
||||||
|
if error != OK:
|
||||||
|
push_error("MatchboxClient: Failed to send message: " + str(error))
|
||||||
|
|
||||||
|
func get_my_peer_id() -> int:
|
||||||
|
return my_peer_id
|
||||||
|
|
||||||
|
func setup_webrtc_peer() -> bool:
|
||||||
|
if webrtc_peer:
|
||||||
|
# Already set up
|
||||||
|
return true
|
||||||
|
|
||||||
|
webrtc_peer = WebRTCMultiplayerPeer.new()
|
||||||
|
|
||||||
|
# Create mesh with our peer ID (assigned by Matchbox)
|
||||||
|
# For host: peer_id = 1, for clients: peer_id = 2, 3, 4, etc.
|
||||||
|
var error = webrtc_peer.create_mesh(my_peer_id)
|
||||||
|
|
||||||
|
if error != OK:
|
||||||
|
push_error("MatchboxClient: Failed to create WebRTC mesh: " + str(error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
multiplayer.multiplayer_peer = webrtc_peer
|
||||||
|
|
||||||
|
print("MatchboxClient: WebRTC mesh created with peer ID: ", my_peer_id)
|
||||||
|
return true
|
||||||
|
|
||||||
|
func add_peer_to_mesh(peer_id: int):
|
||||||
|
if not webrtc_peer:
|
||||||
|
if not setup_webrtc_peer():
|
||||||
|
return
|
||||||
|
|
||||||
|
var pc = peer_connections.get(peer_id)
|
||||||
|
if not pc:
|
||||||
|
push_error("MatchboxClient: No peer connection for peer ", peer_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
var error = webrtc_peer.add_peer(pc, peer_id)
|
||||||
|
if error != OK:
|
||||||
|
push_error("MatchboxClient: Failed to add peer to mesh: " + str(error))
|
||||||
|
return
|
||||||
|
|
||||||
|
print("MatchboxClient: Added peer ", peer_id, " to WebRTC mesh")
|
||||||
1
src/scripts/matchbox_client.gd.uid
Normal file
1
src/scripts/matchbox_client.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bm8jiiypl81wj
|
||||||
@@ -2,21 +2,40 @@ extends Node
|
|||||||
|
|
||||||
# Network Manager - Handles multiplayer connections and player spawning
|
# Network Manager - Handles multiplayer connections and player spawning
|
||||||
# Supports both hosting and joining games
|
# Supports both hosting and joining games
|
||||||
|
# Auto-detects platform and uses appropriate networking:
|
||||||
|
# - ENet for native builds (Windows, Linux, Mac)
|
||||||
|
# - WebRTC for web builds (requires signaling server)
|
||||||
|
|
||||||
signal player_connected(peer_id, player_info)
|
signal player_connected(peer_id, player_info)
|
||||||
signal player_disconnected(peer_id, player_info)
|
signal player_disconnected(peer_id, player_info)
|
||||||
signal connection_failed()
|
signal connection_failed()
|
||||||
signal connection_succeeded()
|
signal connection_succeeded()
|
||||||
|
|
||||||
const DEFAULT_PORT = 7777
|
const DEFAULT_PORT = 21212
|
||||||
const MAX_PLAYERS = 8
|
const MAX_PLAYERS = 8
|
||||||
|
const MATCHBOX_SERVER = "wss://matchbox.thefirstboss.com"
|
||||||
|
const STUN_SERVER = "stun:ruinborn.thefirstboss.com:3578"
|
||||||
|
|
||||||
var players_info = {} # Dictionary of peer_id -> {local_player_count: int, player_names: []}
|
var players_info = {} # Dictionary of peer_id -> {local_player_count: int, player_names: []}
|
||||||
var local_player_count = 1 # How many local players on this machine
|
var local_player_count = 1 # How many local players on this machine
|
||||||
var is_hosting = false
|
var is_hosting = false
|
||||||
var show_room_labels = false # Show room labels when in debug mode
|
var show_room_labels = false # Show room labels when in debug mode
|
||||||
|
var network_mode: int = 0 # 0 = ENet, 1 = WebRTC, 2 = WebSocket
|
||||||
|
var room_id = "" # Room ID for Matchbox (WebRTC) or WebSocket server URL
|
||||||
|
var matchbox_client: Node = null # Matchbox client instance
|
||||||
|
const WEBSOCKET_SERVER_URL = "ws://ruinborn.thefirstboss.com:21212" # WebSocket server URL
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
|
# Detect if running in browser - default to WebRTC on web, ENet on native
|
||||||
|
if OS.get_name() == "Web":
|
||||||
|
network_mode = 1 # WebRTC default for web
|
||||||
|
print("NetworkManager: Detected Web platform, defaulting to WebRTC")
|
||||||
|
print("NetworkManager: Matchbox server: ", MATCHBOX_SERVER)
|
||||||
|
else:
|
||||||
|
network_mode = 0 # ENet default for native
|
||||||
|
print("NetworkManager: Using ENet by default for native platform")
|
||||||
|
print("NetworkManager: You can switch network modes in the menu")
|
||||||
|
|
||||||
# Connect multiplayer signals
|
# Connect multiplayer signals
|
||||||
multiplayer.peer_connected.connect(_on_peer_connected)
|
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||||
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
||||||
@@ -24,17 +43,128 @@ func _ready():
|
|||||||
multiplayer.connection_failed.connect(_on_connection_failed)
|
multiplayer.connection_failed.connect(_on_connection_failed)
|
||||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||||
|
|
||||||
func host_game(port: int = DEFAULT_PORT) -> bool:
|
func set_network_mode(mode: int):
|
||||||
var peer = ENetMultiplayerPeer.new()
|
# 0 = ENet, 1 = WebRTC, 2 = WebSocket
|
||||||
var error = peer.create_server(port, MAX_PLAYERS)
|
# WebRTC is only available on web builds
|
||||||
|
if mode == 1 and OS.get_name() != "Web":
|
||||||
|
push_error("NetworkManager: WebRTC is not available on native platforms. WebRTC only works in web builds. Falling back to ENet.")
|
||||||
|
network_mode = 0
|
||||||
|
print("NetworkManager: ENet mode enabled (WebRTC not available on native platforms)")
|
||||||
|
return
|
||||||
|
|
||||||
|
network_mode = mode
|
||||||
|
match mode:
|
||||||
|
0:
|
||||||
|
print("NetworkManager: ENet mode enabled")
|
||||||
|
1:
|
||||||
|
print("NetworkManager: WebRTC mode enabled")
|
||||||
|
print("NetworkManager: Matchbox server: ", MATCHBOX_SERVER)
|
||||||
|
2:
|
||||||
|
print("NetworkManager: WebSocket mode enabled")
|
||||||
|
print("NetworkManager: WebSocket server: ", WEBSOCKET_SERVER_URL)
|
||||||
|
|
||||||
|
func force_webrtc_mode(enable: bool):
|
||||||
|
# Legacy function for backwards compatibility
|
||||||
|
if enable:
|
||||||
|
set_network_mode(1)
|
||||||
|
else:
|
||||||
|
set_network_mode(0)
|
||||||
|
|
||||||
|
func host_game(port: int = DEFAULT_PORT, matchbox_room: String = "") -> bool:
|
||||||
|
var peer
|
||||||
|
var error
|
||||||
|
|
||||||
|
if network_mode == 1: # WebRTC
|
||||||
|
# WebRTC for browser builds using Matchbox signaling
|
||||||
|
# Generate room ID if not provided
|
||||||
|
if matchbox_room.is_empty():
|
||||||
|
room_id = _generate_room_id()
|
||||||
|
else:
|
||||||
|
room_id = matchbox_room
|
||||||
|
|
||||||
|
print("NetworkManager: Creating WebRTC host with room ID: ", room_id)
|
||||||
|
print("NetworkManager: Share this room code with players!")
|
||||||
|
print("NetworkManager: Matchbox URL: ", MATCHBOX_SERVER, "/", room_id)
|
||||||
|
|
||||||
|
# Create Matchbox client
|
||||||
|
var matchbox_script = load("res://scripts/matchbox_client.gd")
|
||||||
|
matchbox_client = Node.new()
|
||||||
|
matchbox_client.set_script(matchbox_script)
|
||||||
|
add_child(matchbox_client)
|
||||||
|
|
||||||
|
# Connect Matchbox signals
|
||||||
|
matchbox_client.peer_joined.connect(_on_matchbox_peer_joined)
|
||||||
|
matchbox_client.peer_left.connect(_on_matchbox_peer_left)
|
||||||
|
matchbox_client.peer_connected.connect(_on_matchbox_peer_connected)
|
||||||
|
matchbox_client.connection_succeeded.connect(_on_matchbox_connected)
|
||||||
|
matchbox_client.connection_failed.connect(_on_matchbox_connection_failed)
|
||||||
|
matchbox_client.webrtc_ready.connect(_on_matchbox_webrtc_ready)
|
||||||
|
|
||||||
|
# Setup WebRTC peer (will be initialized when Matchbox connects)
|
||||||
|
# For now, we'll set it up after Matchbox connection
|
||||||
|
is_hosting = true
|
||||||
|
|
||||||
|
# Connect to Matchbox room (pass is_hosting = true)
|
||||||
|
if not matchbox_client.connect_to_room(room_id, true):
|
||||||
|
push_error("Failed to connect to Matchbox room")
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Register the host as a player (peer_id 1)
|
||||||
|
players_info[1] = {
|
||||||
|
"local_player_count": local_player_count,
|
||||||
|
"player_names": _generate_player_names(local_player_count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
elif network_mode == 2: # WebSocket
|
||||||
|
# WebSocket for both native and web builds
|
||||||
|
# Generate room ID if not provided
|
||||||
|
if matchbox_room.is_empty():
|
||||||
|
room_id = _generate_room_id()
|
||||||
|
else:
|
||||||
|
room_id = matchbox_room
|
||||||
|
|
||||||
|
print("NetworkManager: Creating WebSocket host with room ID: ", room_id)
|
||||||
|
print("NetworkManager: Share this room code with players!")
|
||||||
|
print("NetworkManager: WebSocket URL: ", WEBSOCKET_SERVER_URL, "/", room_id)
|
||||||
|
|
||||||
|
peer = WebSocketMultiplayerPeer.new()
|
||||||
|
var url = WEBSOCKET_SERVER_URL + "/" + room_id
|
||||||
|
error = peer.create_server(port)
|
||||||
|
|
||||||
if error != OK:
|
if error != OK:
|
||||||
push_error("Failed to create server: " + str(error))
|
push_error("Failed to create WebSocket server: " + str(error))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
multiplayer.multiplayer_peer = peer
|
multiplayer.multiplayer_peer = peer
|
||||||
is_hosting = true
|
is_hosting = true
|
||||||
|
|
||||||
|
print("WebSocket server started on port ", port)
|
||||||
|
print("Players can join at: ", url)
|
||||||
|
|
||||||
|
# Register the host as a player
|
||||||
|
var my_id = multiplayer.get_unique_id()
|
||||||
|
players_info[my_id] = {
|
||||||
|
"local_player_count": local_player_count,
|
||||||
|
"player_names": _generate_player_names(local_player_count, my_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
else: # ENet (mode 0)
|
||||||
|
# ENet for native builds
|
||||||
|
peer = ENetMultiplayerPeer.new()
|
||||||
|
error = peer.create_server(port, MAX_PLAYERS)
|
||||||
|
|
||||||
|
if error != OK:
|
||||||
|
push_error("Failed to create ENet server: " + str(error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
multiplayer.multiplayer_peer = peer
|
||||||
|
is_hosting = true
|
||||||
|
|
||||||
|
print("ENet server started on port ", port)
|
||||||
|
print("Players can join at: ", get_local_ip(), ":", port)
|
||||||
|
|
||||||
# Register the host as a player
|
# Register the host as a player
|
||||||
var my_id = multiplayer.get_unique_id()
|
var my_id = multiplayer.get_unique_id()
|
||||||
players_info[my_id] = {
|
players_info[my_id] = {
|
||||||
@@ -42,29 +172,92 @@ func host_game(port: int = DEFAULT_PORT) -> bool:
|
|||||||
"player_names": _generate_player_names(local_player_count, my_id)
|
"player_names": _generate_player_names(local_player_count, my_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Server started on port ", port)
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
func join_game(address: String, port: int = DEFAULT_PORT) -> bool:
|
func join_game(address: String, port: int = DEFAULT_PORT) -> bool:
|
||||||
var peer = ENetMultiplayerPeer.new()
|
var peer
|
||||||
var error = peer.create_client(address, port)
|
var error
|
||||||
|
|
||||||
|
if network_mode == 1: # WebRTC
|
||||||
|
# WebRTC for browser builds using Matchbox signaling
|
||||||
|
# 'address' is the room ID for WebRTC
|
||||||
|
room_id = address
|
||||||
|
|
||||||
|
print("NetworkManager: Joining WebRTC game with room ID: ", room_id)
|
||||||
|
print("NetworkManager: Matchbox URL: ", MATCHBOX_SERVER, "/", room_id)
|
||||||
|
|
||||||
|
# Create Matchbox client
|
||||||
|
var matchbox_script = load("res://scripts/matchbox_client.gd")
|
||||||
|
matchbox_client = Node.new()
|
||||||
|
matchbox_client.set_script(matchbox_script)
|
||||||
|
add_child(matchbox_client)
|
||||||
|
|
||||||
|
# Connect Matchbox signals
|
||||||
|
matchbox_client.peer_joined.connect(_on_matchbox_peer_joined)
|
||||||
|
matchbox_client.peer_left.connect(_on_matchbox_peer_left)
|
||||||
|
matchbox_client.peer_connected.connect(_on_matchbox_peer_connected)
|
||||||
|
matchbox_client.connection_succeeded.connect(_on_matchbox_connected)
|
||||||
|
matchbox_client.connection_failed.connect(_on_matchbox_connection_failed)
|
||||||
|
matchbox_client.webrtc_ready.connect(_on_matchbox_webrtc_ready)
|
||||||
|
|
||||||
|
is_hosting = false
|
||||||
|
|
||||||
|
# Connect to Matchbox room (pass is_hosting = false)
|
||||||
|
if not matchbox_client.connect_to_room(room_id, false):
|
||||||
|
push_error("Failed to connect to Matchbox room")
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
elif network_mode == 2: # WebSocket
|
||||||
|
# WebSocket for both native and web builds
|
||||||
|
# 'address' is the room ID for WebSocket
|
||||||
|
room_id = address
|
||||||
|
|
||||||
|
print("NetworkManager: Joining WebSocket game with room ID: ", room_id)
|
||||||
|
print("NetworkManager: WebSocket URL: ", WEBSOCKET_SERVER_URL, "/", room_id)
|
||||||
|
|
||||||
|
peer = WebSocketMultiplayerPeer.new()
|
||||||
|
var url = WEBSOCKET_SERVER_URL + "/" + room_id
|
||||||
|
error = peer.create_client(url)
|
||||||
|
|
||||||
if error != OK:
|
if error != OK:
|
||||||
push_error("Failed to create client: " + str(error))
|
push_error("Failed to create WebSocket client: " + str(error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
multiplayer.multiplayer_peer = peer
|
||||||
|
is_hosting = false
|
||||||
|
|
||||||
|
print("Attempting to connect to WebSocket server: ", url)
|
||||||
|
|
||||||
|
return true
|
||||||
|
else: # ENet (mode 0)
|
||||||
|
# ENet for native builds
|
||||||
|
peer = ENetMultiplayerPeer.new()
|
||||||
|
error = peer.create_client(address, port)
|
||||||
|
|
||||||
|
if error != OK:
|
||||||
|
push_error("Failed to create ENet client: " + str(error))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
multiplayer.multiplayer_peer = peer
|
multiplayer.multiplayer_peer = peer
|
||||||
is_hosting = false
|
is_hosting = false
|
||||||
|
|
||||||
print("Attempting to connect to ", address, ":", port)
|
print("Attempting to connect to ", address, ":", port)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
func disconnect_from_game():
|
func disconnect_from_game():
|
||||||
|
if matchbox_client:
|
||||||
|
matchbox_client.disconnect_from_room()
|
||||||
|
matchbox_client.queue_free()
|
||||||
|
matchbox_client = null
|
||||||
|
|
||||||
if multiplayer.multiplayer_peer:
|
if multiplayer.multiplayer_peer:
|
||||||
multiplayer.multiplayer_peer.close()
|
multiplayer.multiplayer_peer.close()
|
||||||
multiplayer.multiplayer_peer = null
|
multiplayer.multiplayer_peer = null
|
||||||
players_info.clear()
|
players_info.clear()
|
||||||
is_hosting = false
|
is_hosting = false
|
||||||
|
room_id = ""
|
||||||
|
|
||||||
func set_local_player_count(count: int):
|
func set_local_player_count(count: int):
|
||||||
local_player_count = max(1, min(count, 4)) # Limit to 1-4 local players
|
local_player_count = max(1, min(count, 4)) # Limit to 1-4 local players
|
||||||
@@ -159,3 +352,140 @@ func get_all_player_ids() -> Array:
|
|||||||
func get_player_info(peer_id: int) -> Dictionary:
|
func get_player_info(peer_id: int) -> Dictionary:
|
||||||
return players_info.get(peer_id, {})
|
return players_info.get(peer_id, {})
|
||||||
|
|
||||||
|
# Matchbox callback handlers
|
||||||
|
func _on_matchbox_connected():
|
||||||
|
print("NetworkManager: Connected to Matchbox server")
|
||||||
|
# WebRTC peer will be set up when Welcome message is received with our peer ID
|
||||||
|
|
||||||
|
func _on_matchbox_webrtc_ready():
|
||||||
|
print("NetworkManager: WebRTC mesh is ready")
|
||||||
|
|
||||||
|
# Register ourselves if we're not hosting (clients need to register)
|
||||||
|
if not is_hosting and matchbox_client:
|
||||||
|
var my_peer_id = matchbox_client.get_my_peer_id()
|
||||||
|
if my_peer_id > 0 and not players_info.has(my_peer_id):
|
||||||
|
players_info[my_peer_id] = {
|
||||||
|
"local_player_count": local_player_count,
|
||||||
|
"player_names": _generate_player_names(local_player_count, my_peer_id)
|
||||||
|
}
|
||||||
|
print("NetworkManager: Registered joining player with peer ID: ", my_peer_id)
|
||||||
|
|
||||||
|
# Emit connection_succeeded signal so the game can start
|
||||||
|
connection_succeeded.emit()
|
||||||
|
|
||||||
|
func _on_matchbox_connection_failed():
|
||||||
|
print("NetworkManager: Failed to connect to Matchbox server")
|
||||||
|
connection_failed.emit()
|
||||||
|
|
||||||
|
func _on_matchbox_peer_joined(peer_id: int):
|
||||||
|
print("NetworkManager: Matchbox peer joined: ", peer_id)
|
||||||
|
# Peer connection will be created by Matchbox client
|
||||||
|
# Once connected, we'll add it to WebRTC mesh
|
||||||
|
|
||||||
|
func _on_matchbox_peer_left(peer_id: int):
|
||||||
|
print("NetworkManager: Matchbox peer left: ", peer_id)
|
||||||
|
# Player disconnect will be handled by multiplayer signals
|
||||||
|
|
||||||
|
func _on_matchbox_peer_connected(peer_id: int):
|
||||||
|
print("NetworkManager: Matchbox peer connected: ", peer_id)
|
||||||
|
# Add peer to WebRTC mesh
|
||||||
|
if matchbox_client:
|
||||||
|
matchbox_client.add_peer_to_mesh(peer_id)
|
||||||
|
|
||||||
|
# Register player info
|
||||||
|
if not players_info.has(peer_id):
|
||||||
|
players_info[peer_id] = {
|
||||||
|
"local_player_count": 1, # Default, will be updated via RPC
|
||||||
|
"player_names": _generate_player_names(1, peer_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Emit player connected signal
|
||||||
|
player_connected.emit(peer_id, players_info[peer_id])
|
||||||
|
|
||||||
|
func get_room_id() -> String:
|
||||||
|
return room_id
|
||||||
|
|
||||||
|
func get_local_ip() -> String:
|
||||||
|
var addresses = IP.get_local_addresses()
|
||||||
|
for addr in addresses:
|
||||||
|
# Skip localhost and IPv6
|
||||||
|
if addr.begins_with("127.") or ":" in addr:
|
||||||
|
continue
|
||||||
|
return addr
|
||||||
|
return "127.0.0.1"
|
||||||
|
|
||||||
|
func _generate_room_id() -> String:
|
||||||
|
# Generate a random 6-character room code
|
||||||
|
var chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" # Avoid confusing chars like O/0, I/1
|
||||||
|
var code = ""
|
||||||
|
for i in range(6):
|
||||||
|
code += chars[randi() % chars.length()]
|
||||||
|
return code
|
||||||
|
|
||||||
|
|
||||||
|
func get_webrtc_peer() -> WebRTCMultiplayerPeer:
|
||||||
|
if network_mode == 1 and multiplayer.multiplayer_peer is WebRTCMultiplayerPeer:
|
||||||
|
return multiplayer.multiplayer_peer as WebRTCMultiplayerPeer
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Create a WebRTC peer connection with STUN server configured
|
||||||
|
func create_peer_connection() -> WebRTCPeerConnection:
|
||||||
|
var peer_connection = WebRTCPeerConnection.new()
|
||||||
|
|
||||||
|
# Configure STUN server for NAT traversal
|
||||||
|
var config = {
|
||||||
|
"iceServers": [
|
||||||
|
{
|
||||||
|
"urls": [STUN_SERVER]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = peer_connection.initialize(config)
|
||||||
|
if error != OK:
|
||||||
|
push_error("Failed to initialize WebRTC peer connection: " + str(error))
|
||||||
|
return null
|
||||||
|
|
||||||
|
print("WebRTC peer connection initialized with STUN server: ", STUN_SERVER)
|
||||||
|
return peer_connection
|
||||||
|
|
||||||
|
# Add a peer connection for WebRTC mesh networking
|
||||||
|
func add_webrtc_peer(peer_id: int) -> bool:
|
||||||
|
var webrtc = get_webrtc_peer()
|
||||||
|
if not webrtc:
|
||||||
|
push_error("Not using WebRTC")
|
||||||
|
return false
|
||||||
|
|
||||||
|
var peer_connection = create_peer_connection()
|
||||||
|
if not peer_connection:
|
||||||
|
return false
|
||||||
|
|
||||||
|
var error = webrtc.add_peer(peer_connection, peer_id)
|
||||||
|
|
||||||
|
if error != OK:
|
||||||
|
push_error("Failed to add WebRTC peer: " + str(error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
print("Added WebRTC peer: ", peer_id, " with STUN server configured")
|
||||||
|
return true
|
||||||
|
|
||||||
|
# Get the peer connection info for a specific peer
|
||||||
|
func get_peer_connection(peer_id: int) -> Dictionary:
|
||||||
|
var webrtc = get_webrtc_peer()
|
||||||
|
if not webrtc:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return webrtc.get_peer(peer_id)
|
||||||
|
|
||||||
|
# Get connection info to share with remote peer (for manual signaling)
|
||||||
|
# This would typically be sent via a signaling server
|
||||||
|
# Note: With matchbox, signaling is handled automatically
|
||||||
|
func get_connection_offer(peer_id: int) -> Dictionary:
|
||||||
|
var peer_conn = get_peer_connection(peer_id)
|
||||||
|
if peer_conn.is_empty():
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# In a real implementation, you'd get the SDP offer from the peer connection
|
||||||
|
# With matchbox, this is handled automatically via the signaling server
|
||||||
|
print("To implement: Get SDP offer for peer ", peer_id)
|
||||||
|
return {}
|
||||||
|
|||||||
141
src/scripts/webrtc_network.gd
Normal file
141
src/scripts/webrtc_network.gd
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
# WebRTC Network Manager - For browser/WebAssembly builds
|
||||||
|
# Uses WebRTCMultiplayerPeer for P2P connections
|
||||||
|
|
||||||
|
signal connection_ready
|
||||||
|
signal connection_failed
|
||||||
|
signal session_created(session_data)
|
||||||
|
signal peer_connected_webrtc(peer_id)
|
||||||
|
signal peer_disconnected_webrtc(peer_id)
|
||||||
|
|
||||||
|
const STUN_SERVER = "stun:ruinborn.thefirstboss.com:3578"
|
||||||
|
|
||||||
|
var webrtc_peer: WebRTCMultiplayerPeer = null
|
||||||
|
var is_host: bool = false
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
print("WebRTC: Using STUN server: ", STUN_SERVER)
|
||||||
|
|
||||||
|
func create_host() -> bool:
|
||||||
|
print("WebRTC: Creating host")
|
||||||
|
webrtc_peer = WebRTCMultiplayerPeer.new()
|
||||||
|
|
||||||
|
# Godot 4.x API: initialize(peer_id, server_compat)
|
||||||
|
# peer_id 1 for server, server_compat = true
|
||||||
|
var error = webrtc_peer.initialize(1, true)
|
||||||
|
if error != OK:
|
||||||
|
push_error("WebRTC: Failed to initialize server: " + str(error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
multiplayer.multiplayer_peer = webrtc_peer
|
||||||
|
is_host = true
|
||||||
|
|
||||||
|
# Connect signals
|
||||||
|
webrtc_peer.peer_connected.connect(_on_peer_connected)
|
||||||
|
webrtc_peer.peer_disconnected.connect(_on_peer_disconnected)
|
||||||
|
|
||||||
|
print("WebRTC: Host initialized successfully")
|
||||||
|
connection_ready.emit()
|
||||||
|
return true
|
||||||
|
|
||||||
|
func create_client() -> bool:
|
||||||
|
print("WebRTC: Creating client")
|
||||||
|
webrtc_peer = WebRTCMultiplayerPeer.new()
|
||||||
|
|
||||||
|
# Godot 4.x API: initialize(peer_id, server_compat)
|
||||||
|
# peer_id 0 for auto-assign, server_compat = false for client
|
||||||
|
var error = webrtc_peer.initialize(0, false)
|
||||||
|
if error != OK:
|
||||||
|
push_error("WebRTC: Failed to initialize client: " + str(error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
multiplayer.multiplayer_peer = webrtc_peer
|
||||||
|
is_host = false
|
||||||
|
|
||||||
|
# Connect signals
|
||||||
|
webrtc_peer.peer_connected.connect(_on_peer_connected)
|
||||||
|
webrtc_peer.peer_disconnected.connect(_on_peer_disconnected)
|
||||||
|
|
||||||
|
print("WebRTC: Client initialized successfully")
|
||||||
|
connection_ready.emit()
|
||||||
|
return true
|
||||||
|
|
||||||
|
func create_mesh(peer_id: int) -> bool:
|
||||||
|
print("WebRTC: Creating mesh peer for ID ", peer_id)
|
||||||
|
if not webrtc_peer:
|
||||||
|
push_error("WebRTC: Peer not initialized")
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Create peer connection with STUN server configured
|
||||||
|
var peer_connection = WebRTCPeerConnection.new()
|
||||||
|
|
||||||
|
# Configure STUN server for NAT traversal
|
||||||
|
var config = {
|
||||||
|
"iceServers": [
|
||||||
|
{
|
||||||
|
"urls": [STUN_SERVER]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
var init_error = peer_connection.initialize(config)
|
||||||
|
if init_error != OK:
|
||||||
|
push_error("WebRTC: Failed to initialize peer connection: " + str(init_error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
var error = webrtc_peer.add_peer(peer_connection, peer_id)
|
||||||
|
|
||||||
|
if error != OK:
|
||||||
|
push_error("WebRTC: Failed to add peer: " + str(error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
print("WebRTC: Mesh peer created for ID ", peer_id, " with STUN server")
|
||||||
|
return true
|
||||||
|
|
||||||
|
# Get the local session description (offer or answer) to share with remote peer
|
||||||
|
func get_local_session() -> String:
|
||||||
|
if not webrtc_peer:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Get all connection states and session descriptions
|
||||||
|
var session_data = {}
|
||||||
|
|
||||||
|
# For host, we need to create offers for each connected peer
|
||||||
|
# For client, we need to create an answer
|
||||||
|
|
||||||
|
# Note: WebRTCMultiplayerPeer handles this internally
|
||||||
|
# We just need to get the peer connection for manual signaling
|
||||||
|
|
||||||
|
# This is a simplified approach - for full WebRTC, you'd need to:
|
||||||
|
# 1. Create RTCPeerConnection
|
||||||
|
# 2. Create offer/answer
|
||||||
|
# 3. Exchange ICE candidates
|
||||||
|
|
||||||
|
# For now, return empty - we'll use a signaling server approach instead
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Set the remote session description from another peer
|
||||||
|
func set_remote_session(session_data: String) -> bool:
|
||||||
|
if not webrtc_peer:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Parse and apply remote session
|
||||||
|
# This would involve SDP parsing and ICE candidate exchange
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _on_peer_connected(id: int):
|
||||||
|
print("WebRTC: Peer connected: ", id)
|
||||||
|
peer_connected_webrtc.emit(id)
|
||||||
|
|
||||||
|
func _on_peer_disconnected(id: int):
|
||||||
|
print("WebRTC: Peer disconnected: ", id)
|
||||||
|
peer_disconnected_webrtc.emit(id)
|
||||||
|
|
||||||
|
func close_connection():
|
||||||
|
if webrtc_peer:
|
||||||
|
webrtc_peer.close()
|
||||||
|
webrtc_peer = null
|
||||||
|
multiplayer.multiplayer_peer = null
|
||||||
|
is_host = false
|
||||||
1
src/scripts/webrtc_network.gd.uid
Normal file
1
src/scripts/webrtc_network.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cyyaodbagbthp
|
||||||
Reference in New Issue
Block a user