9.4 KiB
Projektübersicht: weltenauge
Dieses Dokument dient als zentrale Wissensbasis für das Godot-Projekt "weltenauge". Es soll dem KI-Assistenten und den Entwicklern helfen, den Überblick über die Architektur und die Ziele des Projekts zu behalten.
1. Projektziel & Vision
"weltenauge" ist ein von Neverwinter Nights (NWN) inspiriertes Multiplayer-Rollenspiel. Das Kernstück ist eine robuste Client/Server-Architektur, die in einer einzigen Godot-Codebase umgesetzt wird.
2. Kernkonzepte der Architektur
Client/Server-Modell
- Eine Codebase: Client und Server sind Teil desselben Godot-Projekts.
- Start-Logik:
- Im normalen Modus startet das Spiel mit einem Menü, das "Host" und "Beitreten" anbietet.
- Im Headless-Modus (
--headless) startet das Programm automatisch als dedizierter Server (Host).
- Verantwortlichkeiten: Der Server (Host) ist die Autorität für den Spielzustand. Clients empfangen und visualisieren den Zustand.
Dynamisches Area-Management
- Serverseitig: Der Server lädt alle von Spielern bewohnten Areas (Szenen) gleichzeitig.
- Clientseitig: Ein Client lädt nur die eine Area, in der sich sein Charakter gerade befindet. Dies spart Ressourcen auf den Client-Rechnern.
- Area-Wechsel: Ein Wechsel wird immer vom Server initiiert. Der Server lädt die neue Szene für den Spieler, platziert dessen Avatar und sendet ein Signal an den Client. Der Client lädt daraufhin ebenfalls die neue Szene.
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:
MultiplayerSynchronizerundMultiplayerSpawnerwerden 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, nichtglobal_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:
/core/: Haupt-Game-Loop, State-Machine (Laden, Menü, Spiel)./network/: Peer-Verbindung, Headless-Logik, Synchronisation, RPCs./player/: Input-Handling, Kamera-Steuerung, lokale Charakter-Steuerung./ai/: NPC-Verhaltenslogik (z.B. Behavior Trees)./combat/: Kampfsystem, Trefferberechnung, Würfelwürfe./dialogue/: Gesprächs-System./inventory/: Item-Management, Ausrüstung, Truhen./interaction/: Interaktionslogik für Türen, Hebel, Portale./navigation/: A-Stern (AStar) Pfadfindungs-Logik./data/: Speicher-Logik, Daten-Wrapper (z.B. für JSON, SQLite)./ui/: (Existierender Ordner) UI-Szenen und Skripte.
4. Wichtige Dateien & Szenen
- Spieler-Modell:
res://systems/player/player.tres - Area 1:
res://maps/outdoor/grass/keuwin_grasland.tscn - Area 2:
res://maps/planes/fugue/halle_der_ahnen.tscn - Startpunkt:
main.tscn(vermutlich für das Hauptmenü) - Haupt-Logik:
game_root.gd/game_root.tscn(vermutlich die Wurzel des aktiven Spiels)
5. Implementierungsdetails & Konventionen
- Click-to-Move: Die Spielerbewegung soll aus Performancegründen über eine AStar-Implementierung (
systems/navigation/) gesteuert werden.
6. Globale Skripte (Autoloads)
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.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)
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_requestedSignal, auf das derNetworkManagerhört, ohne dass die beiden sich direkt kennen müssen.
/systems/network/network_manager.gd (Autoload: NetworkManager)
- Rolle: Das Herzstück der gesamten Anwendung.
- Verantwortlichkeit:
- Startlogik: Prüft in
_ready()selbst, ob das Spiel headless gestartet wurde und ruft dannhost_game()auf. - Verbindungs-Management: Startet den Server (
host_game) oder verbindet als Client (join_game). - Area-Management: Lädt und verwaltet die Spielwelten (Areas). Erstellt für jede Area auf dem Server einen eigenen
SubViewportmitown_world_3d = true, um die Physikwelten zu trennen. - 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.
- Startlogik: Prüft in
/systems/player/player_controller.gd
- Rolle: Steuerungslogik für den Spieler-Avatar.
- Verantwortlichkeit: Dieses Skript läuft auf dem Server und allen Clients, verhält sich aber je nach Autorität unterschiedlich:
- Client (mit Autorität): Fängt Mauseklicks ab (
_input), berechnet die Zielposition auf dem Boden und sendet einenrequest_moveRPC an den Server. - Server: Empfängt
request_move, berechnet den Pfad (aktuell nur direktes Ziel) und bewegt denCharacterBody3Dmittelsmove_and_slide()im_physics_process. Die resultierende Position und Rotation werden insync_positionundsync_rotationgespeichert. - Client (ohne Autorität): Liest im
_processkontinuierlichsync_positionundsync_rotationaus und setzt dieglobal_positionundrotationdes Avatars hart auf diese Werte, um ihn mit dem Server synchron zu halten.
- Client (mit Autorität): Fängt Mauseklicks ab (
/systems/player/camera_logic.gd
- Rolle: Kamera-Steuerung.
- Verantwortlichkeit: Ein einfaches Skript, das an einer
Camera3Dhängt. Es verfolgt sanft (lerp) eintarget(den Spieler-Avatar) und behält dabei einen festenoffsetbei, um eine klassische Top-Down-Perspektive zu erzeugen. Die Zuweisung des Ziels erfolgt durch denNetworkManagerüber denassign_camera_targetRPC.
/main.tscn
- Rolle: Hauptmenü-UI.
- Verantwortlichkeit: Enthält die UI-Elemente wie "Host" und "Join" Buttons. Es wird angenommen, dass die
pressed()Signale dieser Buttons direkt im Godot-Editor mit den entsprechenden Signalen imGameEvents-Singleton (host_requested,join_requested) verbunden sind.
8. Wichtige Erkenntnisse & Best Practices
Dieser Abschnitt dokumentiert wichtige technische Lektionen, die während der Entwicklung gelernt wurden.
Dynamische Konfiguration des MultiplayerSynchronizer
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:
-
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.
-
Ort der Konfiguration:
_ready(): Die Logik zur Erstellung derSceneReplicationConfiggehört in die_ready()-funktion des Skripts, dessen Eigenschaften synchronisiert werden sollen (z.B.player_controller.gd). -
Korrekte Eigenschafts-Syntax: Der Pfad zu einer Eigenschaft, die sich direkt auf dem
root_path-Node des Synchronizers befindet (standardmäßig sein Parent), muss mit einem Doppelpunkt beginnen.- Korrekt:
config.add_property(":sync_position") - Falsch:
config.add_property("sync_position")oderconfig.add_property("..:sync_position")
- Korrekt:
-
Timing im Spawner sicherstellen (
async/await): Der Code, der den Node instanziiert und dem Baum hinzufügt (z.B._spawn_player_in_worldimNetworkManager), muss nach demadd_child()-Aufruf explizit auf den nächsten Frame warten, bevor er den Synchronizer des neuen Nodes benutzt. Dies stellt sicher, dass dessen_ready()-Funktion bereits ausgeführt wurde.# Im NetworkManager async func _spawn_player_in_world(id, viewport_node): # ... viewport_node.add_child(player) await get_tree().process_frame # Garantiert, dass _ready() auf player läuft # Jetzt ist die Benutzung des Synchronizers sicher var sync = player.get_node("MultiplayerSynchronizer") sync.set_visibility_for(id, true) # ...