From 67862a46f3bc6b9c737fd2ede9595bf93c6329ea Mon Sep 17 00:00:00 2001 From: Jonas Reith Date: Thu, 27 Aug 2026 09:21:15 +0200 Subject: [PATCH] Add boot flow, save-system skeleton, and Equipment skeleton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit boot/: opening_screen (attribution -> title/flavor text placeholder, explicitly marked TODO for a later real cinematic, skippable by keypress/click) -> main_menu (Neues Spiel/Fortsetzen/Optionen/Credits/ Beenden, ConfirmationDialog for overwrite-existing-save) -> options_menu (TabContainer with 4 empty placeholder categories: Grafik/Sound/ Steuerung/Spieloptionen) and credits_screen, both just navigable stubs. project.godot's run/main_scene now points at boot/opening_screen.tscn instead of directly at the gameplay scene. autoload/save_manager.gd: new SaveManager singleton. Deliberately minimal — persists only a timestamp to user://savegame.json — but a real, working has_save()/save_game()/load_game()/start_new_game() round trip, enough to back the menu's Start/Continue/overwrite-confirm logic without inventing game state that doesn't exist yet. player/equipment/: Equipment (Node) + EquipmentItem (Resource) stub classes per the user's explicit ask for a "class diagram skeleton" to build on later — exported fields and empty TODO methods, no logic. Wired as a child of world/island.tscn's real Player (not player.tscn, which is only a debug prototype scene, not the actual game's player). player.gd's new `equipment` onready var uses get_node_or_null() since that child only exists on the real player, not every scene reusing this script. Verified headless: all new boot scenes load cleanly, a full boot smoke test via run/main_scene succeeds, and — since button-click navigation can't be simulated headlessly — SaveManager's actual save/load/ overwrite/cleanup cycle was exercised directly and behaves correctly (has_save flips true/false correctly, loaded data matches what was saved). Manual in-editor check of the menu navigation itself is still needed and not yet done. --- autoload/save_manager.gd | 37 +++++++++++++ autoload/save_manager.gd.uid | 1 + boot/credits_screen.gd | 6 +++ boot/credits_screen.gd.uid | 1 + boot/credits_screen.tscn | 38 +++++++++++++ boot/main_menu.gd | 45 ++++++++++++++++ boot/main_menu.gd.uid | 1 + boot/main_menu.tscn | 65 ++++++++++++++++++++++ boot/opening_screen.gd | 29 ++++++++++ boot/opening_screen.gd.uid | 1 + boot/opening_screen.tscn | 38 +++++++++++++ boot/options_menu.gd | 6 +++ boot/options_menu.gd.uid | 1 + boot/options_menu.tscn | 74 ++++++++++++++++++++++++++ player/equipment/equipment.gd | 15 ++++++ player/equipment/equipment.gd.uid | 1 + player/equipment/equipment_item.gd | 7 +++ player/equipment/equipment_item.gd.uid | 1 + player/player.gd | 2 + project.godot | 3 +- world/island.tscn | 4 ++ 21 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 autoload/save_manager.gd create mode 100644 autoload/save_manager.gd.uid create mode 100644 boot/credits_screen.gd create mode 100644 boot/credits_screen.gd.uid create mode 100644 boot/credits_screen.tscn create mode 100644 boot/main_menu.gd create mode 100644 boot/main_menu.gd.uid create mode 100644 boot/main_menu.tscn create mode 100644 boot/opening_screen.gd create mode 100644 boot/opening_screen.gd.uid create mode 100644 boot/opening_screen.tscn create mode 100644 boot/options_menu.gd create mode 100644 boot/options_menu.gd.uid create mode 100644 boot/options_menu.tscn create mode 100644 player/equipment/equipment.gd create mode 100644 player/equipment/equipment.gd.uid create mode 100644 player/equipment/equipment_item.gd create mode 100644 player/equipment/equipment_item.gd.uid diff --git a/autoload/save_manager.gd b/autoload/save_manager.gd new file mode 100644 index 0000000..18f0016 --- /dev/null +++ b/autoload/save_manager.gd @@ -0,0 +1,37 @@ +extends Node + +const SAVE_PATH := "user://savegame.json" + +func has_save() -> bool: + return FileAccess.file_exists(SAVE_PATH) + +func start_new_game() -> void: + # TODO: sobald echter Spielzustand existiert (Position, Fortschritt, ...), + # hier den frischen Zustand aufbauen statt nur zu speichern. + save_game() + +func save_game() -> void: + var data := { + "saved_at_unix": Time.get_unix_time_from_system(), + } + var file := FileAccess.open(SAVE_PATH, FileAccess.WRITE) + if file == null: + push_error("Speichern fehlgeschlagen: %s" % FileAccess.get_open_error()) + return + file.store_string(JSON.stringify(data)) + file.close() + +func load_game() -> Dictionary: + if not has_save(): + push_warning("Kein Spielstand vorhanden.") + return {} + var file := FileAccess.open(SAVE_PATH, FileAccess.READ) + var content := file.get_as_text() + file.close() + var parsed = JSON.parse_string(content) + if typeof(parsed) != TYPE_DICTIONARY: + push_error("Spielstand-Datei ist beschädigt.") + return {} + # TODO: geladene Werte tatsächlich auf Spieler/Welt anwenden, sobald es + # etwas Sinnvolles zu laden gibt. + return parsed diff --git a/autoload/save_manager.gd.uid b/autoload/save_manager.gd.uid new file mode 100644 index 0000000..6d1e4d0 --- /dev/null +++ b/autoload/save_manager.gd.uid @@ -0,0 +1 @@ +uid://dhxbb41fxvo3q diff --git a/boot/credits_screen.gd b/boot/credits_screen.gd new file mode 100644 index 0000000..672c4c5 --- /dev/null +++ b/boot/credits_screen.gd @@ -0,0 +1,6 @@ +extends Control + +@onready var back_button: Button = $BackButton + +func _ready() -> void: + back_button.pressed.connect(func(): get_tree().change_scene_to_file("res://boot/main_menu.tscn")) diff --git a/boot/credits_screen.gd.uid b/boot/credits_screen.gd.uid new file mode 100644 index 0000000..43caf60 --- /dev/null +++ b/boot/credits_screen.gd.uid @@ -0,0 +1 @@ +uid://cgyurqw4rcvn0 diff --git a/boot/credits_screen.tscn b/boot/credits_screen.tscn new file mode 100644 index 0000000..aafdce1 --- /dev/null +++ b/boot/credits_screen.tscn @@ -0,0 +1,38 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://boot/credits_screen.gd" id="1_credits"] + +[node name="CreditsScreen" type="Control"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_credits") + +[node name="TitleLabel" type="Label" parent="."] +offset_left = 40.0 +offset_top = 30.0 +offset_right = 400.0 +offset_bottom = 70.0 +text = "Credits" + +[node name="ScrollContainer" type="ScrollContainer" parent="."] +offset_left = 40.0 +offset_top = 90.0 +offset_right = 1240.0 +offset_bottom = 620.0 + +[node name="CreditsLabel" type="Label" parent="ScrollContainer"] +layout_mode = 2 +text = "Programmierung: TODO +Artwork: TODO +Musik/Sound: TODO +Erstellt mit Godot Engine" + +[node name="BackButton" type="Button" parent="."] +offset_left = 40.0 +offset_top = 640.0 +offset_right = 160.0 +offset_bottom = 675.0 +text = "Zurück" diff --git a/boot/main_menu.gd b/boot/main_menu.gd new file mode 100644 index 0000000..fde2ac0 --- /dev/null +++ b/boot/main_menu.gd @@ -0,0 +1,45 @@ +extends Control + +const GAMEPLAY_SCENE := "res://world/island.tscn" + +@onready var start_button: Button = $Panel/ButtonList/StartButton +@onready var continue_button: Button = $Panel/ButtonList/ContinueButton +@onready var options_button: Button = $Panel/ButtonList/OptionsButton +@onready var credits_button: Button = $Panel/ButtonList/CreditsButton +@onready var exit_button: Button = $Panel/ButtonList/ExitButton +@onready var overwrite_dialog: ConfirmationDialog = $OverwriteConfirmDialog + +func _ready() -> void: + continue_button.disabled = not SaveManager.has_save() + start_button.pressed.connect(_on_start_pressed) + continue_button.pressed.connect(_on_continue_pressed) + options_button.pressed.connect(_on_options_pressed) + credits_button.pressed.connect(_on_credits_pressed) + exit_button.pressed.connect(_on_exit_pressed) + overwrite_dialog.confirmed.connect(_on_overwrite_confirmed) + +func _on_start_pressed() -> void: + if SaveManager.has_save(): + overwrite_dialog.popup_centered() + else: + _start_new_game() + +func _on_overwrite_confirmed() -> void: + _start_new_game() + +func _start_new_game() -> void: + SaveManager.start_new_game() + get_tree().change_scene_to_file(GAMEPLAY_SCENE) + +func _on_continue_pressed() -> void: + SaveManager.load_game() + get_tree().change_scene_to_file(GAMEPLAY_SCENE) + +func _on_options_pressed() -> void: + get_tree().change_scene_to_file("res://boot/options_menu.tscn") + +func _on_credits_pressed() -> void: + get_tree().change_scene_to_file("res://boot/credits_screen.tscn") + +func _on_exit_pressed() -> void: + get_tree().quit() diff --git a/boot/main_menu.gd.uid b/boot/main_menu.gd.uid new file mode 100644 index 0000000..65e9a90 --- /dev/null +++ b/boot/main_menu.gd.uid @@ -0,0 +1 @@ +uid://b210xm2monnj3 diff --git a/boot/main_menu.tscn b/boot/main_menu.tscn new file mode 100644 index 0000000..a7a8f82 --- /dev/null +++ b/boot/main_menu.tscn @@ -0,0 +1,65 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://boot/main_menu.gd" id="1_menu"] + +[node name="MainMenu" type="Control"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_menu") + +[node name="Panel" type="Panel" parent="."] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -160.0 +offset_top = -190.0 +offset_right = 160.0 +offset_bottom = 190.0 + +[node name="TitleLabel" type="Label" parent="Panel"] +anchor_right = 1.0 +offset_top = 10.0 +offset_bottom = 60.0 +text = "WayfarersWanderer" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ButtonList" type="VBoxContainer" parent="Panel"] +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 20.0 +offset_top = 70.0 +offset_right = -20.0 +offset_bottom = -20.0 +theme_override_constants/separation = 10 + +[node name="StartButton" type="Button" parent="Panel/ButtonList"] +layout_mode = 2 +text = "Neues Spiel" + +[node name="ContinueButton" type="Button" parent="Panel/ButtonList"] +layout_mode = 2 +text = "Fortsetzen" + +[node name="OptionsButton" type="Button" parent="Panel/ButtonList"] +layout_mode = 2 +text = "Optionen" + +[node name="CreditsButton" type="Button" parent="Panel/ButtonList"] +layout_mode = 2 +text = "Credits" + +[node name="ExitButton" type="Button" parent="Panel/ButtonList"] +layout_mode = 2 +text = "Beenden" + +[node name="OverwriteConfirmDialog" type="ConfirmationDialog" parent="."] +title = "Spielstand überschreiben?" +initial_position = 1 +dialog_text = "Ein Spielstand existiert bereits. Trotzdem überschreiben?" +ok_button_text = "Ja" +cancel_button_text = "Nein" diff --git a/boot/opening_screen.gd b/boot/opening_screen.gd new file mode 100644 index 0000000..ae93923 --- /dev/null +++ b/boot/opening_screen.gd @@ -0,0 +1,29 @@ +extends Control +## Platzhalter-Boot-Screen: Attribution -> Titel mit Flavor-Text -> Hauptmenü. +## TODO: später durch eine echte Opening-Cinematic ersetzen. + +@export var attribution_seconds: float = 2.0 +@export var title_seconds: float = 2.5 + +@onready var attribution_label: Label = $AttributionLabel +@onready var title_label: Label = $TitleLabel +@onready var timer: Timer = $Timer + +var _stage: int = 0 + +func _ready() -> void: + timer.timeout.connect(_advance) + timer.start(attribution_seconds) + +func _unhandled_input(event: InputEvent) -> void: + if (event is InputEventKey and event.pressed) or (event is InputEventMouseButton and event.pressed): + _advance() + +func _advance() -> void: + _stage += 1 + if _stage == 1: + attribution_label.visible = false + title_label.visible = true + timer.start(title_seconds) + else: + get_tree().change_scene_to_file("res://boot/main_menu.tscn") diff --git a/boot/opening_screen.gd.uid b/boot/opening_screen.gd.uid new file mode 100644 index 0000000..b085c20 --- /dev/null +++ b/boot/opening_screen.gd.uid @@ -0,0 +1 @@ +uid://6s84w6vbvidp diff --git a/boot/opening_screen.tscn b/boot/opening_screen.tscn new file mode 100644 index 0000000..52910a7 --- /dev/null +++ b/boot/opening_screen.tscn @@ -0,0 +1,38 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://boot/opening_screen.gd" id="1_open"] + +[node name="OpeningScreen" type="Control"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_open") + +[node name="AttributionLabel" type="Label" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "Erstellt mit Godot Engine +Ein Spiel von TODO: Autor:in" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="TitleLabel" type="Label" parent="."] +visible = false +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "WayfarersWanderer + +TODO: Flavor-Text" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Timer" type="Timer" parent="."] +one_shot = true diff --git a/boot/options_menu.gd b/boot/options_menu.gd new file mode 100644 index 0000000..672c4c5 --- /dev/null +++ b/boot/options_menu.gd @@ -0,0 +1,6 @@ +extends Control + +@onready var back_button: Button = $BackButton + +func _ready() -> void: + back_button.pressed.connect(func(): get_tree().change_scene_to_file("res://boot/main_menu.tscn")) diff --git a/boot/options_menu.gd.uid b/boot/options_menu.gd.uid new file mode 100644 index 0000000..a2eb2e8 --- /dev/null +++ b/boot/options_menu.gd.uid @@ -0,0 +1 @@ +uid://bt1qf4bx8rbhs diff --git a/boot/options_menu.tscn b/boot/options_menu.tscn new file mode 100644 index 0000000..dd62557 --- /dev/null +++ b/boot/options_menu.tscn @@ -0,0 +1,74 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://boot/options_menu.gd" id="1_options"] + +[node name="OptionsMenu" type="Control"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_options") + +[node name="TitleLabel" type="Label" parent="."] +offset_left = 40.0 +offset_top = 30.0 +offset_right = 400.0 +offset_bottom = 70.0 +text = "Optionen" + +[node name="Categories" type="TabContainer" parent="."] +offset_left = 40.0 +offset_top = 90.0 +offset_right = 1240.0 +offset_bottom = 620.0 + +[node name="Grafik" type="Control" parent="Categories"] +layout_mode = 2 + +[node name="Label" type="Label" parent="Categories/Grafik"] +offset_left = 20.0 +offset_top = 20.0 +offset_right = 500.0 +offset_bottom = 50.0 +text = "TODO: Grafik-Einstellungen" + +[node name="Sound" type="Control" parent="Categories"] +visible = false +layout_mode = 2 + +[node name="Label" type="Label" parent="Categories/Sound"] +offset_left = 20.0 +offset_top = 20.0 +offset_right = 500.0 +offset_bottom = 50.0 +text = "TODO: Sound-Einstellungen" + +[node name="Steuerung" type="Control" parent="Categories"] +visible = false +layout_mode = 2 + +[node name="Label" type="Label" parent="Categories/Steuerung"] +offset_left = 20.0 +offset_top = 20.0 +offset_right = 500.0 +offset_bottom = 50.0 +text = "TODO: Steuerungs-Einstellungen" + +[node name="Spieloptionen" type="Control" parent="Categories"] +visible = false +layout_mode = 2 + +[node name="Label" type="Label" parent="Categories/Spieloptionen"] +offset_left = 20.0 +offset_top = 20.0 +offset_right = 500.0 +offset_bottom = 50.0 +text = "TODO: Spieloptionen" + +[node name="BackButton" type="Button" parent="."] +offset_left = 40.0 +offset_top = 640.0 +offset_right = 160.0 +offset_bottom = 675.0 +text = "Zurück" diff --git a/player/equipment/equipment.gd b/player/equipment/equipment.gd new file mode 100644 index 0000000..5d64df9 --- /dev/null +++ b/player/equipment/equipment.gd @@ -0,0 +1,15 @@ +extends Node +class_name Equipment +## Verwaltet die aktuell ausgerüsteten Gegenstände des Spielers. +## TODO: Slot-Regeln definieren, sobald Ausrüstungsgegenstände existieren. + +var equipped_items: Array[EquipmentItem] = [] + +func equip(item: EquipmentItem) -> void: + pass # TODO: Gegenstand ausrüsten (Slot-Logik, Ersetzen, Signale, ...) + +func unequip(item: EquipmentItem) -> void: + pass # TODO: Gegenstand ablegen + +func get_equipped() -> Array[EquipmentItem]: + return equipped_items diff --git a/player/equipment/equipment.gd.uid b/player/equipment/equipment.gd.uid new file mode 100644 index 0000000..5e3b85f --- /dev/null +++ b/player/equipment/equipment.gd.uid @@ -0,0 +1 @@ +uid://c6rkww7tbx5dc diff --git a/player/equipment/equipment_item.gd b/player/equipment/equipment_item.gd new file mode 100644 index 0000000..d6323c1 --- /dev/null +++ b/player/equipment/equipment_item.gd @@ -0,0 +1,7 @@ +extends Resource +class_name EquipmentItem +## Basis-Ressource für zukünftige Ausrüstungsgegenstände (Waffen, Rüstung, ...). +## TODO: konkrete Item-Typen als eigene Resource-Subklassen anlegen, sobald nötig. + +@export var item_name: String = "" +@export var icon: Texture2D diff --git a/player/equipment/equipment_item.gd.uid b/player/equipment/equipment_item.gd.uid new file mode 100644 index 0000000..8c8858d --- /dev/null +++ b/player/equipment/equipment_item.gd.uid @@ -0,0 +1 @@ +uid://xchpeoiegfv3 diff --git a/player/player.gd b/player/player.gd index ce64b65..35e8f89 100755 --- a/player/player.gd +++ b/player/player.gd @@ -6,6 +6,8 @@ extends CharacterBody3D var camera_rig: Node3D @onready var sprite: AnimatedSprite3D = $AnimatedSprite3D +# Nur in world/island.tscn vorhanden, nicht in diesem Debug-Prototyp -> optional +@onready var equipment: Equipment = get_node_or_null("Equipment") func _ready() -> void: camera_rig = get_tree().get_first_node_in_group("camera_rig") diff --git a/project.godot b/project.godot index 364ac0c..42efbc5 100755 --- a/project.godot +++ b/project.godot @@ -11,13 +11,14 @@ config_version=5 [application] config/name="WayfarersWanderer" -run/main_scene="uid://bju1lndv8l1mo" +run/main_scene="res://boot/opening_screen.tscn" config/features=PackedStringArray("4.6", "GL Compatibility") [autoload] InteractionManager="*uid://cbg1abspfwgxo" InteractionUI="*uid://cmg2008xfel2v" +SaveManager="*res://autoload/save_manager.gd" [display] diff --git a/world/island.tscn b/world/island.tscn index f87204b..22d1d2e 100755 --- a/world/island.tscn +++ b/world/island.tscn @@ -4,6 +4,7 @@ [ext_resource type="Script" uid="uid://cpo34pbk0145n" path="res://terrain.gd" id="1_terrain"] [ext_resource type="Texture2D" uid="uid://epcqli428yem" path="res://assets/sprites/player/debug_player.png" id="2_h2yge"] [ext_resource type="Script" uid="uid://b0y06y7c0bihj" path="res://camera_rig.gd" id="3_h2yge"] +[ext_resource type="Script" path="res://player/equipment/equipment.gd" id="4_equip"] [ext_resource type="PackedScene" uid="uid://be6ufrc3dsjge" path="res://poi.tscn" id="5_lquwl"] [ext_resource type="PackedScene" path="res://world/poi/poi_gate.tscn" id="5_gate"] [ext_resource type="PackedScene" uid="uid://dmhs1oy1kh6hi" path="res://assets/models/mountain.glb" id="6_7mycd"] @@ -190,6 +191,9 @@ texture_filter = 0 sprite_frames = SubResource("SpriteFrames_82xsv") animation = &"idle" +[node name="Equipment" type="Node" parent="Player"] +script = ExtResource("4_equip") + [node name="CameraRig" type="Node3D" parent="." unique_id=1094565525 groups=["camera_rig"]] script = ExtResource("3_h2yge") target = NodePath("../Player")