Add boot flow, save-system skeleton, and Equipment skeleton
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.
This commit is contained in:
parent
c6d7e6c84e
commit
67862a46f3
37
autoload/save_manager.gd
Normal file
37
autoload/save_manager.gd
Normal file
@ -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
|
||||
1
autoload/save_manager.gd.uid
Normal file
1
autoload/save_manager.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dhxbb41fxvo3q
|
||||
6
boot/credits_screen.gd
Normal file
6
boot/credits_screen.gd
Normal file
@ -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"))
|
||||
1
boot/credits_screen.gd.uid
Normal file
1
boot/credits_screen.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cgyurqw4rcvn0
|
||||
38
boot/credits_screen.tscn
Normal file
38
boot/credits_screen.tscn
Normal file
@ -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"
|
||||
45
boot/main_menu.gd
Normal file
45
boot/main_menu.gd
Normal file
@ -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()
|
||||
1
boot/main_menu.gd.uid
Normal file
1
boot/main_menu.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b210xm2monnj3
|
||||
65
boot/main_menu.tscn
Normal file
65
boot/main_menu.tscn
Normal file
@ -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"
|
||||
29
boot/opening_screen.gd
Normal file
29
boot/opening_screen.gd
Normal file
@ -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")
|
||||
1
boot/opening_screen.gd.uid
Normal file
1
boot/opening_screen.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://6s84w6vbvidp
|
||||
38
boot/opening_screen.tscn
Normal file
38
boot/opening_screen.tscn
Normal file
@ -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
|
||||
6
boot/options_menu.gd
Normal file
6
boot/options_menu.gd
Normal file
@ -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"))
|
||||
1
boot/options_menu.gd.uid
Normal file
1
boot/options_menu.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bt1qf4bx8rbhs
|
||||
74
boot/options_menu.tscn
Normal file
74
boot/options_menu.tscn
Normal file
@ -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"
|
||||
15
player/equipment/equipment.gd
Normal file
15
player/equipment/equipment.gd
Normal file
@ -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
|
||||
1
player/equipment/equipment.gd.uid
Normal file
1
player/equipment/equipment.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c6rkww7tbx5dc
|
||||
7
player/equipment/equipment_item.gd
Normal file
7
player/equipment/equipment_item.gd
Normal file
@ -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
|
||||
1
player/equipment/equipment_item.gd.uid
Normal file
1
player/equipment/equipment_item.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://xchpeoiegfv3
|
||||
@ -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")
|
||||
|
||||
@ -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]
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user