Bläddra i källkod

Add WebSocket high level multiplayer demo.

Fabio Alessandrelli 6 år sedan
förälder
incheckning
359ef78ee0

+ 3 - 0
networking/websocket_multiplayer/.gitignore

@@ -0,0 +1,3 @@
+.import/*
+*.import
+export_presets.cfg

+ 20 - 0
networking/websocket_multiplayer/default_env.tres

@@ -0,0 +1,20 @@
+[gd_resource type="Environment" load_steps=2 format=2]
+
+[sub_resource type="ProceduralSky" id=1]
+sky_top_color = Color( 0.0470588, 0.454902, 0.976471, 1 )
+sky_horizon_color = Color( 0.556863, 0.823529, 0.909804, 1 )
+sky_curve = 0.25
+ground_bottom_color = Color( 0.101961, 0.145098, 0.188235, 1 )
+ground_horizon_color = Color( 0.482353, 0.788235, 0.952941, 1 )
+ground_curve = 0.01
+sun_energy = 16.0
+__meta__ = {
+
+}
+
+[resource]
+background_mode = 2
+background_sky = SubResource( 1 )
+__meta__ = {
+
+}

BIN
networking/websocket_multiplayer/icon.png


BIN
networking/websocket_multiplayer/img/crown.png


+ 24 - 0
networking/websocket_multiplayer/project.godot

@@ -0,0 +1,24 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+;   [section] ; section goes between []
+;   param=value ; assign values to parameters
+
+config_version=4
+
+_global_script_classes=[  ]
+_global_script_class_icons={
+
+}
+
+[application]
+
+config/name="Websocket Multiplayer Demo"
+run/main_scene="res://scene/main.tscn"
+config/icon="res://icon.png"
+
+[rendering]
+
+environment/default_environment="res://default_env.tres"

+ 68 - 0
networking/websocket_multiplayer/scene/game.tscn

@@ -0,0 +1,68 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://script/game.gd" type="Script" id=1]
+
+[node name="Game" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+mouse_filter = 1
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+
+}
+
+[node name="RichTextLabel" type="RichTextLabel" parent="HBoxContainer"]
+margin_right = 510.0
+margin_bottom = 600.0
+size_flags_horizontal = 3
+__meta__ = {
+
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"]
+margin_left = 514.0
+margin_right = 1024.0
+margin_bottom = 600.0
+size_flags_horizontal = 3
+__meta__ = {
+
+}
+
+[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer"]
+margin_right = 510.0
+margin_bottom = 14.0
+text = "Players:"
+__meta__ = {
+
+}
+
+[node name="ItemList" type="ItemList" parent="HBoxContainer/VBoxContainer"]
+margin_top = 18.0
+margin_right = 510.0
+margin_bottom = 576.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+same_column_width = true
+__meta__ = {
+
+}
+
+[node name="Action" type="Button" parent="HBoxContainer/VBoxContainer"]
+margin_top = 580.0
+margin_right = 510.0
+margin_bottom = 600.0
+disabled = true
+text = "Do Action!"
+__meta__ = {
+
+}
+[connection signal="pressed" from="HBoxContainer/VBoxContainer/Action" to="." method="_on_Action_pressed"]

+ 155 - 0
networking/websocket_multiplayer/scene/main.tscn

@@ -0,0 +1,155 @@
+[gd_scene load_steps=3 format=2]
+
+[ext_resource path="res://script/main.gd" type="Script" id=1]
+[ext_resource path="res://scene/game.tscn" type="PackedScene" id=2]
+
+[node name="Control" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 1 )
+__meta__ = {
+
+}
+
+[node name="Panel" type="Panel" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+
+}
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+margin_left = 20.0
+margin_top = 20.0
+margin_right = -20.0
+margin_bottom = -20.0
+__meta__ = {
+
+}
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer"]
+margin_right = 984.0
+margin_bottom = 24.0
+__meta__ = {
+
+}
+
+[node name="Label" type="Label" parent="Panel/VBoxContainer/HBoxContainer"]
+margin_top = 5.0
+margin_right = 326.0
+margin_bottom = 19.0
+size_flags_horizontal = 3
+text = "Name"
+__meta__ = {
+
+}
+
+[node name="NameEdit" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer"]
+margin_left = 330.0
+margin_right = 984.0
+margin_bottom = 24.0
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 2.0
+text = "A Godot User"
+__meta__ = {
+
+}
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/VBoxContainer"]
+margin_top = 28.0
+margin_right = 984.0
+margin_bottom = 52.0
+__meta__ = {
+
+}
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer/HBoxContainer2"]
+margin_right = 326.0
+margin_bottom = 24.0
+size_flags_horizontal = 3
+__meta__ = {
+
+}
+
+[node name="Host" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
+margin_right = 42.0
+margin_bottom = 24.0
+text = "Host"
+__meta__ = {
+
+}
+
+[node name="Control" type="Control" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
+margin_left = 46.0
+margin_right = 241.0
+margin_bottom = 24.0
+size_flags_horizontal = 3
+__meta__ = {
+
+}
+
+[node name="Connect" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
+margin_left = 245.0
+margin_right = 326.0
+margin_bottom = 24.0
+text = "Connect to"
+__meta__ = {
+
+}
+
+[node name="Disconnect" type="Button" parent="Panel/VBoxContainer/HBoxContainer2/HBoxContainer"]
+visible = false
+margin_left = 68.0
+margin_right = 152.0
+margin_bottom = 24.0
+text = "Disconnect"
+__meta__ = {
+
+}
+
+[node name="Hostname" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer2"]
+margin_left = 330.0
+margin_right = 984.0
+margin_bottom = 24.0
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 2.0
+text = "localhost"
+placeholder_text = "localhost"
+__meta__ = {
+
+}
+
+[node name="Control" type="Control" parent="Panel/VBoxContainer"]
+margin_top = 56.0
+margin_right = 984.0
+margin_bottom = 76.0
+rect_min_size = Vector2( 0, 20 )
+__meta__ = {
+
+}
+
+[node name="Game" parent="Panel/VBoxContainer" instance=ExtResource( 2 )]
+anchor_right = 0.0
+anchor_bottom = 0.0
+margin_top = 80.0
+margin_right = 984.0
+margin_bottom = 560.0
+
+[node name="AcceptDialog" type="AcceptDialog" parent="."]
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+margin_left = -200.0
+margin_top = -100.0
+margin_right = 200.0
+margin_bottom = 100.0
+dialog_text = "Connection closed"
+__meta__ = {
+
+}
+[connection signal="pressed" from="Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Host" to="." method="_on_Host_pressed"]
+[connection signal="pressed" from="Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Connect" to="." method="_on_Connect_pressed"]
+[connection signal="pressed" from="Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Disconnect" to="." method="_on_Disconnect_pressed"]

+ 104 - 0
networking/websocket_multiplayer/script/game.gd

@@ -0,0 +1,104 @@
+extends Control
+
+const _crown = preload("res://img/crown.png")
+
+onready var _list = $HBoxContainer/VBoxContainer/ItemList
+onready var _action = $HBoxContainer/VBoxContainer/Action
+
+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)
+
+sync 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))
+		return
+	do_action(action)
+	next_turn()
+
+sync func do_action(action):
+	var name = _list.get_item_text(_turn)
+	var val = randi() % 100
+	rpc("_log", "%s: %ss %d" % [name, action, val])
+
+sync func set_turn(turn):
+	_turn = turn
+	if turn >= _players.size():
+		return
+	for i in range(0, _players.size()):
+		if i == turn:
+			_list.set_item_icon(i, _crown)
+		else:
+			_list.set_item_icon(i, null)
+	_action.disabled = _players[turn] != get_tree().get_network_unique_id()
+
+sync func del_player(id):
+	var pos = _players.find(id)
+	if pos == -1:
+		return
+	_players.remove(pos)
+	_list.remove_item(pos)
+	if _turn > pos:
+		_turn -= 1
+	if get_tree().is_network_server():
+		rpc("set_turn", _turn)
+
+sync func add_player(id, name=""):
+	_players.append(id)
+	if name == "":
+		_list.add_item("... connecting ...", null, false)
+	else:
+		_list.add_item(name, null, false)
+
+func get_player_name(pos):
+	if pos < _list.get_item_count():
+		return _list.get_item_text(pos)
+	else:
+		return "Error!"
+
+func next_turn():
+	_turn += 1
+	if _turn >= _players.size():
+		_turn = 0
+	rpc("set_turn", _turn)
+
+func start():
+	set_turn(0)
+
+func stop():
+	_players.clear()
+	_list.clear()
+	_turn = 0
+	_action.disabled = true
+
+func on_peer_add(id):
+	if not get_tree().is_network_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)
+
+func on_peer_del(id):
+	if not get_tree().is_network_server():
+		return
+	rpc("del_player", id)
+
+sync func _log(what):
+	$HBoxContainer/RichTextLabel.add_text(what + "\n")
+
+func _on_Action_pressed():
+	if get_tree().is_network_server():
+		do_action("roll")
+		next_turn()
+	else:
+		rpc_id(1, "request_action", "roll")

+ 73 - 0
networking/websocket_multiplayer/script/main.gd

@@ -0,0 +1,73 @@
+extends Control
+
+const DEF_PORT = 8080
+const PROTO_NAME = "ludus"
+
+onready var _host_btn = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Host
+onready var _connect_btn = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Connect
+onready var _disconnect_btn = $Panel/VBoxContainer/HBoxContainer2/HBoxContainer/Disconnect
+onready var _name_edit = $Panel/VBoxContainer/HBoxContainer/NameEdit
+onready var _host_edit = $Panel/VBoxContainer/HBoxContainer2/Hostname
+onready var _game = $Panel/VBoxContainer/Game
+
+func _ready():
+	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
+
+func start_game():
+	_host_btn.disabled = true
+	_name_edit.editable = false
+	_host_edit.editable = false
+	_connect_btn.hide()
+	_disconnect_btn.show()
+	_game.start()
+
+func stop_game():
+	_host_btn.disabled = false
+	_name_edit.editable = true
+	_host_edit.editable = true
+	_disconnect_btn.hide()
+	_connect_btn.show()
+	_game.stop()
+
+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)
+
+func _connected():
+	_game.rpc("set_player_name", _name_edit.text)
+
+func _peer_connected(id):
+	_game.on_peer_add(id)
+
+func _peer_disconnected(id):
+	_game.on_peer_del(id)
+
+func _on_Host_pressed():
+	var host = WebSocketServer.new()
+	host.listen(DEF_PORT, PoolStringArray(["ludus"]), true)
+	get_tree().connect("server_disconnected", self, "_close_network")
+	get_tree().set_network_peer(host)
+	_game.add_player(1, _name_edit.text)
+	start_game()
+
+func _on_Disconnect_pressed():
+	_close_network()
+
+func _on_Connect_pressed():
+	var host = WebSocketClient.new()
+	host.connect_to_url("ws://" + _host_edit.text + ":" + str(DEF_PORT), PoolStringArray([PROTO_NAME]), true)
+	get_tree().connect("connection_failed", self, "_close_network")
+	get_tree().connect("connected_to_server", self, "_connected")
+	get_tree().set_network_peer(host)
+	start_game()