mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2026-01-04 23:10:08 +01:00
[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).
This commit is contained in:
@@ -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
networking/websocket_multiplayer/scene/combo.tscn
Normal file
66
networking/websocket_multiplayer/scene/combo.tscn
Normal file
@@ -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
|
||||
@@ -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)
|
||||
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 )]
|
||||
[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
networking/websocket_multiplayer/script/combo.gd
Normal file
16
networking/websocket_multiplayer/script/combo.gd
Normal file
@@ -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)
|
||||
@@ -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")
|
||||
|
||||
|
||||
remotesync func update_player_name(player, name):
|
||||
@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)
|
||||
|
||||
|
||||
@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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user