From 22291bf639de0224c7ba2cc9043428fb3c94396d Mon Sep 17 00:00:00 2001 From: torsten Date: Tue, 17 Feb 2026 11:27:00 +0100 Subject: [PATCH] Working on UI Logic, will lead to intermediate character creation/selection screen --- weltenauge/PROJEKT_UEBERSICHT.md | 24 ++---- weltenauge/game_root.tscn | 4 + weltenauge/systems/data/character_data.gd | 8 +- weltenauge/ui/account/login.gd | 18 ++++ weltenauge/ui/account/login.gd.uid | 1 + weltenauge/ui/account/login.tscn | 82 +++++++++++++++++++ .../ui/character_select/character_select.gd | 6 +- weltenauge/ui/menus/main_menu.gd | 12 ++- 8 files changed, 126 insertions(+), 29 deletions(-) create mode 100644 weltenauge/ui/account/login.gd create mode 100644 weltenauge/ui/account/login.gd.uid create mode 100644 weltenauge/ui/account/login.tscn diff --git a/weltenauge/PROJEKT_UEBERSICHT.md b/weltenauge/PROJEKT_UEBERSICHT.md index 3446a06..0dbd8b4 100644 --- a/weltenauge/PROJEKT_UEBERSICHT.md +++ b/weltenauge/PROJEKT_UEBERSICHT.md @@ -20,14 +20,6 @@ ### Isolierte Welten auf dem Server -- Um Physik und Szenen-Bäume sauber zu trennen, wird jede Area auf dem Server in einen eigenen **SubViewport** geladen. -- Dies ermöglicht "Multi-World Physics", sodass sich Objekte aus verschiedenen Areas auf dem Server nicht gegenseitig beeinflussen können. - -### Netzwerk-Synchronisation - -- **Visibility Filtering:** `MultiplayerSynchronizer` und `MultiplayerSpawner` werden mit Visibility Filtern versehen. Dadurch wird sichergestellt, dass ein Client nur Updates für die Objekte in seiner eigenen Area erhält. -- **Positions-Synchronisation:** Es wird explizit die `position`-Eigenschaft von Nodes synchronisiert, **nicht** `global_position`, um Konflikte mit der SubViewport-Struktur zu vermeiden. - ## 3. Geplante Ordnerstruktur Alle Skripte und die dazugehörige Logik sollen thematisch in Unterordnern unter `res://systems/` abgelegt werden: @@ -66,20 +58,16 @@ Alle Skripte und die dazugehörige Logik sollen thematisch in Unterordnern unter Die folgenden Skripte sind als Singletons (Autoloads in Godot) konfiguriert und global verfügbar: - **`GameEvents`** (`res://systems/core/game_events.gd`): Zentraler Event-Bus für die Kommunikation zwischen entkoppelten Systemen. -- **`NetworkManager`** (`res://systems/network/network_manager.gd`): Verwaltet die gesamte Netzwerklogik, wie das Hosten, Beitreten und die RPC-Kommunikation. +- `/data/`: Speicher-Logik, Daten-Wrapper (z.B. für JSON, SQLite). - **`ChatManager`** (`res://systems/network/chat_manager.gd`): Kümmert sich um die Logik des In-Game-Chats. - ## 7. Analyse der existierenden Skripte (Stand: Commit 30b6ade) - +- `/data/`: Speicher-Logik, Daten-Wrapper (z.B. für JSON, SQLite). Dieser Abschnitt beschreibt die Verantwortlichkeiten der zentralen Skripte im Projekt. - -### `/game_root.gd` - **Rolle:** Absoluter Startpunkt des Spiels. - **Verantwortlichkeit:** Hält die Instanz des Hauptmenüs (`main.tscn`). Seine einzige Aufgabe ist es, zu existieren und die initiale Szene zu laden. Die eigentliche Logik wird sofort an die globalen Manager und das UI delegiert. ### `/systems/core/game_events.gd` (Autoload: `GameEvents`) - **Rolle:** Globaler Event-Bus. -- **Verantwortlichkeit:** Dient als zentrale Sammelstelle für Signale, um verschiedene Systeme voneinander zu entkoppeln. Zum Beispiel sendet das UI ein `host_requested` Signal, auf das der `NetworkManager` hört, ohne dass die beiden sich direkt kennen müssen. ### `/systems/network/network_manager.gd` (Autoload: `NetworkManager`) - **Rolle:** Das Herzstück der gesamten Anwendung. @@ -114,16 +102,14 @@ Dieser Abschnitt beschreibt die Verantwortlichkeiten der zentralen Skripte im Pr * **Thinking-Phase:** Analysiere vor der Code-Ausgabe, ob Änderungen die SubViewport-Isolation oder die Server-Autorität beeinflussen könnten. * **Minimaler Eingriff:** Verändere nur angeforderte Logik. Behalte Kommentare und Signal-Strukturen bei. -* **Stil:** Schreibe sauberen, performanten GDScript 2.0 Code. Bevorzuge Komposition gegenüber Vererbung. + - **Spieler-Spawning:** Koordiniert den komplexen Prozess des Spieler-Spawns. Weist den Client an, eine Szene zu laden (`client_load_area`), wartet auf dessen Bereitschaftsmeldung (`client_ready_to_spawn`) und instanziiert dann den Spieler-Avatar serverseitig in der korrekten Sub-Welt. -### Technische Details Es gibt kein async Keyword, aber await funktioniert trotzdem. => await get_tree().process_frame -### Dynamische Konfiguration des MultiplayerSynchronizer + - **Spieler-Spawning:** Koordiniert den komplexen Prozess des Spieler-Spawns. Weist den Client an, eine Szene zu laden (`client_load_area`), wartet auf dessen Bereitschaftsmeldung (`client_ready_to_spawn`) und instanziiert dann den Spieler-Avatar serverseitig in der korrekten Sub-Welt. Die Konfiguration des `MultiplayerSynchronizer` für wiederverwendbare Szenen (wie den Spieler) ist kritisch und muss korrekt gehandhabt werden, um Timing- und Pfad-Probleme zu vermeiden. Die folgenden vier Regeln sind entscheidend: - 1. **Konfiguration nur per Code:** Die Konfiguration muss zur Laufzeit im Code erfolgen, nicht im Editor. Der Editor speichert absolute Pfade, die in einem dynamischen Szenen-Setup (mehrere Welten/Viewports) unbrauchbar sind. 2. **Ort der Konfiguration: `_ready()`:** Die Logik zur Erstellung der `SceneReplicationConfig` gehört in die `_ready()`-funktion des Skripts, dessen Eigenschaften synchronisiert werden sollen (z.B. `player_controller.gd`). @@ -132,6 +118,8 @@ Die Konfiguration des `MultiplayerSynchronizer` für wiederverwendbare Szenen (w - **Korrekt:** `config.add_property(":sync_position")` - **Falsch:** `config.add_property("sync_position")` oder `config.add_property("..:sync_position")` + - **Falsch:** `config.add_property("sync_position")` oder `config.add_property("..:sync_position")` + ### Dynamische Konfiguration des MultiplayerSpawner - Handhabt nicht nur originäre Objekterzeugung in den Levels, sondern auch Objektaktualisierungen für "Late Joiner" diff --git a/weltenauge/game_root.tscn b/weltenauge/game_root.tscn index ec26684..419bf76 100644 --- a/weltenauge/game_root.tscn +++ b/weltenauge/game_root.tscn @@ -3,6 +3,7 @@ [ext_resource type="Script" uid="uid://162cjpb4lac4" path="res://game_root.gd" id="1_xj5yv"] [ext_resource type="PackedScene" uid="uid://bhywhrlwjox6t" path="res://ui/menus/main_menu.tscn" id="2_pjpa4"] [ext_resource type="PackedScene" uid="uid://ca2bhph2k3xpy" path="res://ui/character_select/character_select.tscn" id="3_bxsma"] +[ext_resource type="PackedScene" uid="uid://lb6btb6426mi" path="res://ui/account/login.tscn" id="3_lkrbl"] [node name="Game_root" type="Node2D" unique_id=1336425327] script = ExtResource("1_xj5yv") @@ -10,4 +11,7 @@ script = ExtResource("1_xj5yv") [node name="MainMenu" parent="." unique_id=1731390780 instance=ExtResource("2_pjpa4")] visible = false +[node name="Login" parent="." unique_id=1694671237 instance=ExtResource("3_lkrbl")] + [node name="CharacterSelect" parent="." unique_id=2079351175 instance=ExtResource("3_bxsma")] +visible = false diff --git a/weltenauge/systems/data/character_data.gd b/weltenauge/systems/data/character_data.gd index fea9a96..b076f04 100644 --- a/weltenauge/systems/data/character_data.gd +++ b/weltenauge/systems/data/character_data.gd @@ -1,9 +1,7 @@ class_name CharacterData -var id: int = -1 # Eindeutige ID (wird später wichtig) -var name: String = "New Hero" +var id: String = "" # UUID des Charakters +var name: String = "Neuer Held" var level: int = 1 -var area: String = "keuwin_grasland" # Start-Area +var area: String = "keuwin_grasland" var position: Vector3 = Vector3.ZERO -var testvariable: String = "test" -# Hier kommen später noch Inventar, Ausrüstung, etc. hinzu... diff --git a/weltenauge/ui/account/login.gd b/weltenauge/ui/account/login.gd new file mode 100644 index 0000000..66f5cd9 --- /dev/null +++ b/weltenauge/ui/account/login.gd @@ -0,0 +1,18 @@ +extends Control + +@onready var ip_input: TextEdit = $IPInput + +func _on_host_button_pressed() -> void: + print("host pressed") + GameEvents.host_requested.emit() + hide() + +func _on_join_button_pressed(): + var username = $UsernameLineEdit.text + var password = $PasswordLineEdit.text # TODO: Handle Password + + # Hier findet später die Prüfung gegen den Server statt + # Im Moment simulieren wir einfach einen erfolgreichen Login + #TODO + GameEvents.join_requested.emit(ip_input.text) + hide() diff --git a/weltenauge/ui/account/login.gd.uid b/weltenauge/ui/account/login.gd.uid new file mode 100644 index 0000000..a30aaac --- /dev/null +++ b/weltenauge/ui/account/login.gd.uid @@ -0,0 +1 @@ +uid://c6yngwnpqsnpi diff --git a/weltenauge/ui/account/login.tscn b/weltenauge/ui/account/login.tscn new file mode 100644 index 0000000..3267515 --- /dev/null +++ b/weltenauge/ui/account/login.tscn @@ -0,0 +1,82 @@ +[gd_scene format=3 uid="uid://lb6btb6426mi"] + +[ext_resource type="Script" uid="uid://c6yngwnpqsnpi" path="res://ui/account/login.gd" id="1_x74xt"] + +[node name="Login" type="Control" unique_id=1694671237] +layout_mode = 3 +anchors_preset = 0 +offset_left = 23.0 +offset_top = 102.0 +offset_right = 23.0 +offset_bottom = 102.0 +script = ExtResource("1_x74xt") + +[node name="IPInput" type="TextEdit" parent="." unique_id=1340335140] +custom_minimum_size = Vector2(150, 40) +layout_mode = 0 +offset_left = 375.0 +offset_top = -70.0 +offset_right = 525.0 +offset_bottom = -30.0 +text = "127.0.0.1" +editable = false + +[node name="Join_Button" type="Button" parent="." unique_id=811892478] +layout_mode = 0 +offset_left = 30.0 +offset_top = 12.0 +offset_right = 231.0 +offset_bottom = 43.0 +text = "Join" + +[node name="Host_Button" type="Button" parent="." unique_id=1465654892] +layout_mode = 0 +offset_left = 313.0 +offset_top = 11.0 +offset_right = 530.0 +offset_bottom = 42.0 +text = "Host" + +[node name="lb_User" type="Label" parent="." unique_id=1356001317] +layout_mode = 0 +offset_left = 21.0 +offset_top = -72.0 +offset_right = 62.0 +offset_bottom = -49.0 +text = "User:" + +[node name="lb_User" type="Label" parent="lb_User" unique_id=463394394] +layout_mode = 0 +offset_left = 286.0 +offset_top = 11.0 +offset_right = 340.0 +offset_bottom = 34.0 +text = "Server:" + +[node name="lb_Password" type="Label" parent="." unique_id=1027088176] +layout_mode = 0 +offset_left = 18.0 +offset_top = -39.0 +offset_right = 94.0 +offset_bottom = -16.0 +text = "Passwort:" + +[node name="UsernameLineEdit" type="LineEdit" parent="." unique_id=1896094376] +layout_mode = 0 +offset_left = 122.0 +offset_top = -72.0 +offset_right = 231.0 +offset_bottom = -41.0 +text = "test" + +[node name="PasswordLineEdit" type="LineEdit" parent="." unique_id=214865529] +layout_mode = 0 +offset_left = 121.0 +offset_top = -38.0 +offset_right = 230.0 +offset_bottom = -7.0 +text = "test" + +[connection signal="pressed" from="Join_Button" to="." method="_on_join_button_pressed"] +[connection signal="button_up" from="Host_Button" to="." method="_on_host_pressed"] +[connection signal="pressed" from="Host_Button" to="." method="_on_host_button_pressed"] diff --git a/weltenauge/ui/character_select/character_select.gd b/weltenauge/ui/character_select/character_select.gd index fbf5f4c..528e5b3 100644 --- a/weltenauge/ui/character_select/character_select.gd +++ b/weltenauge/ui/character_select/character_select.gd @@ -12,10 +12,12 @@ func _ready(): func load_characters(): # (Hier kommt die Logik zum Durchsuchen des "user://" Ordners nach JSON-Dateien hin) # (Für den Anfang können wir einfach einen Standard-Charakter erstellen) - var data = SaveLoad.load_character() + #var data = SaveLoad.load_character() + + var data = null if data == null: data = CharacterData.new() - data.id = 1 # Eindeutige ID generieren (später) + data.id = "000001" # Eindeutige ID generieren (später) characters = [data] SaveLoad.save_character(data) else: diff --git a/weltenauge/ui/menus/main_menu.gd b/weltenauge/ui/menus/main_menu.gd index 56f9d78..fe21d7b 100644 --- a/weltenauge/ui/menus/main_menu.gd +++ b/weltenauge/ui/menus/main_menu.gd @@ -2,12 +2,16 @@ extends Control @onready var ip_input: TextEdit = $IPInput -func _on_host_pressed(): +func _on_host_button_pressed() -> void: print("host pressed") GameEvents.host_requested.emit() hide() -func _on_join_pressed(): - print("join pressed") +func _on_join_button_pressed(): + var username = $UsernameLineEdit.text + var password = $PasswordLineEdit.text # TODO: Handle Password + + # Hier findet später die Prüfung gegen den Server statt + # Im Moment simulieren wir einfach einen erfolgreichen Login + #TODO GameEvents.join_requested.emit(ip_input.text) - hide()