Working on UI Logic, will lead to intermediate character creation/selection screen

This commit is contained in:
torsten 2026-02-17 11:27:00 +01:00
parent 6352347c4d
commit 22291bf639
8 changed files with 126 additions and 29 deletions

View file

@ -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"

View file

@ -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

View file

@ -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...

View file

@ -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()

View file

@ -0,0 +1 @@
uid://c6yngwnpqsnpi

View file

@ -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"]

View file

@ -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:

View file

@ -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()