Start Character Rooster

This commit is contained in:
torsten 2026-02-16 15:05:03 +01:00
parent 9b457d6fb4
commit 6352347c4d
9 changed files with 155 additions and 15 deletions

View file

@ -2,14 +2,6 @@
"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.
Dies ist ein "Ewigkeits-Projekt", das nichtkommerziell ist, und
vom Entwickler immer wieder für längere Zeit weggelegt wird. Fokus liegt auf 3D Welten und Weltensimulation.
Dadurch ist es notwendig, dass das gesamte System so modular und
selbsterklärend aufgebaut wird, dass eine Rückkehr zum Projekt nach
langer Pause immer noch machbar ist. Features sind daher soweit einzuschränken,
dass ein absolut Minimaler Prototyp in einem vernünftigen Zeitraum erstellbar wird.
Ausserdem ist Modularität und einfache Testbarkeit Pflicht.
## 2. Kernkonzepte der Architektur
### Client/Server-Modell
@ -63,11 +55,11 @@ Alle Skripte und die dazugehörige Logik sollen thematisch in Unterordnern unter
## 5. Implementierungsdetails & Konventionen
- **Click-to-Move:** Die Spielerbewegung soll aus Performancegründen über eine AStar-Implementierung (`systems/navigation/`) gesteuert werden.
- **GDScript-Konvention: `async`-Schlüsselwort vermeiden:** In GDScript 4 wird jede Funktion, die `await` enthält, automatisch von der Engine als asynchron behandelt. Das `async`-Schlüsselwort in der Funktionssignatur (`async func...`) ist optional und hat in diesem Projekt wiederholt zu schwer diagnostizierbaren Syntaxfehlern geführt.
- **Regel:** Das `async`-Schlüsselwort **darf nicht** verwendet werden.
- **Korrekt:** `func my_function(): await get_tree().process_frame`
- **Falsch:** `async func my_function(): await get_tree().process_frame`
- **Version:** Basis ist **Godot 4.6**. Nutze aktuelle APIs (besonders für Navigation und Multiplayer).
- **GDScript-Regel (No Async Keyword):** Das Schlüsselwort `async` darf **nicht** verwendet werden. Funktionen mit `await` sind automatisch asynchron.
- - *Korrekt:* `func move(): await get_tree().process_frame`
- **Statische Typisierung:** Nutze konsequent Typ-Hints (z.B. `var health: int = 100`), um Fehlerquellen zu minimieren.
- **Node-Referenzen:** Nutze `@onready var` und relative Pfade. Vermeide `/root/`-Pfade wegen der SubViewport-Struktur.
## 6. Globale Skripte (Autoloads)
@ -114,9 +106,17 @@ Dieser Abschnitt beschreibt die Verantwortlichkeiten der zentralen Skripte im Pr
## 8. Wichtige Erkenntnisse & Best Practices
Dieser Abschnitt dokumentiert wichtige technische Lektionen, die während der Entwicklung gelernt wurden.
* **MultiplayerSynchronizer:** Konfiguration **nur per Code** in `_ready()`.
* **Property-Syntax:** Pfade in der `SceneReplicationConfig` müssen mit Doppelpunkt starten, wenn sie sich auf den Root-Pfad beziehen (z.B. `config.add_property(":sync_position")`).
* **MultiplayerSpawner:** Nutze einen generischen Spawner in der Basis-Level-Szene für automatische Synchronisation bei Late-Joinern.
### Technische Detail
## 7. Anweisungen für die KI (Refactoring & Generierung)
* **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.
### Technische Details
Es gibt kein async Keyword, aber await funktioniert trotzdem. => await get_tree().process_frame

View file

