Add localization support and refactor menus

- Implemented LocalizationManager for handling multiple languages.
- Updated LoadGameMenu, MainMenu, PauseMenu, and SettingsMenu to support localization.
- Added InstanceSelectionMenu for selecting game instances.
- Refactored SaveManager to handle new SaveInstance structure.
- Created SaveDialog for saving games with user-defined names.
- Enhanced SaveData to manage multiple save instances.
- Added error handling and logging for save/load operations.
- Updated UI elements to reflect localized text.
This commit is contained in:
Björn Blomberg
2025-10-17 14:22:21 +02:00
parent b37fe0a858
commit 74062a37d6
32 changed files with 2304 additions and 46 deletions

View File

@@ -8,11 +8,11 @@ Ett 2D spel byggt i Godot med C# som innehåller en startmeny, timer och save/lo
- **Timer** som räknar speltid i övre högra hörnet
- **Pausmeny** med knapp i övre vänstra hörnet som innehåller:
- Pause/Resume funktionalitet
- Save Game
- Save Game (med anpassade save-namn)
- Exit to Main Menu
- Exit to Desktop
- **Save/Load System** som sparar speltid i `saves/` mappen
- **Load Game Menu** som visar alla sparade spel med datum
- **Save/Load System** som sparar speltid och anpassade namn
- **Load Game Menu** som visar alla sparade spel med anpassade namn, speltid och datum
- Load Game knappen är automatiskt utgråad om inga sparade spel finns
## Krav och Dependencies
@@ -76,8 +76,11 @@ Under **Dotnet** sektionen:
2. Välj destination och filnamn (t.ex. `TheGame.exe`)
3. Klicka **Save**
**Viktigt**: Språkfiler (`resources/languages/*.json`) inkluderas automatiskt i exporten!
#### Steg 5: Testa EXE-filen
- Den skapade `.exe` filen kan köras direkt på Windows
- Språkväxling fungerar direkt i den exporterade versionen
- `saves/` mappen kommer att skapas bredvid exe-filen automatiskt
### Felsökning Export Problem
@@ -111,17 +114,27 @@ godot --headless --export-release "Windows Desktop" TheGame.exe
TheGame/
├── project.godot # Godot projektfil
├── TheGame.csproj # C# projektfil
├── default_bus_layout.tres # Audio bus konfiguration
├── scenes/ # Godot scener
│ ├── MainMenu.tscn # Huvudmeny
│ ├── Game.tscn # Spelscen
── LoadGameMenu.tscn # Ladda spel meny
── LoadGameMenu.tscn # Ladda spel meny
│ └── SettingsMenu.tscn # Inställningar meny
├── scripts/ # C# scripts
│ ├── MainMenu.cs # Huvudmeny logik
│ ├── Game.cs # Spel logik och timer
│ ├── PauseMenu.cs # Pausmeny logik
│ ├── SaveManager.cs # Save/Load hantering
│ ├── LoadGameMenu.cs # Ladda spel meny
── GameState.cs # Spelstatus hantering
── SaveDialog.cs # Save dialog
│ ├── GameState.cs # Spelstatus hantering
│ ├── GameSettings.cs # Inställningar hantering
│ ├── LocalizationManager.cs # Språkhantering
│ └── SettingsMenu.cs # Inställningar meny logik
├── resources/ # Resurser som inkluderas vid export
│ └── languages/ # Språkfiler
│ ├── eng.json # Engelska översättningar
│ └── sve.json # Svenska översättningar
├── saves/ # Sparade spel (skapas automatiskt)
└── README.md # Denna fil
```
@@ -131,6 +144,25 @@ TheGame/
- **ESC** - Pausa/återuppta spelet
- **Musklick** - Navigera menyer och knappar
- **Pausknapp** (☰) - Pausa spelet (övre vänstra hörnet)
- **Enter** - Bekräfta save-namn i save dialog
## Save/Load System
### Spara Spel
1. Pausa spelet (ESC eller pausknapp)
2. Klicka "Save Game"
3. Ange ett anpassat namn för ditt save (eller använd standard-namnet)
4. Tryck Enter eller klicka OK
### Ladda Spel
1. Från huvudmenyn, klicka "Load Game"
2. Välj det save du vill ladda från listan
3. Spelet kommer att fortsätta från den sparade tiden
Save-filer innehåller:
- **Anpassat namn** (som du angav)
- **Speltid** (timer-status)
- **Datum och tid** när spelet sparades
## Felsökning
@@ -152,6 +184,31 @@ TheGame/
- Detta är förväntat beteende om inga sparfiler finns
- Knappen blir automatiskt klickbar när du har sparat minst ett spel
5. **Språk växlar inte**
- Kontrollera att språkfilen finns i `resources/languages/`
- Kolla Godot's output för felmeddelanden
- Engelska används som fallback om språkfil saknas
## 🌍 Lägg Till Nya Språk
### Skapa Ny Språkfil:
1. Skapa ny `.json` fil i `resources/languages/`
2. Exempel: `ger.json` för tyska, `fra.json` för franska
3. Kopiera struktur från `eng.json`
4. Översätt alla textsträngar
5. Språket dyker automatiskt upp i settings!
### Rekommenderade Språkkoder:
- `eng` - English
- `sve` - Svenska
- `ger` - Deutsch (German)
- `fra` - Français (French)
- `spa` - Español (Spanish)
- `ita` - Italiano (Italian)
- `jpn` - 日本語 (Japanese)
**✅ Viktigt**: Språkfiler i `resources/languages/` inkluderas automatiskt vid export!
## Utveckling
För att utveckla projektet vidare:

21
default_bus_layout.tres Normal file
View File

@@ -0,0 +1,21 @@
[gd_resource type="AudioBusLayout" load_steps=0 format=3 uid="uid://bywagvqncwpol"]
[resource]
bus/1/name = &"Background"
bus/1/solo = false
bus/1/mute = false
bus/1/bypass_fx = false
bus/1/volume_db = 0.0
bus/1/send = &"Master"
bus/2/name = &"Effects"
bus/2/solo = false
bus/2/mute = false
bus/2/bypass_fx = false
bus/2/volume_db = 0.0
bus/2/send = &"Master"
bus/3/name = &"Radio"
bus/3/solo = false
bus/3/mute = false
bus/3/bypass_fx = false
bus/3/volume_db = 0.0
bus/3/send = &"Master"

View File

@@ -15,6 +15,10 @@ run/main_scene="res://scenes/MainMenu.tscn"
config/features=PackedStringArray("4.5", "C#", "Forward Plus")
config/icon="res://icon.svg"
[audio]
buses/default_bus_layout="res://default_bus_layout.tres"
[display]
window/size/viewport_width=1280

View File

@@ -0,0 +1,51 @@
{
"main_menu_title": "THE GAME",
"main_menu_start": "Start Game",
"main_menu_load": "Load Game",
"main_menu_settings": "Settings",
"main_menu_exit": "Exit",
"pause_menu_title": "PAUSED",
"pause_menu_resume": "Resume",
"pause_menu_save": "Save Game",
"pause_menu_settings": "Settings",
"pause_menu_main_menu": "Exit to Main Menu",
"pause_menu_desktop": "Exit to Desktop",
"load_game_title": "LOAD GAME",
"load_game_back": "Back to Main Menu",
"load_game_no_saves": "No saved games found",
"load_game_playtime": "Playtime",
"load_game_saved": "Saved",
"save_dialog_title": "Save Game",
"save_dialog_enter_name": "Enter save name:",
"save_dialog_placeholder": "My Save Game",
"settings_title": "SETTINGS",
"settings_general": "General",
"settings_video": "Video",
"settings_audio": "Audio",
"settings_apply": "Apply",
"settings_cancel": "Cancel",
"settings_back": "Back",
"settings_language": "Language",
"settings_fullscreen": "Fullscreen",
"settings_master_volume": "Master Volume",
"settings_background_volume": "Background Volume",
"settings_effects_volume": "Effects Volume",
"settings_radio_volume": "Radio Volume",
"game_content": "GAME CONTENT HERE\\nPress ESC or click pause button to pause",
"exit_warning_title": "Unsaved Progress",
"exit_warning_message": "You have unsaved progress. If you continue, all unsaved progress will be lost.\\n\\nDo you want to exit anyway?",
"ok": "OK",
"cancel": "Cancel",
"yes": "Yes",
"no": "No"
}

View File

@@ -0,0 +1,51 @@
{
"main_menu_title": "SPELET",
"main_menu_start": "Starta Spel",
"main_menu_load": "Ladda Spel",
"main_menu_settings": "Inställningar",
"main_menu_exit": "Avsluta",
"pause_menu_title": "PAUSAD",
"pause_menu_resume": "Fortsätt",
"pause_menu_save": "Spara Spel",
"pause_menu_settings": "Inställningar",
"pause_menu_main_menu": "Tillbaka till Huvudmeny",
"pause_menu_desktop": "Avsluta till Skrivbord",
"load_game_title": "LADDA SPEL",
"load_game_back": "Tillbaka till Huvudmeny",
"load_game_no_saves": "Inga sparade spel hittades",
"load_game_playtime": "Speltid",
"load_game_saved": "Sparat",
"save_dialog_title": "Spara Spel",
"save_dialog_enter_name": "Ange sparnamn:",
"save_dialog_placeholder": "Mitt Sparade Spel",
"settings_title": "INSTÄLLNINGAR",
"settings_general": "Allmänt",
"settings_video": "Video",
"settings_audio": "Ljud",
"settings_apply": "Tillämpa",
"settings_cancel": "Avbryt",
"settings_back": "Tillbaka",
"settings_language": "Språk",
"settings_fullscreen": "Helskärm",
"settings_master_volume": "Huvudvolym",
"settings_background_volume": "Bakgrundsljud",
"settings_effects_volume": "Effekter",
"settings_radio_volume": "Radio",
"game_content": "SPELINNEHÅLL HÄR\\nTryck ESC eller klicka på pausknappen för att pausa",
"exit_warning_title": "Osparad Framsteg",
"exit_warning_message": "Du har osparad framsteg. Om du fortsätter kommer all osparad framsteg att förloras.\\n\\nVill du avsluta ändå?",
"ok": "OK",
"cancel": "Avbryt",
"yes": "Ja",
"no": "Nej"
}

View File

@@ -68,6 +68,7 @@ layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
process_mode = 3
script = ExtResource("2_a5qiy")
[node name="Background" type="ColorRect" parent="PauseMenu"]
@@ -105,6 +106,10 @@ text = "Resume"
layout_mode = 2
text = "Save Game"
[node name="SettingsButton" type="Button" parent="PauseMenu/MenuContainer"]
layout_mode = 2
text = "Settings"
[node name="MainMenuButton" type="Button" parent="PauseMenu/MenuContainer"]
layout_mode = 2
text = "Exit to Main Menu"
@@ -116,5 +121,6 @@ text = "Exit to Desktop"
[connection signal="pressed" from="UI/PauseButton" to="." method="_on_pause_button_pressed"]
[connection signal="pressed" from="PauseMenu/MenuContainer/ResumeButton" to="PauseMenu" method="_on_resume_button_pressed"]
[connection signal="pressed" from="PauseMenu/MenuContainer/SaveButton" to="PauseMenu" method="_on_save_button_pressed"]
[connection signal="pressed" from="PauseMenu/MenuContainer/SettingsButton" to="PauseMenu" method="_on_settings_button_pressed"]
[connection signal="pressed" from="PauseMenu/MenuContainer/MainMenuButton" to="PauseMenu" method="_on_main_menu_button_pressed"]
[connection signal="pressed" from="PauseMenu/MenuContainer/DesktopButton" to="PauseMenu" method="_on_desktop_button_pressed"]

View File

@@ -45,10 +45,15 @@ text = "Start Game"
layout_mode = 2
text = "Load Game"
[node name="SettingsButton" type="Button" parent="VBoxContainer"]
layout_mode = 2
text = "Settings"
[node name="ExitButton" type="Button" parent="VBoxContainer"]
layout_mode = 2
text = "Exit"
[connection signal="pressed" from="VBoxContainer/StartButton" to="." method="_on_start_button_pressed"]
[connection signal="pressed" from="VBoxContainer/LoadButton" to="." method="_on_load_button_pressed"]
[connection signal="pressed" from="VBoxContainer/SettingsButton" to="." method="_on_settings_button_pressed"]
[connection signal="pressed" from="VBoxContainer/ExitButton" to="." method="_on_exit_button_pressed"]

193
scenes/SettingsMenu.tscn Normal file
View File

@@ -0,0 +1,193 @@
[gd_scene load_steps=2 format=3 uid="uid://b2k5vxrj8nq4h"]
[ext_resource type="Script" path="res://scripts/SettingsMenu.cs" id="1_settings"]
[node name="SettingsMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
process_mode = 3
script = ExtResource("1_settings")
[node name="Background" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0.15, 0.15, 0.25, 1)
[node name="MainContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -400.0
offset_top = -300.0
offset_right = 400.0
offset_bottom = 300.0
[node name="TitleLabel" type="Label" parent="MainContainer"]
layout_mode = 2
text = "SETTINGS"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="MainContainer"]
layout_mode = 2
[node name="TabContainer" type="TabContainer" parent="MainContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="General" type="Control" parent="MainContainer/TabContainer"]
layout_mode = 2
[node name="GeneralVBox" type="VBoxContainer" parent="MainContainer/TabContainer/General"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 20.0
offset_top = 20.0
offset_right = -20.0
offset_bottom = -20.0
[node name="LanguageContainer" type="HBoxContainer" parent="MainContainer/TabContainer/General/GeneralVBox"]
layout_mode = 2
[node name="LanguageLabel" type="Label" parent="MainContainer/TabContainer/General/GeneralVBox/LanguageContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Language:"
vertical_alignment = 1
[node name="LanguageOption" type="OptionButton" parent="MainContainer/TabContainer/General/GeneralVBox/LanguageContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Video" type="Control" parent="MainContainer/TabContainer"]
layout_mode = 2
[node name="VideoVBox" type="VBoxContainer" parent="MainContainer/TabContainer/Video"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 20.0
offset_top = 20.0
offset_right = -20.0
offset_bottom = -20.0
[node name="FullscreenContainer" type="HBoxContainer" parent="MainContainer/TabContainer/Video/VideoVBox"]
layout_mode = 2
[node name="FullscreenLabel" type="Label" parent="MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Fullscreen:"
vertical_alignment = 1
[node name="FullscreenCheck" type="CheckBox" parent="MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer"]
layout_mode = 2
[node name="Audio" type="Control" parent="MainContainer/TabContainer"]
layout_mode = 2
[node name="AudioVBox" type="VBoxContainer" parent="MainContainer/TabContainer/Audio"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 20.0
offset_top = 20.0
offset_right = -20.0
offset_bottom = -20.0
[node name="MasterContainer" type="VBoxContainer" parent="MainContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
[node name="MasterLabel" type="Label" parent="MainContainer/TabContainer/Audio/AudioVBox/MasterContainer"]
layout_mode = 2
text = "Master Volume: 100"
[node name="MasterSlider" type="HSlider" parent="MainContainer/TabContainer/Audio/AudioVBox/MasterContainer"]
layout_mode = 2
min_value = 1.0
max_value = 100.0
step = 1.0
value = 100.0
[node name="BackgroundContainer" type="VBoxContainer" parent="MainContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
[node name="BackgroundLabel" type="Label" parent="MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer"]
layout_mode = 2
text = "Background Volume: 100"
[node name="BackgroundSlider" type="HSlider" parent="MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer"]
layout_mode = 2
min_value = 1.0
max_value = 100.0
step = 1.0
value = 100.0
[node name="EffectsContainer" type="VBoxContainer" parent="MainContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
[node name="EffectsLabel" type="Label" parent="MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer"]
layout_mode = 2
text = "Effects Volume: 100"
[node name="EffectsSlider" type="HSlider" parent="MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer"]
layout_mode = 2
min_value = 1.0
max_value = 100.0
step = 1.0
value = 100.0
[node name="RadioContainer" type="VBoxContainer" parent="MainContainer/TabContainer/Audio/AudioVBox"]
layout_mode = 2
[node name="RadioLabel" type="Label" parent="MainContainer/TabContainer/Audio/AudioVBox/RadioContainer"]
layout_mode = 2
text = "Radio Volume: 100"
[node name="RadioSlider" type="HSlider" parent="MainContainer/TabContainer/Audio/AudioVBox/RadioContainer"]
layout_mode = 2
min_value = 1.0
max_value = 100.0
step = 1.0
value = 100.0
[node name="HSeparator2" type="HSeparator" parent="MainContainer"]
layout_mode = 2
[node name="ButtonContainer" type="HBoxContainer" parent="MainContainer"]
layout_mode = 2
[node name="ApplyButton" type="Button" parent="MainContainer/ButtonContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Apply"
[node name="CancelButton" type="Button" parent="MainContainer/ButtonContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Cancel"
[node name="BackButton" type="Button" parent="MainContainer/ButtonContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Back"
[connection signal="item_selected" from="MainContainer/TabContainer/General/GeneralVBox/LanguageContainer/LanguageOption" to="." method="_on_language_option_item_selected"]
[connection signal="toggled" from="MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer/FullscreenCheck" to="." method="_on_fullscreen_check_toggled"]
[connection signal="value_changed" from="MainContainer/TabContainer/Audio/AudioVBox/MasterContainer/MasterSlider" to="." method="_on_master_slider_value_changed"]
[connection signal="value_changed" from="MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer/BackgroundSlider" to="." method="_on_background_slider_value_changed"]
[connection signal="value_changed" from="MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer/EffectsSlider" to="." method="_on_effects_slider_value_changed"]
[connection signal="value_changed" from="MainContainer/TabContainer/Audio/AudioVBox/RadioContainer/RadioSlider" to="." method="_on_radio_slider_value_changed"]
[connection signal="pressed" from="MainContainer/ButtonContainer/ApplyButton" to="." method="_on_apply_button_pressed"]
[connection signal="pressed" from="MainContainer/ButtonContainer/CancelButton" to="." method="_on_cancel_button_pressed"]
[connection signal="pressed" from="MainContainer/ButtonContainer/BackButton" to="." method="_on_back_button_pressed"]

View File

@@ -0,0 +1,302 @@
using Godot;
namespace TheGame
{
public partial class AdvancedSaveDialog : AcceptDialog
{
private LineEdit _nameInput;
private CheckBox _newInstanceCheck;
private Label _infoLabel;
private Game _gameController;
private SaveData _existingSaveData;
public override void _Ready()
{
// Sätt process mode så att dialogen fungerar när spelet är pausat
ProcessMode = ProcessModeEnum.WhenPaused;
var loc = LocalizationManager.Instance;
Title = loc.GetText("save_dialog_title");
// Kontrollera om det finns befintlig save för denna seed
var saveManager = new SaveManager();
if (GameState.CurrentGameSeed != null)
{
_existingSaveData = saveManager.FindSaveDataBySeed(GameState.CurrentGameSeed);
}
CreateDialogContent();
// Sätt process mode på alla child nodes
SetProcessModeRecursive(this, ProcessModeEnum.WhenPaused);
}
private void CreateDialogContent()
{
var vbox = new VBoxContainer();
if (_existingSaveData == null || _existingSaveData.Instances.Count == 0)
{
// Första gången man sparar detta spel - bara namn input
CreateFirstTimeSaveUI(vbox);
}
else
{
// Det finns befintliga saves - visa alternativ
CreateExistingSaveUI(vbox);
}
AddChild(vbox);
}
private void CreateFirstTimeSaveUI(VBoxContainer vbox)
{
var loc = LocalizationManager.Instance;
var infoLabel = new Label();
infoLabel.Text = "This is your first save for this game.";
infoLabel.AutowrapMode = TextServer.AutowrapMode.WordSmart;
vbox.AddChild(infoLabel);
var separator = new HSeparator();
vbox.AddChild(separator);
// Save name input
var nameLabel = new Label();
nameLabel.Text = loc.GetText("save_dialog_enter_name");
vbox.AddChild(nameLabel);
_nameInput = new LineEdit();
_nameInput.PlaceholderText = loc.GetText("save_dialog_placeholder");
_nameInput.Text = $"Save {System.DateTime.Now:HH:mm:ss}";
vbox.AddChild(_nameInput);
// Anslut signaler
Confirmed += OnSaveConfirmed;
// Fokusera på text input
_nameInput.GrabFocus();
_nameInput.SelectAll();
}
private void CreateExistingSaveUI(VBoxContainer vbox)
{
var latestInstance = _existingSaveData.GetLatestInstance();
var infoLabel = new Label();
infoLabel.Text = $"Game Seed: {_existingSaveData.GameSeed.ToString()}\nExisting saves: {_existingSaveData.Instances.Count}\nLatest: {latestInstance.DisplayName} ({latestInstance.GetFormattedGameTime()})";
infoLabel.AutowrapMode = TextServer.AutowrapMode.WordSmart;
vbox.AddChild(infoLabel);
var separator = new HSeparator();
vbox.AddChild(separator);
// Button för att skriva över senaste save
var overwriteButton = new Button();
overwriteButton.Text = "Save over the last save point";
overwriteButton.CustomMinimumSize = new Vector2(0, 40);
overwriteButton.Pressed += OnOverwriteLatestPressed;
vbox.AddChild(overwriteButton);
// Button för att skapa ny save point
var newSaveButton = new Button();
newSaveButton.Text = "Save a new save point";
newSaveButton.CustomMinimumSize = new Vector2(0, 40);
newSaveButton.Pressed += OnNewSavePressed;
vbox.AddChild(newSaveButton);
// Dölj standard OK-knappen
GetOkButton().Visible = false;
}
public void Initialize(Game gameController)
{
_gameController = gameController;
}
private void ShowErrorDialog(string message)
{
var errorDialog = new AcceptDialog();
errorDialog.DialogText = message;
errorDialog.Title = "Save Error";
errorDialog.ProcessMode = ProcessModeEnum.WhenPaused;
GetTree().Root.AddChild(errorDialog);
errorDialog.PopupCentered();
}
private void OnOverwriteLatestPressed()
{
// Skriv över senaste save utan att fråga om namn
if (_gameController != null)
{
var saveManager = new SaveManager();
float currentTime = _gameController.GetGameTime();
bool saveSuccess = saveManager.SaveGame(currentTime, "", GameState.CurrentGameSeed, true); // overwriteLatest = true
if (saveSuccess)
{
GameState.MarkProgressAsSaved(currentTime);
_gameController.TogglePause();
}
else
{
ShowErrorDialog("Failed to save the game. Please try again.");
return;
}
}
QueueFree();
}
private void OnNewSavePressed()
{
// Visa namn-input dialog för ny save point
ShowNameInputDialog();
}
private void ShowNameInputDialog()
{
// Rensa nuvarande innehåll
foreach (Node child in GetChildren())
{
if (child != GetOkButton())
{
child.QueueFree();
}
}
var vbox = new VBoxContainer();
var loc = LocalizationManager.Instance;
var nameLabel = new Label();
nameLabel.Text = loc.GetText("save_dialog_enter_name");
vbox.AddChild(nameLabel);
_nameInput = new LineEdit();
_nameInput.PlaceholderText = loc.GetText("save_dialog_placeholder");
_nameInput.Text = $"Save {System.DateTime.Now:HH:mm:ss}";
vbox.AddChild(_nameInput);
AddChild(vbox);
// Visa OK-knappen igen och anslut till ny save
GetOkButton().Visible = true;
// Ta bort tidigare signaler och lägg till ny
if (IsConnected("confirmed", new Callable(this, nameof(OnSaveConfirmed))))
{
Disconnect("confirmed", new Callable(this, nameof(OnSaveConfirmed)));
}
Confirmed += OnNewSaveConfirmed;
// Fokusera på text input
_nameInput.GrabFocus();
_nameInput.SelectAll();
}
private void OnNewSaveConfirmed()
{
if (_gameController != null)
{
string saveName = _nameInput.Text.Trim();
if (string.IsNullOrEmpty(saveName))
{
saveName = $"Save {System.DateTime.Now:HH:mm:ss}";
}
var saveManager = new SaveManager();
float currentTime = _gameController.GetGameTime();
bool saveSuccess = saveManager.SaveGame(currentTime, saveName, GameState.CurrentGameSeed, false); // overwriteLatest = false (ny instans)
if (saveSuccess)
{
GameState.MarkProgressAsSaved(currentTime);
_gameController.TogglePause();
}
else
{
ShowErrorDialog("Failed to save the game. Please try again.");
return;
}
}
QueueFree();
}
private void OnSaveConfirmed()
{
// För första gången man sparar
if (_gameController != null)
{
string saveName = _nameInput.Text.Trim();
if (string.IsNullOrEmpty(saveName))
{
saveName = $"Save {System.DateTime.Now:HH:mm:ss}";
}
var saveManager = new SaveManager();
float currentTime = _gameController.GetGameTime();
bool saveSuccess = saveManager.SaveGame(currentTime, saveName, GameState.CurrentGameSeed, false); // Första save är alltid ny instans
if (saveSuccess)
{
GameState.MarkProgressAsSaved(currentTime);
_gameController.TogglePause();
}
else
{
ShowErrorDialog("Failed to save the game. Please try again.");
return;
}
}
QueueFree();
}
private void SetProcessModeRecursive(Node node, ProcessModeEnum mode)
{
if (node is Control control)
{
control.ProcessMode = mode;
}
foreach (Node child in node.GetChildren())
{
SetProcessModeRecursive(child, mode);
}
}
public override void _Input(InputEvent @event)
{
if (@event is InputEventKey keyEvent && keyEvent.Pressed)
{
if (keyEvent.Keycode == Key.Enter || keyEvent.Keycode == Key.KpEnter)
{
// Hantera Enter baserat på nuvarande state
if (_nameInput != null && _nameInput.Visible)
{
if (IsConnected("confirmed", new Callable(this, nameof(OnNewSaveConfirmed))))
{
OnNewSaveConfirmed();
}
else
{
OnSaveConfirmed();
}
}
GetViewport().SetInputAsHandled();
}
else if (keyEvent.Keycode == Key.Escape)
{
Hide();
QueueFree();
GetViewport().SetInputAsHandled();
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,106 @@
using Godot;
using System;
namespace TheGame
{
public partial class ExitConfirmationDialog : AcceptDialog
{
private Action _onConfirm;
private Button _yesButton;
private Button _noButton;
private Label _messageLabel;
public override void _Ready()
{
// Lägg till i localized_ui gruppen
AddToGroup("localized_ui");
// Skapa dialog layout
SetupDialog();
UpdateLocalization();
}
private void SetupDialog()
{
// Sätt dialog egenskaper
Title = "";
Unresizable = false;
ProcessMode = ProcessModeEnum.WhenPaused;
// Ta bort standard OK knapp
GetOkButton().QueueFree();
// Skapa innehåll
var vbox = new VBoxContainer();
AddChild(vbox);
_messageLabel = new Label();
_messageLabel.HorizontalAlignment = HorizontalAlignment.Center;
_messageLabel.AutowrapMode = TextServer.AutowrapMode.WordSmart;
vbox.AddChild(_messageLabel);
// Lägg till mellanrum
var spacer = new Control();
spacer.CustomMinimumSize = new Vector2(0, 20);
vbox.AddChild(spacer);
// Skapa knappar
var buttonContainer = new HBoxContainer();
buttonContainer.Alignment = BoxContainer.AlignmentMode.Center;
vbox.AddChild(buttonContainer);
_yesButton = new Button();
_yesButton.Pressed += OnYesPressed;
buttonContainer.AddChild(_yesButton);
// Lägg till mellanrum mellan knappar
var buttonSpacer = new Control();
buttonSpacer.CustomMinimumSize = new Vector2(20, 0);
buttonContainer.AddChild(buttonSpacer);
_noButton = new Button();
_noButton.Pressed += OnNoPressed;
buttonContainer.AddChild(_noButton);
// Sätt minimum storlek för dialog
Size = new Vector2I(400, 200);
}
public void UpdateLocalization()
{
var loc = LocalizationManager.Instance;
Title = loc.GetText("exit_warning_title");
_messageLabel.Text = loc.GetText("exit_warning_message");
_yesButton.Text = loc.GetText("yes");
_noButton.Text = loc.GetText("no");
}
public void ShowDialog(Action onConfirm)
{
_onConfirm = onConfirm;
PopupCentered();
_noButton.GrabFocus(); // Fokusera på "Nej" som säkrare alternativ
}
private void OnYesPressed()
{
Hide();
_onConfirm?.Invoke();
}
private void OnNoPressed()
{
Hide();
}
public override void _Input(InputEvent @event)
{
// Hantera ESC för att stänga dialog (samma som "Nej")
if (@event.IsActionPressed("ui_cancel") && Visible)
{
OnNoPressed();
GetViewport().SetInputAsHandled();
}
}
}
}

View File

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

View File

@@ -6,20 +6,57 @@ namespace TheGame
{
private Label _timerLabel;
private Control _pauseMenu;
private Label _gameContentLabel;
private float _gameTime = 0.0f;
private bool _isPaused = false;
private ExitConfirmationDialog _exitDialog;
public override void _Ready()
{
// Lägg till i localized_ui gruppen
AddToGroup("localized_ui");
_timerLabel = GetNode<Label>("UI/TimerLabel");
_pauseMenu = GetNode<Control>("PauseMenu");
_gameContentLabel = GetNode<Label>("GameContent");
// Om vi kommer från Load Game, försök ladda sparat tillstånd
// Sätt process mode så att pausmenyn fungerar när spelet är pausat
_pauseMenu.ProcessMode = ProcessModeEnum.WhenPaused;
// Kontrollera om vi laddar ett sparat spel eller startar nytt
if (GameState.LoadedGameTime >= 0)
{
// Laddar befintligt spel
_gameTime = GameState.LoadedGameTime;
GameState.LastSavedGameTime = _gameTime; // Sätt senaste sparade tid
GameState.LoadedGameTime = -1; // Reset efter användning
GD.Print($"Loaded existing game with seed: {GameState.CurrentGameSeed?.ToString() ?? "None"}");
}
else
{
// Nytt spel - skapa ny seed om ingen finns
if (GameState.CurrentGameSeed == null)
{
GameState.CurrentGameSeed = new GameSeed();
GD.Print($"Started new game with seed: {GameState.CurrentGameSeed.ToString()}");
}
GameState.LastSavedGameTime = 0;
}
// Skapa exit confirmation dialog
_exitDialog = new ExitConfirmationDialog();
AddChild(_exitDialog);
// Hantera fönsterstängning
GetTree().AutoAcceptQuit = false;
UpdateLocalization();
}
public void UpdateLocalization()
{
var loc = LocalizationManager.Instance;
_gameContentLabel.Text = loc.GetText("game_content");
}
public override void _Process(double delta)
@@ -28,6 +65,36 @@ namespace TheGame
{
_gameTime += (float)delta;
UpdateTimerDisplay();
// Kontrollera om det finns osparad progress
GameState.CheckUnsavedProgress(_gameTime);
}
}
public override void _Notification(int what)
{
if (what == NotificationWMCloseRequest)
{
// Hantera X-knappen på fönstret
HandleExitRequest(() => GetTree().Quit());
}
}
public void HandleExitRequest(System.Action exitAction)
{
if (GameState.HasUnsavedProgress)
{
// Pausa spelet och visa varning
if (!_isPaused)
{
TogglePause();
}
_exitDialog.ShowDialog(exitAction);
}
else
{
// Ingen osparad progress, avsluta direkt
exitAction?.Invoke();
}
}

55
scripts/GameSeed.cs Normal file
View File

@@ -0,0 +1,55 @@
using System;
namespace TheGame
{
public class GameSeed
{
public string SeedValue { get; set; }
public DateTime CreatedDate { get; set; }
public string DisplayName { get; set; }
public GameSeed()
{
SeedValue = GenerateNewSeed();
CreatedDate = DateTime.Now;
DisplayName = $"Game {CreatedDate:yyyy-MM-dd HH:mm}";
}
public GameSeed(string customSeed, string displayName = "")
{
SeedValue = customSeed;
CreatedDate = DateTime.Now;
DisplayName = string.IsNullOrEmpty(displayName) ? $"Game {CreatedDate:yyyy-MM-dd HH:mm}" : displayName;
}
private static string GenerateNewSeed()
{
// Generera en 8-tecken seed baserad på timestamp och random
var random = new Random();
var timestamp = DateTime.Now.Ticks;
var combined = timestamp + random.Next(1000, 9999);
// Konvertera till hexadecimal och ta första 8 tecknen
return Math.Abs(combined).ToString("X")[..8];
}
public override string ToString()
{
return $"{DisplayName} (Seed: {SeedValue})";
}
public override bool Equals(object obj)
{
if (obj is GameSeed other)
{
return SeedValue.Equals(other.SeedValue);
}
return false;
}
public override int GetHashCode()
{
return SeedValue.GetHashCode();
}
}
}

1
scripts/GameSeed.cs.uid Normal file
View File

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

148
scripts/GameSettings.cs Normal file
View File

@@ -0,0 +1,148 @@
using Godot;
using System;
using System.IO;
using System.Text.Json;
namespace TheGame
{
public class GameSettings
{
public bool Fullscreen { get; set; } = false;
public int MasterVolume { get; set; } = 100;
public int BackgroundVolume { get; set; } = 100;
public int EffectsVolume { get; set; } = 100;
public int RadioVolume { get; set; } = 100;
public string Language { get; set; } = "eng";
private static GameSettings _instance;
private static readonly string _settingsPath;
static GameSettings()
{
// Spara bredvid binären (executable directory)
string executableDir = OS.GetExecutablePath().GetBaseDir();
_settingsPath = Path.Combine(executableDir, "settings.json");
}
public static GameSettings Instance
{
get
{
if (_instance == null)
{
_instance = LoadSettings();
}
return _instance;
}
}
private static GameSettings LoadSettings()
{
try
{
if (File.Exists(_settingsPath))
{
string jsonString = File.ReadAllText(_settingsPath);
var settings = JsonSerializer.Deserialize<GameSettings>(jsonString);
if (settings != null)
{
GD.Print($"Settings loaded from: {_settingsPath}");
return settings;
}
}
}
catch (Exception ex)
{
GD.PrintErr($"Failed to load settings: {ex.Message}");
}
// Returnera standardinställningar om fil inte finns eller laddning misslyckas
GD.Print("Using default settings");
return new GameSettings();
}
public void SaveSettings()
{
try
{
string jsonString = JsonSerializer.Serialize(this, new JsonSerializerOptions
{
WriteIndented = true
});
File.WriteAllText(_settingsPath, jsonString);
GD.Print($"Settings saved to: {_settingsPath}");
}
catch (Exception ex)
{
GD.PrintErr($"Failed to save settings: {ex.Message}");
}
}
public void ApplyVideoSettings()
{
if (Fullscreen)
{
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
}
else
{
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
}
}
public void ApplyAudioSettings()
{
// Konvertera från 1-100 till dB (Godot använder dB för ljud)
float masterDb = ConvertVolumeToDb(MasterVolume);
float backgroundDb = ConvertVolumeToDb(BackgroundVolume);
float effectsDb = ConvertVolumeToDb(EffectsVolume);
float radioDb = ConvertVolumeToDb(RadioVolume);
// Sätt bus-volym (dessa måste skapas i Audio Bus Layout)
try
{
AudioServer.SetBusVolumeDb(AudioServer.GetBusIndex("Master"), masterDb);
int backgroundIndex = AudioServer.GetBusIndex("Background");
if (backgroundIndex != -1)
AudioServer.SetBusVolumeDb(backgroundIndex, backgroundDb);
int effectsIndex = AudioServer.GetBusIndex("Effects");
if (effectsIndex != -1)
AudioServer.SetBusVolumeDb(effectsIndex, effectsDb);
int radioIndex = AudioServer.GetBusIndex("Radio");
if (radioIndex != -1)
AudioServer.SetBusVolumeDb(radioIndex, radioDb);
GD.Print($"Applied audio settings: Master={MasterVolume}, Background={BackgroundVolume}, Effects={EffectsVolume}, Radio={RadioVolume}");
}
catch (System.Exception ex)
{
GD.PrintErr($"Failed to apply audio settings: {ex.Message}");
}
}
private float ConvertVolumeToDb(int volume)
{
if (volume <= 0)
return -80.0f; // Tyst
// Konvertera 1-100 till -60dB till 0dB
return -60.0f + (volume / 100.0f) * 60.0f;
}
public void ApplyLanguageSettings()
{
LocalizationManager.Instance.SetLanguage(Language);
}
public void ApplyAllSettings()
{
ApplyVideoSettings();
ApplyAudioSettings();
ApplyLanguageSettings();
SaveSettings();
}
}
}

View File

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

View File

@@ -5,5 +5,33 @@ namespace TheGame
public static class GameState
{
public static float LoadedGameTime = -1;
public static GameSeed CurrentGameSeed = null;
public static SaveInstance LoadedInstance = null;
public static bool HasUnsavedProgress = false;
public static float LastSavedGameTime = 0;
public static void MarkProgressAsSaved(float gameTime)
{
LastSavedGameTime = gameTime;
HasUnsavedProgress = false;
}
public static void CheckUnsavedProgress(float currentGameTime)
{
// Om speltiden har ökat sedan senaste sparningen, markera som osparad
if (currentGameTime > LastSavedGameTime + 1.0f) // 1 sekund tolerans
{
HasUnsavedProgress = true;
}
}
public static void ResetGameState()
{
LoadedGameTime = -1;
CurrentGameSeed = null;
LoadedInstance = null;
HasUnsavedProgress = false;
LastSavedGameTime = 0;
}
}
}

View File

@@ -0,0 +1,121 @@
using Godot;
using System.Collections.Generic;
using System.Linq;
namespace TheGame
{
public partial class InstanceSelectionMenu : AcceptDialog
{
private SaveData _saveData;
private VBoxContainer _instanceList;
private Button _cancelButton;
public override void _Ready()
{
ProcessMode = ProcessModeEnum.Always;
Title = "Select Save Instance";
var vbox = new VBoxContainer();
// Header info
var headerLabel = new Label();
headerLabel.Text = $"Game Seed: {_saveData?.GameSeed?.ToString() ?? "Unknown"}";
headerLabel.AddThemeStyleboxOverride("normal", new StyleBoxFlat());
vbox.AddChild(headerLabel);
var separator = new HSeparator();
vbox.AddChild(separator);
// Scroll container för instanser
var scrollContainer = new ScrollContainer();
scrollContainer.CustomMinimumSize = new Vector2(0, 300);
_instanceList = new VBoxContainer();
scrollContainer.AddChild(_instanceList);
vbox.AddChild(scrollContainer);
// Cancel button
var buttonContainer = new HBoxContainer();
buttonContainer.Alignment = BoxContainer.AlignmentMode.End;
_cancelButton = new Button();
_cancelButton.Text = "Cancel";
_cancelButton.Pressed += OnCancelPressed;
buttonContainer.AddChild(_cancelButton);
vbox.AddChild(buttonContainer);
AddChild(vbox);
PopulateInstanceList();
}
public void Initialize(SaveData saveData)
{
_saveData = saveData;
}
private void PopulateInstanceList()
{
if (_saveData?.Instances == null) return;
// Sortera instances efter save date (nyast först)
var sortedInstances = _saveData.Instances.OrderByDescending(i => i.SaveDate).ToList();
foreach (var instance in sortedInstances)
{
var instanceButton = new Button();
instanceButton.CustomMinimumSize = new Vector2(0, 60);
instanceButton.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
// Skapa text med formatering
var instanceText = $"{instance.DisplayName}\n";
instanceText += $"Game Time: {instance.GetFormattedGameTime()}\n";
instanceText += $"Saved: {instance.SaveDate:yyyy-MM-dd HH:mm:ss}";
instanceButton.Text = instanceText;
instanceButton.Pressed += () => OnInstanceSelected(instance);
_instanceList.AddChild(instanceButton);
}
}
private void OnInstanceSelected(SaveInstance instance)
{
var saveManager = new SaveManager();
if (saveManager.LoadGame(_saveData, instance))
{
GetTree().ChangeSceneToFile("res://scenes/Game.tscn");
}
else
{
GD.PrintErr("Failed to load selected game instance");
// Visa error message till användaren
var errorDialog = new AcceptDialog();
errorDialog.DialogText = "Failed to load the selected save instance.";
errorDialog.Title = "Load Error";
GetTree().Root.AddChild(errorDialog);
errorDialog.PopupCentered();
}
QueueFree();
}
private void OnCancelPressed()
{
QueueFree();
}
public override void _Input(InputEvent @event)
{
if (@event is InputEventKey keyEvent && keyEvent.Pressed)
{
if (keyEvent.Keycode == Key.Escape)
{
OnCancelPressed();
GetViewport().SetInputAsHandled();
}
}
}
}
}

View File

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

View File

@@ -7,14 +7,39 @@ namespace TheGame
{
private VBoxContainer _saveList;
private SaveManager _saveManager;
private Label _titleLabel;
private Button _backButton;
public override void _Ready()
{
// Lägg till i localized_ui gruppen
AddToGroup("localized_ui");
_saveList = GetNode<VBoxContainer>("VBoxContainer/ScrollContainer/SaveList");
_titleLabel = GetNode<Label>("VBoxContainer/TitleLabel");
_backButton = GetNode<Button>("VBoxContainer/BackButton");
_saveManager = new SaveManager();
UpdateLocalization();
PopulateSaveList();
}
public void UpdateLocalization()
{
var loc = LocalizationManager.Instance;
_titleLabel.Text = loc.GetText("load_game_title");
_backButton.Text = loc.GetText("load_game_back");
}
public override void _Notification(int what)
{
if (what == NotificationWMCloseRequest)
{
// I load game menyn kan vi avsluta direkt
GetTree().Quit();
}
}
private void PopulateSaveList()
{
// Rensa tidigare innehåll
@@ -28,7 +53,8 @@ namespace TheGame
if (savedGames.Count == 0)
{
var noSavesLabel = new Label();
noSavesLabel.Text = "No saved games found";
var loc = LocalizationManager.Instance;
noSavesLabel.Text = loc.GetText("load_game_no_saves");
noSavesLabel.HorizontalAlignment = HorizontalAlignment.Center;
_saveList.AddChild(noSavesLabel);
return;
@@ -36,35 +62,69 @@ namespace TheGame
foreach (var saveData in savedGames)
{
var saveButton = new Button();
var gameContainer = new VBoxContainer();
gameContainer.AddThemeConstantOverride("separation", 5);
// Formatera tid för visning
int hours = (int)(saveData.GameTime / 3600);
int minutes = (int)((saveData.GameTime % 3600) / 60);
int seconds = (int)(saveData.GameTime % 60);
string timeText = $"{hours:D2}:{minutes:D2}:{seconds:D2}";
// Header med seed info
var seedLabel = new Label();
seedLabel.Text = $"Game: {saveData.GameSeed.ToString()}";
seedLabel.AddThemeStyleboxOverride("normal", new StyleBoxFlat());
gameContainer.AddChild(seedLabel);
saveButton.Text = $"{saveData.SaveName}\nTime: {timeText}\nSaved: {saveData.SaveDate:yyyy-MM-dd HH:mm:ss}";
// Continue button (senaste instansen)
var latestInstance = saveData.GetLatestInstance();
if (latestInstance != null)
{
var continueButton = new Button();
continueButton.Text = $"Continue: {latestInstance.DisplayName}\nTime: {latestInstance.GetFormattedGameTime()}\nSaved: {latestInstance.SaveDate:yyyy-MM-dd HH:mm}";
continueButton.Pressed += () => LoadGame(saveData, latestInstance);
gameContainer.AddChild(continueButton);
}
// Anslut knapp-händelse
saveButton.Pressed += () => LoadSelectedGame(saveData);
// Load Previous button (om det finns fler instanser)
if (saveData.Instances.Count > 1)
{
var loadPreviousButton = new Button();
loadPreviousButton.Text = $"Load Previous ({saveData.Instances.Count - 1} other saves)";
loadPreviousButton.Pressed += () => ShowInstanceSelection(saveData);
gameContainer.AddChild(loadPreviousButton);
}
_saveList.AddChild(saveButton);
// Separator mellan games
var separator = new HSeparator();
gameContainer.AddChild(separator);
_saveList.AddChild(gameContainer);
}
}
private void LoadSelectedGame(SaveData saveData)
private void LoadGame(SaveData saveData, SaveInstance instance = null)
{
if (_saveManager.LoadGame(saveData))
if (_saveManager.LoadGame(saveData, instance))
{
GetTree().ChangeSceneToFile("res://scenes/Game.tscn");
}
else
{
GD.PrintErr("Failed to load selected game");
// Visa error message
var errorDialog = new AcceptDialog();
errorDialog.DialogText = "Failed to load the selected save.";
errorDialog.Title = "Load Error";
GetTree().Root.AddChild(errorDialog);
errorDialog.PopupCentered();
}
}
private void ShowInstanceSelection(SaveData saveData)
{
var instanceMenu = new InstanceSelectionMenu();
instanceMenu.Initialize(saveData);
GetTree().Root.AddChild(instanceMenu);
instanceMenu.PopupCentered(new Vector2I(500, 400));
}
private void _on_back_button_pressed()
{
GetTree().ChangeSceneToFile("res://scenes/MainMenu.tscn");

View File

@@ -0,0 +1,255 @@
using Godot;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
namespace TheGame
{
public class LocalizationManager
{
private static LocalizationManager _instance;
private Dictionary<string, string> _currentTranslations;
private Dictionary<string, string> _fallbackTranslations;
private string _currentLanguage = "eng";
private readonly string _languagesPath;
public static LocalizationManager Instance
{
get
{
if (_instance == null)
{
_instance = new LocalizationManager();
}
return _instance;
}
}
private LocalizationManager()
{
// För development: använd fil-systemet
// För export: använd Godot's resource system
_languagesPath = "res://resources/languages";
// Skapa engelska som fallback om den inte finns
CreateDefaultEnglishFile();
// Ladda engelska som fallback
_fallbackTranslations = LoadLanguageFile("eng");
// Ladda nuvarande språk
_currentTranslations = _fallbackTranslations;
}
private void CreateDefaultEnglishFile()
{
// Kontrollera om engelsk fil finns i resources
if (!Godot.FileAccess.FileExists("res://resources/languages/eng.json"))
{
var defaultTranslations = new Dictionary<string, string>
{
// Main Menu
{"main_menu_title", "THE GAME"},
{"main_menu_start", "Start Game"},
{"main_menu_load", "Load Game"},
{"main_menu_settings", "Settings"},
{"main_menu_exit", "Exit"},
// Pause Menu
{"pause_menu_title", "PAUSED"},
{"pause_menu_resume", "Resume"},
{"pause_menu_save", "Save Game"},
{"pause_menu_settings", "Settings"},
{"pause_menu_main_menu", "Exit to Main Menu"},
{"pause_menu_desktop", "Exit to Desktop"},
// Load Game Menu
{"load_game_title", "LOAD GAME"},
{"load_game_back", "Back to Main Menu"},
{"load_game_no_saves", "No saved games found"},
{"load_game_playtime", "Playtime"},
{"load_game_saved", "Saved"},
// Save Dialog
{"save_dialog_title", "Save Game"},
{"save_dialog_enter_name", "Enter save name:"},
{"save_dialog_placeholder", "My Save Game"},
// Settings Menu
{"settings_title", "SETTINGS"},
{"settings_general", "General"},
{"settings_video", "Video"},
{"settings_audio", "Audio"},
{"settings_apply", "Apply"},
{"settings_cancel", "Cancel"},
{"settings_back", "Back"},
// General Settings
{"settings_language", "Language"},
// Video Settings
{"settings_fullscreen", "Fullscreen"},
// Audio Settings
{"settings_master_volume", "Master Volume"},
{"settings_background_volume", "Background Volume"},
{"settings_effects_volume", "Effects Volume"},
{"settings_radio_volume", "Radio Volume"},
// Game
{"game_content", "GAME CONTENT HERE\\nPress ESC or click pause button to pause"},
// Common
{"ok", "OK"},
{"cancel", "Cancel"},
{"yes", "Yes"},
{"no", "No"}
};
try
{
string jsonString = JsonSerializer.Serialize(defaultTranslations, new JsonSerializerOptions
{
WriteIndented = true
});
// Försök spara endast i development mode
if (OS.IsDebugBuild())
{
var file = Godot.FileAccess.Open("res://resources/languages/eng.json", Godot.FileAccess.ModeFlags.Write);
if (file != null)
{
file.StoreString(jsonString);
file.Close();
GD.Print("Created default English language file in resources");
}
}
}
catch (Exception ex)
{
GD.PrintErr($"Failed to create English language file: {ex.Message}");
}
}
}
public List<string> GetAvailableLanguages()
{
var languages = new List<string>();
try
{
// Använd DirAccess för att läsa från resources
var dir = DirAccess.Open("res://resources/languages");
if (dir != null)
{
dir.ListDirBegin();
string fileName = dir.GetNext();
while (fileName != "")
{
if (!dir.CurrentIsDir() && fileName.EndsWith(".json"))
{
string languageCode = fileName.GetBaseName();
languages.Add(languageCode);
}
fileName = dir.GetNext();
}
dir.ListDirEnd();
}
}
catch (Exception ex)
{
GD.PrintErr($"Failed to get available languages: {ex.Message}");
}
// Se till att engelska alltid finns
if (!languages.Contains("eng"))
{
languages.Add("eng");
}
GD.Print($"Available languages: {string.Join(", ", languages)}");
return languages;
}
private Dictionary<string, string> LoadLanguageFile(string languageCode)
{
var translations = new Dictionary<string, string>();
string filePath = $"res://resources/languages/{languageCode}.json";
try
{
if (Godot.FileAccess.FileExists(filePath))
{
var file = Godot.FileAccess.Open(filePath, Godot.FileAccess.ModeFlags.Read);
if (file != null)
{
string jsonString = file.GetAsText();
file.Close();
var loadedTranslations = JsonSerializer.Deserialize<Dictionary<string, string>>(jsonString);
if (loadedTranslations != null)
{
translations = loadedTranslations;
GD.Print($"Loaded {translations.Count} translations for language: {languageCode}");
}
}
}
else
{
GD.Print($"Language file not found: {filePath}");
}
}
catch (Exception ex)
{
GD.PrintErr($"Failed to load language file {filePath}: {ex.Message}");
}
return translations;
}
public void SetLanguage(string languageCode)
{
if (_currentLanguage == languageCode)
return;
_currentLanguage = languageCode;
_currentTranslations = LoadLanguageFile(languageCode);
GD.Print($"Language changed to: {languageCode}");
// Skicka signal för att uppdatera alla UI-element
GetTree().CallGroup("localized_ui", "UpdateLocalization");
}
public string GetText(string key)
{
// Försök hämta från nuvarande språk
if (_currentTranslations != null && _currentTranslations.ContainsKey(key))
{
return _currentTranslations[key];
}
// Fallback till engelska
if (_fallbackTranslations != null && _fallbackTranslations.ContainsKey(key))
{
return _fallbackTranslations[key];
}
// Om inte ens engelska finns, returnera nyckeln
GD.PrintErr($"Missing translation for key: {key}");
return key;
}
public string GetCurrentLanguage()
{
return _currentLanguage;
}
private SceneTree GetTree()
{
return Engine.GetMainLoop() as SceneTree;
}
}
}

View File

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

View File

@@ -5,11 +5,37 @@ namespace TheGame
public partial class MainMenu : Control
{
private Button _loadButton;
private Label _titleLabel;
private Button _startButton;
private Button _settingsButton;
private Button _exitButton;
public override void _Ready()
{
// Lägg till i localized_ui gruppen
AddToGroup("localized_ui");
_loadButton = GetNode<Button>("VBoxContainer/LoadButton");
_titleLabel = GetNode<Label>("VBoxContainer/TitleLabel");
_startButton = GetNode<Button>("VBoxContainer/StartButton");
_settingsButton = GetNode<Button>("VBoxContainer/SettingsButton");
_exitButton = GetNode<Button>("VBoxContainer/ExitButton");
// Initiera inställningar och språk
GameSettings.Instance.ApplyAllSettings();
UpdateLoadButtonState();
UpdateLocalization();
}
public void UpdateLocalization()
{
var loc = LocalizationManager.Instance;
_titleLabel.Text = loc.GetText("main_menu_title");
_startButton.Text = loc.GetText("main_menu_start");
_loadButton.Text = loc.GetText("main_menu_load");
_settingsButton.Text = loc.GetText("main_menu_settings");
_exitButton.Text = loc.GetText("main_menu_exit");
}
private void UpdateLoadButtonState()
@@ -22,14 +48,38 @@ namespace TheGame
private void _on_start_button_pressed()
{
// Rensa befintligt game state för att starta nytt spel
GameState.ResetGameState();
GetTree().ChangeSceneToFile("res://scenes/Game.tscn");
}
public override void _Notification(int what)
{
if (what == NotificationWMCloseRequest)
{
// I huvudmenyn kan vi avsluta direkt
GetTree().Quit();
}
}
private void _on_load_button_pressed()
{
GetTree().ChangeSceneToFile("res://scenes/LoadGameMenu.tscn");
}
private void _on_settings_button_pressed()
{
// Skapa settings menu som popup
var settingsScene = GD.Load<PackedScene>("res://scenes/SettingsMenu.tscn");
var settingsMenu = settingsScene.Instantiate<SettingsMenu>();
// Sätt process mode för main menu context
settingsMenu.ProcessMode = ProcessModeEnum.Always;
GetTree().Root.AddChild(settingsMenu);
}
private void _on_exit_button_pressed()
{
GetTree().Quit();

View File

@@ -5,10 +5,39 @@ namespace TheGame
public partial class PauseMenu : Control
{
private Game _gameController;
private Label _titleLabel;
private Button _resumeButton;
private Button _saveButton;
private Button _settingsButton;
private Button _mainMenuButton;
private Button _desktopButton;
public override void _Ready()
{
// Lägg till i localized_ui gruppen
AddToGroup("localized_ui");
_gameController = GetParent<Game>();
_titleLabel = GetNode<Label>("MenuContainer/TitleLabel");
_resumeButton = GetNode<Button>("MenuContainer/ResumeButton");
_saveButton = GetNode<Button>("MenuContainer/SaveButton");
_settingsButton = GetNode<Button>("MenuContainer/SettingsButton");
_mainMenuButton = GetNode<Button>("MenuContainer/MainMenuButton");
_desktopButton = GetNode<Button>("MenuContainer/DesktopButton");
UpdateLocalization();
}
public void UpdateLocalization()
{
var loc = LocalizationManager.Instance;
_titleLabel.Text = loc.GetText("pause_menu_title");
_resumeButton.Text = loc.GetText("pause_menu_resume");
_saveButton.Text = loc.GetText("pause_menu_save");
_settingsButton.Text = loc.GetText("pause_menu_settings");
_mainMenuButton.Text = loc.GetText("pause_menu_main_menu");
_desktopButton.Text = loc.GetText("pause_menu_desktop");
}
private void _on_resume_button_pressed()
@@ -18,23 +47,38 @@ namespace TheGame
private void _on_save_button_pressed()
{
var saveManager = new SaveManager();
float currentTime = _gameController.GetGameTime();
saveManager.SaveGame(currentTime);
// Skapa och visa advanced save dialog
var advancedSaveDialog = new AdvancedSaveDialog();
advancedSaveDialog.ProcessMode = ProcessModeEnum.WhenPaused; // Sätt innan initialize
advancedSaveDialog.Initialize(_gameController);
GetTree().Root.AddChild(advancedSaveDialog);
advancedSaveDialog.PopupCentered(new Vector2I(450, 350));
}
private void _on_settings_button_pressed()
{
// Skapa settings menu som popup
var settingsScene = GD.Load<PackedScene>("res://scenes/SettingsMenu.tscn");
var settingsMenu = settingsScene.Instantiate<SettingsMenu>();
// Visa bekräftelse eller gå tillbaka till spelet
_gameController.TogglePause();
// Sätt process mode så att den fungerar när pausat
settingsMenu.ProcessMode = ProcessModeEnum.WhenPaused;
GetTree().Root.AddChild(settingsMenu);
}
private void _on_main_menu_button_pressed()
{
GetTree().Paused = false;
GetTree().ChangeSceneToFile("res://scenes/MainMenu.tscn");
_gameController.HandleExitRequest(() => {
GetTree().Paused = false;
GameState.ResetGameState();
GetTree().ChangeSceneToFile("res://scenes/MainMenu.tscn");
});
}
private void _on_desktop_button_pressed()
{
GetTree().Quit();
_gameController.HandleExitRequest(() => GetTree().Quit());
}
}
}

101
scripts/SaveDialog.cs Normal file
View File

@@ -0,0 +1,101 @@
using Godot;
namespace TheGame
{
public partial class SaveDialog : AcceptDialog
{
private LineEdit _nameInput;
private Game _gameController;
public override void _Ready()
{
// Sätt process mode så att dialogen fungerar när spelet är pausat
ProcessMode = ProcessModeEnum.WhenPaused;
var loc = LocalizationManager.Instance;
Title = loc.GetText("save_dialog_title");
// Skapa innehåll för dialogen
var vbox = new VBoxContainer();
var label = new Label();
label.Text = loc.GetText("save_dialog_enter_name");
vbox.AddChild(label);
_nameInput = new LineEdit();
_nameInput.PlaceholderText = loc.GetText("save_dialog_placeholder");
_nameInput.Text = $"Save {System.DateTime.Now:yyyy-MM-dd HH:mm}";
vbox.AddChild(_nameInput);
AddChild(vbox);
// Sätt process mode på alla child nodes
SetProcessModeRecursive(this, ProcessModeEnum.WhenPaused);
// Anslut signaler
Confirmed += OnSaveConfirmed;
// Fokusera på text input
_nameInput.GrabFocus();
_nameInput.SelectAll();
}
public void Initialize(Game gameController)
{
_gameController = gameController;
}
private void OnSaveConfirmed()
{
if (_gameController != null)
{
string saveName = _nameInput.Text.Trim();
if (string.IsNullOrEmpty(saveName))
{
saveName = $"Save {System.DateTime.Now:yyyy-MM-dd HH:mm}";
}
var saveManager = new SaveManager();
float currentTime = _gameController.GetGameTime();
saveManager.SaveGame(currentTime, saveName);
// Stäng pausmenyn och återgå till spelet
_gameController.TogglePause();
}
QueueFree();
}
private void SetProcessModeRecursive(Node node, ProcessModeEnum mode)
{
if (node is Control control)
{
control.ProcessMode = mode;
}
foreach (Node child in node.GetChildren())
{
SetProcessModeRecursive(child, mode);
}
}
public override void _Input(InputEvent @event)
{
// Se till att input fungerar när spelet är pausat
if (@event is InputEventKey keyEvent && keyEvent.Pressed)
{
if (keyEvent.Keycode == Key.Enter || keyEvent.Keycode == Key.KpEnter)
{
OnSaveConfirmed();
GetViewport().SetInputAsHandled();
}
else if (keyEvent.Keycode == Key.Escape)
{
Hide();
QueueFree();
GetViewport().SetInputAsHandled();
}
}
}
}
}

View File

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

57
scripts/SaveInstance.cs Normal file
View File

@@ -0,0 +1,57 @@
using System;
namespace TheGame
{
public class SaveInstance
{
public string InstanceId { get; set; }
public float GameTime { get; set; }
public DateTime SaveDate { get; set; }
public string DisplayName { get; set; }
public bool IsAutoSave { get; set; } = false;
public SaveInstance()
{
InstanceId = Guid.NewGuid().ToString("N")[..8]; // 8-tecken ID
SaveDate = DateTime.Now;
DisplayName = $"Save {SaveDate:HH:mm:ss}";
}
public SaveInstance(float gameTime, string displayName = "", bool isAutoSave = false)
{
InstanceId = Guid.NewGuid().ToString("N")[..8];
GameTime = gameTime;
SaveDate = DateTime.Now;
IsAutoSave = isAutoSave;
if (string.IsNullOrEmpty(displayName))
{
if (isAutoSave)
{
DisplayName = $"Auto-save {SaveDate:HH:mm:ss}";
}
else
{
DisplayName = $"Save {SaveDate:HH:mm:ss}";
}
}
else
{
DisplayName = displayName;
}
}
public string GetFormattedGameTime()
{
int hours = (int)(GameTime / 3600);
int minutes = (int)((GameTime % 3600) / 60);
int seconds = (int)(GameTime % 60);
return $"{hours:D2}:{minutes:D2}:{seconds:D2}";
}
public override string ToString()
{
return $"{DisplayName} - {GetFormattedGameTime()} ({SaveDate:yyyy-MM-dd HH:mm:ss})";
}
}
}

View File

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

View File

@@ -2,15 +2,43 @@ using Godot;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace TheGame
{
public class SaveData
{
public GameSeed GameSeed { get; set; }
public List<SaveInstance> Instances { get; set; } = new List<SaveInstance>();
// Backward compatibility
public float GameTime { get; set; }
public DateTime SaveDate { get; set; }
public string SaveName { get; set; }
public string DisplayName { get; set; } = "";
public SaveData()
{
GameSeed = new GameSeed();
}
public SaveData(GameSeed gameSeed)
{
GameSeed = gameSeed;
}
public SaveInstance GetLatestInstance()
{
if (Instances.Count == 0) return null;
return Instances.OrderByDescending(i => i.SaveDate).First();
}
public SaveInstance GetInstanceWithHighestGameTime()
{
if (Instances.Count == 0) return null;
return Instances.OrderByDescending(i => i.GameTime).First();
}
}
public class SaveManager
@@ -19,25 +47,100 @@ namespace TheGame
public SaveManager()
{
_saveDirectory = Path.Combine(OS.GetExecutablePath().GetBaseDir(), "saves");
// Använd user data directory istället för executable directory
string userDataDir = OS.GetUserDataDir();
_saveDirectory = Path.Combine(userDataDir, "saves");
// Skapa saves mapp om den inte finns
if (!Directory.Exists(_saveDirectory))
{
Directory.CreateDirectory(_saveDirectory);
GD.Print($"Created saves directory: {_saveDirectory}");
}
else
{
GD.Print($"Using saves directory: {_saveDirectory}");
}
}
public void SaveGame(float gameTime)
public bool SaveGame(float gameTime, string displayName = "", GameSeed gameSeed = null, bool overwriteLatest = true)
{
var saveData = new SaveData
try
{
GameTime = gameTime,
SaveDate = DateTime.Now,
SaveName = $"Save_{DateTime.Now:yyyyMMdd_HHmmss}"
};
// Om ingen seed angiven, använd nuvarande eller skapa ny
if (gameSeed == null)
{
gameSeed = GameState.CurrentGameSeed ?? new GameSeed();
GameState.CurrentGameSeed = gameSeed;
}
// Sök efter befintlig SaveData för denna seed
var existingSaveData = FindSaveDataBySeed(gameSeed);
if (existingSaveData == null)
{
// Skapa ny SaveData för denna seed
existingSaveData = new SaveData(gameSeed);
}
// Skapa ny instans
var newInstance = new SaveInstance(gameTime, displayName);
if (overwriteLatest && existingSaveData.Instances.Count > 0)
{
// Skriv över den senaste instansen
var latestInstance = existingSaveData.GetLatestInstance();
existingSaveData.Instances.Remove(latestInstance);
}
existingSaveData.Instances.Add(newInstance);
// Spara till fil
bool saveSuccess = SaveSeedToFile(existingSaveData);
if (saveSuccess)
{
GD.Print($"Game saved - Seed: {gameSeed.SeedValue}, Instance: {newInstance.DisplayName}");
return true;
}
else
{
GD.PrintErr($"Failed to save game - Seed: {gameSeed.SeedValue}");
return false;
}
}
catch (Exception ex)
{
GD.PrintErr($"Error saving game: {ex.Message}");
return false;
}
}
public SaveData FindSaveDataBySeed(GameSeed gameSeed)
{
string fileName = $"seed_{gameSeed.SeedValue}.json";
string filePath = Path.Combine(_saveDirectory, fileName);
string fileName = $"{saveData.SaveName}.json";
if (File.Exists(filePath))
{
try
{
string jsonString = File.ReadAllText(filePath);
var saveData = JsonSerializer.Deserialize<SaveData>(jsonString);
return saveData;
}
catch (Exception ex)
{
GD.PrintErr($"Failed to load seed file {filePath}: {ex.Message}");
}
}
return null;
}
private bool SaveSeedToFile(SaveData saveData)
{
string fileName = $"seed_{saveData.GameSeed.SeedValue}.json";
string filePath = Path.Combine(_saveDirectory, fileName);
try
@@ -47,11 +150,13 @@ namespace TheGame
WriteIndented = true
});
File.WriteAllText(filePath, jsonString);
GD.Print($"Game saved successfully: {fileName}");
GD.Print($"Seed saved to: {filePath}");
return true;
}
catch (Exception ex)
{
GD.PrintErr($"Failed to save game: {ex.Message}");
GD.PrintErr($"Failed to save seed file: {ex.Message}");
return false;
}
}
@@ -59,22 +164,50 @@ namespace TheGame
{
var savedGames = new List<SaveData>();
GD.Print($"Looking for saves in: {_saveDirectory}");
if (!Directory.Exists(_saveDirectory))
{
GD.Print("Save directory does not exist");
return savedGames;
}
try
{
var saveFiles = Directory.GetFiles(_saveDirectory, "*.json");
// Ladda både nya seed-filer och gamla format för backward compatibility
var allFiles = Directory.GetFiles(_saveDirectory, "*.json");
GD.Print($"Found {allFiles.Length} save files");
foreach (string filePath in saveFiles)
foreach (string filePath in allFiles)
{
GD.Print($"Processing save file: {filePath}");
try
{
string jsonString = File.ReadAllText(filePath);
var saveData = JsonSerializer.Deserialize<SaveData>(jsonString);
if (saveData != null)
if (Path.GetFileName(filePath).StartsWith("seed_"))
{
savedGames.Add(saveData);
// Ny seed-baserad fil
var saveData = JsonSerializer.Deserialize<SaveData>(jsonString);
if (saveData != null && saveData.Instances.Count > 0)
{
savedGames.Add(saveData);
GD.Print($"Successfully loaded seed: {saveData.GameSeed.SeedValue} with {saveData.Instances.Count} instances");
}
}
else
{
// Gammal fil - konvertera till nytt format
var oldSaveData = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString);
if (oldSaveData != null)
{
var convertedSave = ConvertOldSaveFormat(oldSaveData);
if (convertedSave != null)
{
savedGames.Add(convertedSave);
GD.Print($"Converted old save format");
}
}
}
}
catch (Exception ex)
@@ -83,8 +216,9 @@ namespace TheGame
}
}
// Sortera efter datum (nyast först)
savedGames.Sort((a, b) => b.SaveDate.CompareTo(a.SaveDate));
// Sortera efter senaste aktivitet
savedGames = savedGames.OrderByDescending(s => s.GetLatestInstance()?.SaveDate ?? DateTime.MinValue).ToList();
GD.Print($"Total loaded saves: {savedGames.Count}");
}
catch (Exception ex)
{
@@ -94,11 +228,56 @@ namespace TheGame
return savedGames;
}
public bool LoadGame(SaveData saveData)
private SaveData ConvertOldSaveFormat(Dictionary<string, object> oldData)
{
try
{
GameState.LoadedGameTime = saveData.GameTime;
// Extrahera värden från gammal format
var gameTime = Convert.ToSingle(oldData.GetValueOrDefault("GameTime", 0f));
var saveDateStr = oldData.GetValueOrDefault("SaveDate", "")?.ToString();
var displayName = oldData.GetValueOrDefault("DisplayName", "")?.ToString();
if (DateTime.TryParse(saveDateStr, out DateTime saveDate))
{
var gameSeed = new GameSeed($"OLD_{DateTime.Now.Ticks % 100000000:X}", "Converted Save");
var saveData = new SaveData(gameSeed);
var instance = new SaveInstance(gameTime, displayName ?? "Converted Save");
instance.SaveDate = saveDate;
saveData.Instances.Add(instance);
return saveData;
}
}
catch (Exception ex)
{
GD.PrintErr($"Failed to convert old save format: {ex.Message}");
}
return null;
}
public bool LoadGame(SaveData saveData, SaveInstance instance = null)
{
try
{
// Om ingen specifik instans angiven, ta den med högst speltid
if (instance == null)
{
instance = saveData.GetInstanceWithHighestGameTime();
}
if (instance == null)
{
GD.PrintErr("No valid instance found to load");
return false;
}
GameState.LoadedGameTime = instance.GameTime;
GameState.CurrentGameSeed = saveData.GameSeed;
GameState.LoadedInstance = instance;
GD.Print($"Loaded game - Seed: {saveData.GameSeed.SeedValue}, Instance: {instance.DisplayName}, Time: {instance.GetFormattedGameTime()}");
return true;
}
catch (Exception ex)

288
scripts/SettingsMenu.cs Normal file
View File

@@ -0,0 +1,288 @@
using Godot;
using System.Collections.Generic;
namespace TheGame
{
public partial class SettingsMenu : Control
{
// UI References
private Label _titleLabel;
private TabContainer _tabContainer;
// General Tab
private Label _languageLabel;
private OptionButton _languageOption;
// Video Tab
private Label _fullscreenLabel;
private CheckBox _fullscreenCheck;
// Audio Tab
private Label _masterLabel;
private HSlider _masterSlider;
private Label _backgroundLabel;
private HSlider _backgroundSlider;
private Label _effectsLabel;
private HSlider _effectsSlider;
private Label _radioLabel;
private HSlider _radioSlider;
// Buttons
private Button _applyButton;
private Button _cancelButton;
private Button _backButton;
// Settings
private GameSettings _settings;
private GameSettings _originalSettings;
public override void _Ready()
{
// Lägg till i localized_ui gruppen för språkuppdateringar
AddToGroup("localized_ui");
// Sätt process mode baserat på om spelet är pausat eller inte
if (GetTree().Paused)
{
ProcessMode = ProcessModeEnum.WhenPaused;
}
else
{
ProcessMode = ProcessModeEnum.Always;
}
// Hämta UI referenser
GetUIReferences();
// Ladda nuvarande inställningar
_settings = GameSettings.Instance;
_originalSettings = new GameSettings
{
Fullscreen = _settings.Fullscreen,
MasterVolume = _settings.MasterVolume,
BackgroundVolume = _settings.BackgroundVolume,
EffectsVolume = _settings.EffectsVolume,
RadioVolume = _settings.RadioVolume,
Language = _settings.Language
};
// Sätt process mode på alla UI element så de fungerar när pausat
SetProcessModeRecursive(this, ProcessModeEnum.WhenPaused);
// Initiera UI
InitializeLanguageOptions();
LoadCurrentSettings();
UpdateLocalization();
}
public override void _Notification(int what)
{
if (what == NotificationWMCloseRequest)
{
// Settings menyn är en popup, så vi stänger bara den själv
_on_cancel_button_pressed();
}
}
private void SetProcessModeRecursive(Node node, ProcessModeEnum mode)
{
if (node is Control control)
{
control.ProcessMode = mode;
}
foreach (Node child in node.GetChildren())
{
SetProcessModeRecursive(child, mode);
}
}
private void GetUIReferences()
{
_titleLabel = GetNode<Label>("MainContainer/TitleLabel");
_tabContainer = GetNode<TabContainer>("MainContainer/TabContainer");
// General
_languageLabel = GetNode<Label>("MainContainer/TabContainer/General/GeneralVBox/LanguageContainer/LanguageLabel");
_languageOption = GetNode<OptionButton>("MainContainer/TabContainer/General/GeneralVBox/LanguageContainer/LanguageOption");
// Video
_fullscreenLabel = GetNode<Label>("MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer/FullscreenLabel");
_fullscreenCheck = GetNode<CheckBox>("MainContainer/TabContainer/Video/VideoVBox/FullscreenContainer/FullscreenCheck");
// Audio
_masterLabel = GetNode<Label>("MainContainer/TabContainer/Audio/AudioVBox/MasterContainer/MasterLabel");
_masterSlider = GetNode<HSlider>("MainContainer/TabContainer/Audio/AudioVBox/MasterContainer/MasterSlider");
_backgroundLabel = GetNode<Label>("MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer/BackgroundLabel");
_backgroundSlider = GetNode<HSlider>("MainContainer/TabContainer/Audio/AudioVBox/BackgroundContainer/BackgroundSlider");
_effectsLabel = GetNode<Label>("MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer/EffectsLabel");
_effectsSlider = GetNode<HSlider>("MainContainer/TabContainer/Audio/AudioVBox/EffectsContainer/EffectsSlider");
_radioLabel = GetNode<Label>("MainContainer/TabContainer/Audio/AudioVBox/RadioContainer/RadioLabel");
_radioSlider = GetNode<HSlider>("MainContainer/TabContainer/Audio/AudioVBox/RadioContainer/RadioSlider");
// Buttons
_applyButton = GetNode<Button>("MainContainer/ButtonContainer/ApplyButton");
_cancelButton = GetNode<Button>("MainContainer/ButtonContainer/CancelButton");
_backButton = GetNode<Button>("MainContainer/ButtonContainer/BackButton");
}
private void InitializeLanguageOptions()
{
_languageOption.Clear();
var availableLanguages = LocalizationManager.Instance.GetAvailableLanguages();
var languageNames = new Dictionary<string, string>
{
{"eng", "English"},
{"sve", "Svenska"}
};
foreach (string lang in availableLanguages)
{
string displayName = languageNames.ContainsKey(lang) ? languageNames[lang] : lang.ToUpper();
_languageOption.AddItem(displayName);
_languageOption.SetItemMetadata(_languageOption.GetItemCount() - 1, lang);
}
}
private void LoadCurrentSettings()
{
// Language
for (int i = 0; i < _languageOption.GetItemCount(); i++)
{
if (_languageOption.GetItemMetadata(i).AsString() == _settings.Language)
{
_languageOption.Selected = i;
break;
}
}
// Video
_fullscreenCheck.ButtonPressed = _settings.Fullscreen;
// Audio
_masterSlider.Value = _settings.MasterVolume;
_backgroundSlider.Value = _settings.BackgroundVolume;
_effectsSlider.Value = _settings.EffectsVolume;
_radioSlider.Value = _settings.RadioVolume;
UpdateVolumeLabels();
}
private void UpdateVolumeLabels()
{
var loc = LocalizationManager.Instance;
_masterLabel.Text = $"{loc.GetText("settings_master_volume")}: {(int)_masterSlider.Value}";
_backgroundLabel.Text = $"{loc.GetText("settings_background_volume")}: {(int)_backgroundSlider.Value}";
_effectsLabel.Text = $"{loc.GetText("settings_effects_volume")}: {(int)_effectsSlider.Value}";
_radioLabel.Text = $"{loc.GetText("settings_radio_volume")}: {(int)_radioSlider.Value}";
}
public void UpdateLocalization()
{
var loc = LocalizationManager.Instance;
_titleLabel.Text = loc.GetText("settings_title");
// Tab names
_tabContainer.SetTabTitle(0, loc.GetText("settings_general"));
_tabContainer.SetTabTitle(1, loc.GetText("settings_video"));
_tabContainer.SetTabTitle(2, loc.GetText("settings_audio"));
// General
_languageLabel.Text = loc.GetText("settings_language") + ":";
// Video
_fullscreenLabel.Text = loc.GetText("settings_fullscreen") + ":";
// Audio
UpdateVolumeLabels();
// Buttons
_applyButton.Text = loc.GetText("settings_apply");
_cancelButton.Text = loc.GetText("settings_cancel");
_backButton.Text = loc.GetText("settings_back");
}
// Signal handlers
private void _on_language_option_item_selected(int index)
{
string selectedLanguage = _languageOption.GetItemMetadata(index).AsString();
_settings.Language = selectedLanguage;
// Tillämpa språkändring direkt
LocalizationManager.Instance.SetLanguage(selectedLanguage);
}
private void _on_fullscreen_check_toggled(bool buttonPressed)
{
_settings.Fullscreen = buttonPressed;
}
private void _on_master_slider_value_changed(double value)
{
_settings.MasterVolume = (int)value;
UpdateVolumeLabels();
}
private void _on_background_slider_value_changed(double value)
{
_settings.BackgroundVolume = (int)value;
UpdateVolumeLabels();
}
private void _on_effects_slider_value_changed(double value)
{
_settings.EffectsVolume = (int)value;
UpdateVolumeLabels();
}
private void _on_radio_slider_value_changed(double value)
{
_settings.RadioVolume = (int)value;
UpdateVolumeLabels();
}
private void _on_apply_button_pressed()
{
_settings.ApplyAllSettings();
GoBack();
}
private void _on_cancel_button_pressed()
{
// Återställ inställningar
_settings.Fullscreen = _originalSettings.Fullscreen;
_settings.MasterVolume = _originalSettings.MasterVolume;
_settings.BackgroundVolume = _originalSettings.BackgroundVolume;
_settings.EffectsVolume = _originalSettings.EffectsVolume;
_settings.RadioVolume = _originalSettings.RadioVolume;
_settings.Language = _originalSettings.Language;
// Återställ språk
LocalizationManager.Instance.SetLanguage(_originalSettings.Language);
GoBack();
}
private void _on_back_button_pressed()
{
_on_apply_button_pressed(); // Spara ändringar när man går tillbaka
}
private void GoBack()
{
// Kontrollera varifrån vi kom
if (GetTree().Paused)
{
// Vi kom från pausmenyn - ta bara bort settings window
QueueFree();
}
else
{
// Vi kom från huvudmenyn
GetTree().ChangeSceneToFile("res://scenes/MainMenu.tscn");
}
}
}
}

View File

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