diff --git a/project.godot b/project.godot index 3c3b8dd..58b0109 100644 --- a/project.godot +++ b/project.godot @@ -26,7 +26,7 @@ _global_script_class_icons={ [application] -config/name="kdt" +config/name="webrtc_tech_demo" run/main_scene="res://screens/main_menu.tscn" config/icon="res://assets/img/icon.png" @@ -36,7 +36,7 @@ Global="*res://scripts/global/global.gd" [gdnative] -singletons=[ "res://webrtc/webrtc_debug.tres" ] +singletons=[ "res://webrtc/webrtc.tres" ] [physics] diff --git a/screens/main_menu.tscn b/screens/main_menu.tscn index 4c1d6a1..02f80b4 100644 --- a/screens/main_menu.tscn +++ b/screens/main_menu.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=4 format=2] +[gd_scene load_steps=5 format=2] [ext_resource path="res://assets/fonts/iosevkalyte/iosevkalyte-regular.ttf" type="DynamicFontData" id=1] +[ext_resource path="res://assets/img/cross.png" type="Texture" id=2] [ext_resource path="res://scripts/screens/main_menu.gd" type="Script" id=3] [sub_resource type="DynamicFont" id=1] @@ -19,16 +20,16 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="VBoxContainer" type="VBoxContainer" parent="."] +[node name="v" type="VBoxContainer" parent="."] margin_right = 984.0 margin_bottom = 560.0 -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +[node name="h" type="HBoxContainer" parent="v"] margin_right = 984.0 margin_bottom = 50.0 alignment = 1 -[node name="quit" type="Button" parent="VBoxContainer/HBoxContainer"] +[node name="quit" type="Button" parent="v/h"] margin_right = 325.0 margin_bottom = 50.0 rect_min_size = Vector2( 150, 50 ) @@ -36,11 +37,13 @@ hint_tooltip = "Yeah, let's get out of here." size_flags_horizontal = 3 size_flags_vertical = 3 text = "Quit" +icon = ExtResource( 2 ) +expand_icon = true __meta__ = { "_edit_use_anchors_": false } -[node name="multiplayer" type="Button" parent="VBoxContainer/HBoxContainer"] +[node name="multiplayer" type="Button" parent="v/h"] margin_left = 329.0 margin_right = 654.0 margin_bottom = 50.0 @@ -50,7 +53,7 @@ size_flags_horizontal = 3 size_flags_vertical = 3 text = "Multiplayer" -[node name="Singleplayer" type="Button" parent="VBoxContainer/HBoxContainer"] +[node name="singleplayer" type="Button" parent="v/h"] margin_left = 658.0 margin_right = 984.0 margin_bottom = 50.0 @@ -65,20 +68,20 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="Container" type="CenterContainer" parent="VBoxContainer"] +[node name="c" type="CenterContainer" parent="v"] margin_top = 54.0 margin_right = 984.0 margin_bottom = 560.0 size_flags_horizontal = 3 size_flags_vertical = 3 -[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Container"] +[node name="v" type="VBoxContainer" parent="v/c"] margin_left = 124.0 margin_top = 153.0 margin_right = 859.0 margin_bottom = 352.0 -[node name="Label" type="Label" parent="VBoxContainer/Container/VBoxContainer"] +[node name="title" type="Label" parent="v/c/v"] margin_right = 735.0 margin_bottom = 181.0 custom_fonts/font = SubResource( 1 ) @@ -87,26 +90,26 @@ Multiplayer Tech Demo" align = 1 valign = 1 -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Container/VBoxContainer"] +[node name="h" type="HBoxContainer" parent="v/c/v"] margin_top = 185.0 margin_right = 735.0 margin_bottom = 199.0 alignment = 1 -[node name="Label" type="Label" parent="VBoxContainer/Container/VBoxContainer/HBoxContainer"] +[node name="made_by" type="Label" parent="v/c/v/h"] margin_left = 312.0 margin_right = 371.0 margin_bottom = 14.0 text = "made by " -[node name="LinkButton" type="LinkButton" parent="VBoxContainer/Container/VBoxContainer/HBoxContainer"] +[node name="link" type="LinkButton" parent="v/c/v/h"] margin_left = 375.0 margin_right = 422.0 margin_bottom = 14.0 hint_tooltip = "Open https://lyte.dev" text = "lytedev" -[connection signal="pressed" from="VBoxContainer/HBoxContainer/quit" to="." method="_on_JoinLobbyButton_pressed"] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/multiplayer" to="." method="_on_multiplayer_pressed"] -[connection signal="pressed" from="VBoxContainer/HBoxContainer/Singleplayer" to="." method="_on_Singleplayer_pressed"] -[connection signal="pressed" from="VBoxContainer/Container/VBoxContainer/HBoxContainer/LinkButton" to="." method="_on_LinkButton_pressed"] +[connection signal="pressed" from="v/h/quit" to="." method="_on_JoinLobbyButton_pressed"] +[connection signal="pressed" from="v/h/multiplayer" to="." method="_on_multiplayer_pressed"] +[connection signal="pressed" from="v/h/singleplayer" to="." method="_on_Singleplayer_pressed"] +[connection signal="pressed" from="v/c/v/h/link" to="." method="_on_LinkButton_pressed"] diff --git a/scripts/global/global.gd b/scripts/global/global.gd index e22ad9d..8f2c568 100644 --- a/scripts/global/global.gd +++ b/scripts/global/global.gd @@ -27,6 +27,7 @@ func check_onetime_flag(flag): return result func _ready(): + signaller_client.connect("connected", self, "_signaller_connected") negotiator.signaller_client = signaller_client negotiator.ice_servers = ice_servers add_child(negotiator) @@ -63,7 +64,6 @@ func _signaller_connected(): func lobby_browser(): negotiator.close() - signaller_client.connect("connected", self, "_signaller_connected") negotiator.connect_to_signaller() func lobby(): @@ -72,3 +72,9 @@ func lobby(): func quit(): negotiator.close() get_tree().quit() + +func key_shortcut(code): + var sc = ShortCut.new() + sc.shortcut = InputEventKey.new() + sc.shortcut.scancode = code + return sc diff --git a/scripts/global/signaller_client.gd b/scripts/global/signaller_client.gd index ef6d3eb..06ade4a 100644 --- a/scripts/global/signaller_client.gd +++ b/scripts/global/signaller_client.gd @@ -10,14 +10,15 @@ const DISPLAY_NAME_FILE = "user://display_name.txt" const DEFAULT_DISPLAY_NAME = "UnnamedPlayer" # lobby data -signal lobby_data_received(lobbies) +signal lobby_data(lobbies) signal lobby_joined(lobby_and_peer) signal lobby_left(id) signal lobby_delete(id) # peer data -signal peer_data_received(peers) +signal peer_data(peers) signal peer_left(id) +signal peer_init(peer_id) # WebRTC negotiations signal candidate_received(cand) @@ -36,6 +37,8 @@ onready var client_id = null onready var display_name = DEFAULT_DISPLAY_NAME setget set_display_name onready var peer_id = null +onready var lobby_data = null + var websocket_url = null func _init(url): @@ -96,6 +99,8 @@ func send_offer(peerId, offer) -> int: func send_answer(peerId, answer) -> int: return _send_json({"peerId": peerId, "answer": answer }, "answer") +func get_lobby_name(): return lobby_data.name + func _closed(code): print("WebSocket closed: %s: " % code) emit_signal("disconnected") @@ -123,44 +128,45 @@ func _handle_data(): _process_message(data.result) var _result = websocket.get_peer(1).put_packet("".to_utf8()) else: - print("Unhandled message: %s" % msg) + var d = msg.split(":", false, 2) + _process_message({type = d[0], data = d[1]}) func _process_message(data: Dictionary): match data["type"]: "init": client_id = data.id print("Signaller Received init response: %s" % data) - "your_peer_id": - peer_id = int(data.peerId) - emit_signal("peer_id_set", peer_id) + "init_peer": + print("init_peer: %s" % data) + peer_id = int(data.data) + emit_signal("peer_init", peer_id) - "lobby_data": emit_signal("lobby_data_received", data.data) + "lobby_data": emit_signal("lobby_data", data.data) "lobby_delete": emit_signal("lobby_delete", data.id) "lobby_joined": - peer_id = data.peerId + print("lobby_joined: %s" % data) + lobby_data = data emit_signal("lobby_joined", data) "lobby_left": peer_id = null emit_signal("lobby_left", data["id"]) "peer_data": - print(typeof(data), data) - if data.get("id") != null: emit_signal("peer_joined", [data]) - else: emit_signal("peer_joined", data["data"]) + emit_signal("peer_data", [data] if data.has("id") else data["data"]) "peer_left": print("Peer Left: %s" % data) emit_signal("peer_left", data) "candidate": - print("Candidate received - Data: %s" % JSON.print(data["data"])) - emit_signal("candidate_received", data["data"]) + print("Candidate received - Data: %s" % JSON.print(data)) + emit_signal("candidate_received", data) "offer": - print("Offer received - Data: %s" % JSON.print(data["data"])) - emit_signal("offer_received", data["data"]) + print("Offer received - Data: %s" % JSON.print(data)) + emit_signal("offer_received", data) "answer": - print("Answer received - Data: %s" % JSON.print(data["data"])) - emit_signal("answer_received", data["data"]) + print("Answer received - Data: %s" % JSON.print(data)) + emit_signal("answer_received", data) "ping": _send("pong") _: print("Unhandled Message - Data: %s" % JSON.print(data)) diff --git a/scripts/global/webrtc_negotiator.gd b/scripts/global/webrtc_negotiator.gd index c7d5044..b2645da 100644 --- a/scripts/global/webrtc_negotiator.gd +++ b/scripts/global/webrtc_negotiator.gd @@ -12,23 +12,20 @@ var ice_servers = null func _init(_ice_servers, _signaller_client): ice_servers = _ice_servers signaller_client = _signaller_client + print("Initializing WebRTCNegotiator with ICE servers: %s" % ice_servers) func _ready(): signaller_client.connect("offer_received", self, "_offer_received") signaller_client.connect("answer_received", self, "_answer_received") signaller_client.connect("candidate_received", self, "_candidate_received") - signaller_client.connect("connected", self, "_signaller_connected") # signaller_client.connect("websocket_disconnected", self, "disconnected") - signaller_client.connect("peer_id_set", self, "_peer_id_set") - signaller_client.connect("peer_joined", self, "_peer_joined") + signaller_client.connect("peer_init", self, "_peer_id_set") + signaller_client.connect("peer_data", self, "_peer_data") # webrtc.connect("peer_disconnected", self, "peer_disconnected") # connect("lobby_sealed", self, "lobby_sealed") add_child(self.signaller_client) -func _signaller_connected(): - print("Connected to signaller. Will be using the following ICE servers: %s", ice_servers) - func close(): webrtc.close() signaller_client.close() @@ -52,30 +49,27 @@ func _create_peer(peer_id): print("Did not create offer: %d <= %d" % [peer_id, uid]) return peer -func _new_ice_candidate(mid_name, index_name, sdp_name, peer_id): - print("New ICE Candidate - MID: %s, Index: %s, SDP: %s, PeerID: %s" % [mid_name, index_name, sdp_name, peer_id]) - signaller_client.send_candidate(peer_id, mid_name, index_name, sdp_name) +func _new_ice_candidate(mid, index, sdp, peer_id): + print("New ICE Candidate - MID: %s, Index: %s, SDP: %s, PeerID: %s" % [mid, index, sdp, peer_id]) + signaller_client.send_candidate(peer_id, mid, index, sdp) func _offer_created(type, data, peer_id): - print("Offer created - Type: %s, PeerID: %s, Data: %s" % [type, peer_id, JSON.print(data)]) - if not webrtc.has_peer(peer_id): - return + print("%s created - PeerID: %s, Data: %s" % [type, peer_id, JSON.print(data)]) + if not webrtc.has_peer(peer_id): return webrtc.get_peer(peer_id).connection.set_local_description(type, data) - if type == "offer": - signaller_client.send_offer(peer_id, data) - else: - signaller_client.send_answer(peer_id, data) + if type == "offer": signaller_client.send_offer(peer_id, data) + else: signaller_client.send_answer(peer_id, data) func _peer_id_set(peer_id): print("Peer ID received %s - setting up to peer..." % [peer_id]) webrtc.initialize(peer_id, true) get_tree().network_peer = webrtc -func _peer_joined(peers): +func _peer_data(peers): for i in range(len(peers)): var peer = peers[i] if peer.has("peerId"): - var peer_id = peer["peerId"] + var peer_id = peer.peerId if not webrtc.has_peer(peer_id): _create_peer(peer_id) @@ -84,24 +78,24 @@ func peer_disconnected(id): if webrtc.has_peer(id): webrtc.remove_peer(id) -func offer_received(data): - var id = data["peerId"] +func _offer_received(data): + var id = data.peerId if webrtc.has_peer(id): - print("Setting offer remote description: %s" % JSON.print(data)) - webrtc.get_peer(id).connection.set_remote_description("offer", data["offer"]) + print("Setting offer remote description: %s" % JSON.print(data.offer)) + webrtc.get_peer(id).connection.set_remote_description("offer", data.offer) else: print("Received an offer for a peer with ID %s that hasn't been added" % id) -func answer_received(data): - var id = data["peerId"] +func _answer_received(data): + var id = data.peerId if webrtc.has_peer(id): print("Setting answer remote description: %s" % JSON.print(data)) - webrtc.get_peer(id).connection.set_remote_description("answer", data["answer"]) + webrtc.get_peer(id).connection.set_remote_description("answer", data.answer) -func candidate_received(data): - var id = data["peerId"] +func _candidate_received(data): + var id = data.peerId if webrtc.has_peer(id): print("Adding ice candidate: %s" % JSON.print(data)) - webrtc.get_peer(id).connection.add_ice_candidate(data["mid"], data["index"], data["sdp"]) + webrtc.get_peer(id).connection.add_ice_candidate(data.mid, data.index, data.sdp) else: print("Received candidate for non-existant peer %s" % id) diff --git a/scripts/objects/lobby.gd b/scripts/objects/lobby.gd index 34aabac..b6730f1 100644 --- a/scripts/objects/lobby.gd +++ b/scripts/objects/lobby.gd @@ -34,4 +34,4 @@ func set_current_players(n): _update_text() func _on_join_pressed(): - Global.client.signaller.join_lobby(id) + Global.signaller_client.join_lobby(id) diff --git a/scripts/screens/lobby_browser.gd b/scripts/screens/lobby_browser.gd index 1c3dd0d..5dfd9c9 100644 --- a/scripts/screens/lobby_browser.gd +++ b/scripts/screens/lobby_browser.gd @@ -15,23 +15,16 @@ onready var lobby = preload("res://objects/lobby.tscn") func _ready(): display_name_edit.text = Global.signaller_client.display_name - Global.signaller_client.connect("lobby_new", self, "_lobby_new") + Global.signaller_client.connect("lobby_data", self, "_lobby_data") Global.signaller_client.connect("lobby_delete", self, "_lobby_delete") Global.signaller_client.connect("lobby_joined", self, "_lobby_joined") Global.signaller_client.connect("lobby_left", self, "_lobby_left") - Global.signaller_client.connect("websocket_disconnected", self, "_signaller_disconnected") + Global.signaller_client.connect("disconnected", self, "_signaller_disconnected") Global.signaller_client.request_lobby_list() if Global.check_onetime_flag("create-lobby"): Global.signaller_client.create_lobby() - """ - var ls = [] - for n in range(200): - ls.push_back({id = n, name = n, currentPlayers = n, maxPlayers = n, locked = false}) - call_deferred("_lobby_new", ls) - """ - # TODO: add search func _input(ev): pass @@ -46,7 +39,8 @@ func _input(ev): func _signaller_disconnected(): Global.main_menu() -func _lobby_joined(_id, _peerId): +func _lobby_joined(data): + print("Lobby Joined: %s" % data) Global.lobby() func _lobby_left(_id): @@ -63,19 +57,14 @@ func _on_join_pressed(): if len(items) > 0: Global.signaller_client.join_lobby(lobbies.get_item_metadata(items[0])["id"]) -func _lobby_new(new_lobbies: Array): +func _lobby_data(new_lobbies: Array): # TODO: handle scrolling so that the user is never too jarred (like chat) - for lobby_index in range(len(new_lobbies)): - var lobby_data = new_lobbies[lobby_index] - var id = lobby_data["id"] - if lobbies.has(id): - _update_lobby(id, lobby_data) - else: - _add_lobby(id, lobby_data) + for l in new_lobbies: + if lobbies.has(l.id): _update_lobby(l.id, l) + else: _add_lobby(l.id, l) - if Global.join_first_available_lobby: - Global.join_first_available_lobby = false - Global.signaller_client.join_lobby(id) + if Global.check_onetime_flag("join-first-available-lobby"): + Global.signaller_client.join_lobby(l.id) func _lobby_delete(id: String): print("Lobby Deleted: %s" % id) diff --git a/scripts/screens/main_menu.gd b/scripts/screens/main_menu.gd index c07409c..962ce7f 100644 --- a/scripts/screens/main_menu.gd +++ b/scripts/screens/main_menu.gd @@ -1,6 +1,9 @@ extends Node func _ready(): + $v/h/quit.shortcut = Global.key_shortcut(KEY_ESCAPE) + $v/h/multiplayer.shortcut = Global.key_shortcut(KEY_M) + $v/h/singleplayer.shortcut = Global.key_shortcut(KEY_S) if Global.check_onetime_flag("multiplayer"): _on_multiplayer_pressed() diff --git a/scripts/screens/multiplayer_lobby.gd b/scripts/screens/multiplayer_lobby.gd index 883fe85..f243419 100644 --- a/scripts/screens/multiplayer_lobby.gd +++ b/scripts/screens/multiplayer_lobby.gd @@ -16,28 +16,27 @@ onready var peers = {} onready var is_host = false func _ready(): - Global.client.signaller.connect("peer_joined", self, "_peer_joined") - Global.client.signaller.connect("peer_left", self, "_peer_left") - Global.client.signaller.connect("lobby_left", self, "_lobby_left") - Global.client.signaller.connect("lobby_new", self, "_lobby_update") + Global.signaller_client.connect("peer_data", self, "_peer_data") + Global.signaller_client.connect("peer_left", self, "_peer_left") + Global.signaller_client.connect("lobby_left", self, "_lobby_left") + Global.signaller_client.connect("lobby_data", self, "_lobby_data") - Global.client.signaller.connect("websocket_disconnected", self, "_signaller_disconnected") + Global.signaller_client.connect("disconnected", self, "_signaller_disconnected") - lobby_name.text = "%s" % Global.client.signaller.lobby_name + # lobby_name.text = "%s" % Global.signaller_client.lobby_name - Global.client.signaller.call_deferred("request_peer_list") - call_deferred("add_chat", "# Connected to %s" % Global.client.signaller.lobby_name) + Global.signaller_client.call_deferred("request_peer_list") + call_deferred("add_chat", "# Connected to %s" % Global.signaller_client.get_lobby_name()) # hide/show controls depending on whether or not we're the host - is_host = Global.client.signaller.peer_id == 1 - if is_host: - ready_up.queue_free() + is_host = Global.signaller_client.is_host() + if is_host: ready_up.queue_free() else: lobby_name.editable = false max_players.editable = false start.queue_free() lock.queue_free() - print("Setting up lobby... %s %s" % [Global.client.signaller.peer_id, is_host]) + print("Setting up lobby... %s %s" % [Global.signaller_client.peer_id, is_host]) func _lobby_update(l): for u in l: @@ -47,20 +46,15 @@ func _lobby_update(l): print(l) break -func _peer_joined(joined_peers): +func _peer_data(peers): call_deferred("update_player_count") if is_host: call_deferred("update_can_start") - for peer_index in range(len(joined_peers)): - var peer_data = joined_peers[peer_index] - var id = peer_data["id"] - if peers.has(id): - _update_peer(id, peer_data) - else: - _add_peer(id, peer_data) + for p in peers: + if peers.has(p.id): _update_peer(p.id, p) + else: _add_peer(p.id, p) func _add_peer(id, peer_data): call_deferred("update_player_count") - var new_peer = peer.instance() new_peer.set_with_dict(peer_data) peers_grid.add_child(new_peer) @@ -68,9 +62,10 @@ func _add_peer(id, peer_data): add_chat("> %s joined the lobby" % new_peer.display_name) func _update_peer(id, peer_data): - if peer_data.ready != peers[id].ready: add_chat("! %s is %s" % [peers[id].display_name, "now ready" if peer_data.ready else "no longer ready"]) + if peer_data.ready != peers[id].ready: + var update_text = "now ready" if peer_data.ready else "no longer ready" + add_chat("! %s is %s" % [peers[id].display_name, update_text]) peers[id].set_with_dict(peer_data) - # TODO: announce changes in chat? func _peer_left(ids: Array): call_deferred("update_player_count") @@ -99,7 +94,7 @@ func preserve_chat_scroll(n, tl, fl, tc, fc): func send_chat_message(): var message = chat_edit.text if message != "": - rpc("add_chat", "%s: %s" % [Global.client.signaller.display_name, message]) + rpc("add_chat", "%s: %s" % [Global.signaller_client.display_name, message]) chat_edit.text = "" func update_can_start(): @@ -114,11 +109,9 @@ func update_can_start(): start.disabled = !can_start func _on_start_pressed(): - print("Start %s" % is_host) if is_host: rpc("start_game") remotesync func start_game(): - print("Starting game!") Global.goto_game() func _lobby_left(_id): Global.lobby_browser() @@ -126,6 +119,6 @@ func _on_leave_button_pressed(): Global.lobby_browser() func _on_TextEdit_text_entered(_new_text): send_chat_message() func update_player_count(): peers_list_label.text = "Players: %d" % peers.size() func _on_Button_pressed(): send_chat_message() -func _on_ready_up_toggled(button_pressed: bool): Global.client.signaller.set_ready(button_pressed) -func _on_lobby_info_text_changed(new_text: String): Global.client.signaller.set_lobby_name(new_text) +func _on_ready_up_toggled(button_pressed: bool): Global.signaller_client.set_ready(button_pressed) +func _on_lobby_info_text_changed(new_text: String): Global.signaller_client.set_lobby_name(new_text) func _signaller_disconnected(): Global.main_menu() diff --git a/server.ts b/server.ts index 667beae..ede7fb6 100644 --- a/server.ts +++ b/server.ts @@ -397,15 +397,17 @@ function onMessage(client: Client, ev: MessageEvent) { /* falls through */ case "offer": { if (!client.lobby) return; - console.log(`Received WebRTC Negotiation Message (type: ${msg.type})...`); if (typeof msg.data !== "object") return; const webrtcMessage = (msg.data as { peerId?: number }); if (!webrtcMessage.peerId || typeof webrtcMessage.peerId !== "number") { return; } + console.log( + `Received WebRTC Negotiation Message (type: ${msg.type}, from: ${client.peerId}, to: ${webrtcMessage.peerId})...`, + ); const destClient = client.lobby.getPeer(webrtcMessage.peerId); if (!destClient || !destClient.peerId) return; - webrtcMessage.peerId = destClient.peerId; + webrtcMessage.peerId = client.peerId as number; destClient.send(buildMessage(msg.type, webrtcMessage)); break; } @@ -424,12 +426,12 @@ function onClientLeave(client: Client) { } function onSocketClose(client: Client, _ev: Event) { - console.log("Client Close"); + console.log("Client Socket Close"); onClientLeave(client); } function onSocketError(client: Client, _ev: Event) { - console.log("Client Error"); + console.log("Client Socket Error"); onClientLeave(client); }