@ -2,8 +2,12 @@
[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"]
[node name="Game_root" type="Node2D" unique_id=1336425327]
script = ExtResource("1_xj5yv")
[node name="MainMenu" parent="." unique_id=1731390780 instance=ExtResource("2_pjpa4")]
visible = false
[node name="CharacterSelect" parent="." unique_id=2079351175 instance=ExtResource("3_bxsma")]

View file

@ -0,0 +1,9 @@
class_name CharacterData
var id: int = -1 # Eindeutige ID (wird später wichtig)
var name: String = "New Hero"
var level: int = 1
var area: String = "keuwin_grasland" # Start-Area
var position: Vector3 = Vector3.ZERO
var testvariable: String = "test"
# Hier kommen später noch Inventar, Ausrüstung, etc. hinzu...

View file

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

View file

@ -0,0 +1,47 @@
class_name SaveLoad
const SAVE_PATH = "user://player_data.json"
static func save_character(data: CharacterData):
var save_dict = {
"id": data.id,
"name": data.name,
"level": data.level,
"area": data.area,
"position": { # Vector3 als Dictionary speichern
"x": data.position.x,
"y": data.position.y,
"z": data.position.z
}
}
var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
if file:
file.store_string(JSON.stringify(save_dict, "\t"))
file.close()
print("Saved player data to ", SAVE_PATH)
else:
printerr("Error saving to ", SAVE_PATH)
static func load_character() -> CharacterData:
var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
if file:
var json_string = file.get_as_text()
file.close()
var data = JSON.parse_string(json_string)
if data == null:
printerr("JSON Parse Error")
return null
var char_data = CharacterData.new()
char_data.id = data["id"]
char_data.name = data["name"]
char_data.level = data["level"]
char_data.area = data["area"]
char_data.position = Vector3(data["position"]["x"],data["position"]["y"],data["position"]["z"])
print("Loaded player data from ", SAVE_PATH)
return char_data
else:
print("No savefile found, returning default CharacterData.")
return null

View file

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

View file

@ -0,0 +1,47 @@
extends Control
@onready var character_list = $ItemList # Oder Tree, je nachdem wie die Liste aussieht
var characters: Array[CharacterData] = []
func _ready():
# Vorhandene Charaktere laden
load_characters()
update_ui()
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()
if data == null:
data = CharacterData.new()
data.id = 1 # Eindeutige ID generieren (später)
characters = [data]
SaveLoad.save_character(data)
else:
characters = [data]
func update_ui():
character_list.clear()
for char in characters:
character_list.add_item(char.name) # Hier später: Icons, Stats etc.
func _on_character_selected(index):
var selected_character = characters[index]
# (Hier Code zum Anzeigen der Details und Bearbeiten des Charakters)
# (Am Ende: StartGame.emit(selected_character))
pass
signal StartGame(character: CharacterData) # Ab hier wirds interessant.
func _on_start_game_pressed():
if characters.size() > 0:
StartGame.emit(characters[0]) # Einfachheitshalber: Ersten Charakter nehmen
# Aufräumen und die Spielwelt laden (später)
func _on_create_character_pressed():
var new_character = CharacterData.new()
new_character.id = characters.size() + 1 # Provisorische ID
characters.append(new_character)
SaveLoad.save_character(new_character)
update_ui()

View file

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

View file

@ -0,0 +1,30 @@
[gd_scene format=3 uid="uid://ca2bhph2k3xpy"]
[ext_resource type="Script" uid="uid://b5htxbkq23mo6" path="res://ui/character_select/character_select.gd" id="1_o7qwi"]
[node name="CharacterSelect" type="Control" unique_id=2056198803]
layout_mode = 3
anchors_preset = 0
script = ExtResource("1_o7qwi")
[node name="ItemList" type="ItemList" parent="." unique_id=1442954519]
layout_mode = 0
offset_top = 32.0
offset_right = 368.0
offset_bottom = 571.0
[node name="Label" type="Label" parent="." unique_id=1413425912]
layout_mode = 0
offset_left = 111.0
offset_top = 2.0
offset_right = 217.0
offset_bottom = 31.0
text = "Charaktere:"
[node name="Button" type="Button" parent="." unique_id=50672893]
layout_mode = 0
offset_left = 113.0
offset_top = 585.0
offset_right = 224.0
offset_bottom = 621.0
text = "Start"