Browse Source

[Net] Update WebSocket multiplayer demo.

Updated to Godot 4.

Added combo (default) scene showing dedicated multiplayer branches
(i.e. running both server and clients in the same SceneTree).
Fabio Alessandrelli 2 years ago
parent
commit
da12c09942

+ 3 - 2
networking/websocket_multiplayer/project.godot

@@ -6,13 +6,14 @@
 ;   [section] ; section goes between []
 ;   param=value ; assign values to parameters
 
-config_version=4
+config_version=5
 
 [application]
 
 config/name="WebSocket Multiplayer Demo"
 config/description="This is a sample showing how the use WebSockets along with the Multiplayer API in Godot."
-run/main_scene="res://scene/main.tscn"
+run/main_scene="res://scene/combo.tscn"
+config/features=PackedStringArray("4.0")
 config/icon="res://icon.png"
 
 [rendering]

+ 66 - 0
networking/websocket_multiplayer/scene/combo.tscn

@@ -0,0 +1,66 @@
+[gd_scene load_steps=3 format=3 uid="uid://cwmhra3pt1h83"]
+
+[ext_resource type="Script" path="res://script/combo.gd" id="1_8i0ov"]
+[ext_resource type="PackedScene" uid="uid://c240icwf4uov8" path="res://scene/main.tscn" id="2_reiiv"]
+
+[node name="Combo" 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_8i0ov")
+
+[node name="GridContainer" type="GridContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+columns = 2
+
+[node name="Main" parent="GridContainer" instance=ExtResource("2_reiiv")]
+layout_mode = 2
+anchors_preset = 0
+anchor_right = 0.0
+anchor_bottom = 0.0
+offset_right = 574.0
+offset_bottom = 322.0
+grow_horizontal = 1
+grow_vertical = 1
+
+[node name="Main2" parent="GridContainer" instance=ExtResource("2_reiiv")]
+layout_mode = 2
+anchors_preset = 0
+anchor_right = 0.0
+anchor_bottom = 0.0
+offset_left = 578.0
+offset_right = 1152.0
+offset_bottom = 322.0
+grow_horizontal = 1
+grow_vertical = 1
+
+[node name="Main3" parent="GridContainer" instance=ExtResource("2_reiiv")]
+layout_mode = 2
+anchors_preset = 0
+anchor_right = 0.0
+anchor_bottom = 0.0
+offset_top = 326.0
+offset_right = 574.0
+offset_bottom = 648.0
+grow_horizontal = 1
+grow_vertical = 1
+
+[node name="Main4" parent="GridContainer" instance=ExtResource("2_reiiv")]
+layout_mode = 2
+anchors_preset = 0
+anchor_right = 0.0
+anchor_bottom = 0.0
+offset_left = 578.0
+offset_top = 326.0
+offset_right = 1152.0
+offset_bottom = 648.0
+grow_horizontal = 1
+grow_vertical = 1

+ 54 - 48
networking/websocket_multiplayer/scene/main.tscn

@@ -1,72 +1,83 @@
-[gd_scene load_steps=3 format=2]
+[gd_scene load_steps=3 format=3 uid="uid://c240icwf4uov8"]
 
-[ext_resource path="res://script/main.gd" type="Script" id=1]
-[ext_resource path="res://scene/game.tscn" type="PackedScene" id=2]
+[ext_resource type="Script" path="res://script/main.gd" id="1"]
+[ext_resource type="PackedScene" path="res://scene/game.tscn" id="2"]
 
 [node name="Main" type="Control"]
+layout_mode = 3
+anchors_preset = 15
 anchor_right = 1.0
 anchor_bottom = 1.0
-script = ExtResource( 1 )
-__meta__ = {
-"_edit_use_anchors_": false
-}
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("1")
 
 [node name="Panel" type="Panel" parent="."]
+anchors_preset = 15
 anchor_right = 1.0
 anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
 
 [node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
+anchors_preset = 15
 anchor_right = 1.0
 anchor_bottom = 1.0
 offset_left = 20.0
 offset_top = 20.0
 offset_right = -20.0
 offset_bottom = -20.0
+grow_horizontal = 2
+grow_vertical = 2
 
 [node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer"]
-offset_right = 984.0
-offset_bottom = 24.0
+offset_right = 1112.0
+offset_bottom = 31.0
 
 [node name="Label" type="Label" parent="Panel/VBoxContainer/HBoxContainer"]
-offset_top = 5.0
-offset_right = 326.0
-offset_bottom = 19.0
+offset_top = 2.0
+offset_right = 369.0
+offset_bottom = 28.0
 size_flags_horizontal = 3
 text = "Name"
 
 [node name="NameEdit" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer"]
-offset_left = 330.0
-offset_right = 984.0
-offset_bottom = 24.0
+offset_left = 373.0
+offset_right = 1112.0
+offset_bottom = 31.0
 size_flags_horizontal = 3
 size_flags_stretch_ratio = 2.0
 text = "A Godot User"
 
 [node name="HBoxContainer2" type="HBoxContainer" parent="Panel/VBoxContainer"]
-offset_top = 28.0
-offset_right = 984.0
-offset_bottom = 52.0
+offset_top = 35.0
+offset_right = 1112.0
+offset_bottom = 66.0
 
 [node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer/HBoxContainer2"]
-offset_right = 326.0
-offset_bottom = 24.0
+offset_right = 369.0
+offset_bottom = 31.0
 size_flags_horizontal = 3
 
 [node name="Host" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
-offset_right = 42.0
-offset_bottom = 24.0
+offset_right = 44.0
+offset_bottom = 31.0
 text = "Host"
 
 [node name="Control" type="Control" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
-offset_left = 46.0
-offset_right = 241.0
-offset_bottom = 24.0
+layout_mode = 3
+anchors_preset = 0
+offset_left = 48.0
+offset_right = 273.0
+offset_bottom = 31.0
 size_flags_horizontal = 3
 
 [node name="Connect" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
-offset_left = 245.0
-offset_right = 326.0
-offset_bottom = 24.0
+offset_left = 277.0
+offset_right = 369.0
+offset_bottom = 31.0
 text = "Connect to"
 
 [node name="Disconnect" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
@@ -77,36 +88,31 @@ offset_bottom = 24.0
 text = "Disconnect"
 
 [node name="Hostname" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer2"]
-offset_left = 330.0
-offset_right = 984.0
-offset_bottom = 24.0
+offset_left = 373.0
+offset_right = 1112.0
+offset_bottom = 31.0
 size_flags_horizontal = 3
 size_flags_stretch_ratio = 2.0
 text = "localhost"
 placeholder_text = "localhost"
 
 [node name="Control" type="Control" parent="Panel/VBoxContainer"]
-offset_top = 56.0
-offset_right = 984.0
-offset_bottom = 76.0
-rect_min_size = Vector2(0, 20)
-
-[node name="Game" parent="Panel/VBoxContainer" instance=ExtResource( 2 )]
+layout_mode = 3
+anchors_preset = 0
+offset_top = 70.0
+offset_right = 1112.0
+offset_bottom = 70.0
+
+[node name="Game" parent="Panel/VBoxContainer" instance=ExtResource("2")]
+layout_mode = 3
+anchors_preset = 0
 anchor_right = 0.0
 anchor_bottom = 0.0
-offset_top = 80.0
-offset_right = 984.0
-offset_bottom = 560.0
+offset_top = 74.0
+offset_right = 1112.0
+offset_bottom = 608.0
 
 [node name="AcceptDialog" type="AcceptDialog" parent="."]
-anchor_left = 0.5
-anchor_top = 0.5
-anchor_right = 0.5
-anchor_bottom = 0.5
-offset_left = -200.0
-offset_top = -100.0
-offset_right = 200.0
-offset_bottom = 100.0
 dialog_text = "Connection closed"
 
 [connection signal="pressed" from="Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Host" to="." method="_on_Host_pressed"]

+ 16 - 0
networking/websocket_multiplayer/script/combo.gd

@@ -0,0 +1,16 @@
+extends Control
+
+var paths := []
+
+func _enter_tree():
+	for ch in $GridContainer.get_children():
+		paths.append(NodePath(str(get_path()) + "/GridContainer/" + str(ch.name)))
+	# Sets a dedicated Multiplayer API for each branch.
+	for path in paths:
+		get_tree().set_multiplayer(MultiplayerAPI.create_default_interface(), path)
+
+
+func _exit_tree():
+	# Clear the branch-specific Multiplayer API.
+	for path in paths:
+		get_tree().set_multiplayer(null, path)

+ 45 - 33
networking/websocket_multiplayer/script/game.gd

@@ -5,36 +5,50 @@ const _crown = preload("res://img/crown.png")
 @onready var _list = $HBoxContainer/VBoxContainer/ItemList
 @onready var _action = $HBoxContainer/VBoxContainer/Action
 
+const ACTIONS = ["roll", "pass"]
+
 var _players = []
 var _turn = -1
 
-master func set_player_name(name):
-	var sender = get_tree().get_rpc_sender_id()
-	rpc("update_player_name", sender, name)
+@rpc func _log(what):
+	$HBoxContainer/RichTextLabel.add_text(what + "\n")
+
+
+@rpc(any_peer) func set_player_name(name):
+	if not is_multiplayer_authority():
+		return
+	var sender = multiplayer.get_remote_sender_id()
+	update_player_name.rpc(sender, name)
 
 
-remotesync func update_player_name(player, name):
+@rpc(call_local) func update_player_name(player, name):
 	var pos = _players.find(player)
 	if pos != -1:
 		_list.set_item_text(pos, name)
 
 
-master func request_action(action):
-	var sender = get_tree().get_rpc_sender_id()
-	if _players[_turn] != get_tree().get_rpc_sender_id():
-		rpc("_log", "Someone is trying to cheat! %s" % str(sender))
+@rpc(any_peer) func request_action(action):
+	if not is_multiplayer_authority():
+		return
+	var sender = multiplayer.get_remote_sender_id()
+	if _players[_turn] != sender:
+		_log.rpc("Someone is trying to cheat! %s" % str(sender))
 		return
+	if action not in ACTIONS:
+		_log.rpc("Invalid action: %s" % action)
+		return
+
 	do_action(action)
 	next_turn()
 
 
-remotesync func do_action(action):
-	var name = _list.get_item_text(_turn)
+func do_action(action):
+	var player_name = _list.get_item_text(_turn)
 	var val = randi() % 100
-	rpc("_log", "%s: %ss %d" % [name, action, val])
+	_log.rpc("%s: %ss %d" % [player_name, action, val])
 
 
-remotesync func set_turn(turn):
+@rpc(call_local) func set_turn(turn):
 	_turn = turn
 	if turn >= _players.size():
 		return
@@ -43,27 +57,27 @@ remotesync func set_turn(turn):
 			_list.set_item_icon(i, _crown)
 		else:
 			_list.set_item_icon(i, null)
-	_action.disabled = _players[turn] != get_tree().get_network_unique_id()
+	_action.disabled = _players[turn] != multiplayer.get_unique_id()
 
 
-remotesync func del_player(id):
+@rpc(call_local) func del_player(id):
 	var pos = _players.find(id)
 	if pos == -1:
 		return
-	_players.remove(pos)
+	_players.erase(pos)
 	_list.remove_item(pos)
 	if _turn > pos:
 		_turn -= 1
-	if get_tree().is_network_server():
-		rpc("set_turn", _turn)
+	if multiplayer.is_server():
+		set_turn.rpc(_turn)
 
 
-remotesync func add_player(id, name=""):
+@rpc(call_local) func add_player(id, pname=""):
 	_players.append(id)
-	if name == "":
+	if pname == "":
 		_list.add_item("... connecting ...", null, false)
 	else:
-		_list.add_item(name, null, false)
+		_list.add_item(pname, null, false)
 
 
 func get_player_name(pos):
@@ -77,7 +91,7 @@ func next_turn():
 	_turn += 1
 	if _turn >= _players.size():
 		_turn = 0
-	rpc("set_turn", _turn)
+	set_turn.rpc(_turn)
 
 
 func start():
@@ -92,27 +106,25 @@ func stop():
 
 
 func on_peer_add(id):
-	if not get_tree().is_network_server():
+	if not multiplayer.is_server():
 		return
 	for i in range(0, _players.size()):
-		rpc_id(id, "add_player", _players[i], get_player_name(i))
-	rpc("add_player", id)
-	rpc_id(id, "set_turn", _turn)
+		add_player.rpc_id(id, _players[i], get_player_name(i))
+	add_player.rpc(id)
+	set_turn.rpc_id(id, _turn)
 
 
 func on_peer_del(id):
-	if not get_tree().is_network_server():
+	if not multiplayer.is_server():
 		return
-	rpc("del_player", id)
-
-
-remotesync func _log(what):
-	$HBoxContainer/RichTextLabel.add_text(what + "\n")
+	del_player.rpc(id)
 
 
 func _on_Action_pressed():
-	if get_tree().is_network_server():
+	if multiplayer.is_server():
+		if _turn != 0:
+			return
 		do_action("roll")
 		next_turn()
 	else:
-		rpc_id(1, "request_action", "roll")
+		request_action.rpc_id(1, "roll")

+ 26 - 25
networking/websocket_multiplayer/script/main.gd

@@ -10,14 +10,22 @@ const PROTO_NAME = "ludus"
 @onready var _host_edit = $Panel/VBoxContainer/HBoxContainer2/Hostname
 @onready var _game = $Panel/VBoxContainer/Game
 
-var peer = null
+var peer = WebSocketMultiplayerPeer.new()
+
+
+func _init():
+	peer.supported_protocols = ["ludus"]
+
 
 func _ready():
-	#warning-ignore-all:return_value_discarded
-	get_tree().connect(&"network_peer_disconnected", self._peer_disconnected)
-	get_tree().connect(&"network_peer_connected", self._peer_connected)
-	$AcceptDialog.get_label().align = Label.ALIGN_CENTER
-	$AcceptDialog.get_label().valign = Label.VALIGN_CENTER
+	multiplayer.peer_connected.connect(_peer_connected)
+	multiplayer.peer_disconnected.connect(_peer_disconnected)
+	multiplayer.server_disconnected.connect(_close_network)
+	multiplayer.connection_failed.connect(_close_network)
+	multiplayer.connected_to_server.connect(_connected)
+
+	$AcceptDialog.get_label().horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+	$AcceptDialog.get_label().vertical_alignment = VERTICAL_ALIGNMENT_CENTER
 	# Set the player name according to the system username. Fallback to the path.
 	if OS.has_environment("USERNAME"):
 		_name_edit.text = OS.get_environment("USERNAME")
@@ -45,20 +53,15 @@ func stop_game():
 
 
 func _close_network():
-	if get_tree().is_connected("server_disconnected", self, "_close_network"):
-		get_tree().disconnect(&"server_disconnected", self._close_network)
-	if get_tree().is_connected("connection_failed", self, "_close_network"):
-		get_tree().disconnect(&"connection_failed", self._close_network)
-	if get_tree().is_connected("connected_to_server", self, "_connected"):
-		get_tree().disconnect(&"connected_to_server", self._connected)
 	stop_game()
-	$AcceptDialog.show_modal()
-	$AcceptDialog.get_close_button().grab_focus()
-	get_tree().set_network_peer(null)
+	$AcceptDialog.popup_centered()
+	$AcceptDialog.get_ok_button().grab_focus()
+	multiplayer.multiplayer_peer = null
+	peer.close()
 
 
 func _connected():
-	_game.rpc("set_player_name", _name_edit.text)
+	_game.set_player_name.rpc(_name_edit.text)
 
 
 func _peer_connected(id):
@@ -66,14 +69,14 @@ func _peer_connected(id):
 
 
 func _peer_disconnected(id):
+	print("Disconnected %d" % id)
 	_game.on_peer_del(id)
 
 
 func _on_Host_pressed():
-	peer = WebSocketServer.new()
-	peer.listen(DEF_PORT, PackedStringArray(["ludus"]), true)
-	get_tree().connect(&"server_disconnected", self._close_network)
-	get_tree().set_network_peer(peer)
+	multiplayer.multiplayer_peer = null
+	peer.create_server(DEF_PORT)
+	multiplayer.multiplayer_peer = peer
 	_game.add_player(1, _name_edit.text)
 	start_game()
 
@@ -83,9 +86,7 @@ func _on_Disconnect_pressed():
 
 
 func _on_Connect_pressed():
-	peer = WebSocketClient.new()
-	peer.connect_to_url("ws://" + _host_edit.text + ":" + str(DEF_PORT), PackedStringArray([PROTO_NAME]), true)
-	get_tree().connect(&"connection_failed", self._close_network)
-	get_tree().connect(&"connected_to_server", self._connected)
-	get_tree().set_network_peer(peer)
+	multiplayer.multiplayer_peer = null
+	peer.create_client("ws://" + _host_edit.text + ":" + str(DEF_PORT))
+	multiplayer.multiplayer_peer = peer
 	start_game()