Refactor: reorganize scripts with domain-driven design structure

Move all C# scripts from flat structure to organized folders:
- Core/ for game logic (Game, GameState, GameSettings)
- UI/Menus/ and UI/Dialogs/ for user interface
- Systems/ for reusable systems (Save, Localization)
- Data/ for data models and configuration
- Added framework for future: Gameplay/, Story/, Modding/

Update all .tscn scene files to reference new script paths.
Fix timing issue in AdvancedSaveDialog focus handling.
This commit is contained in:
Björn Blomberg
2025-10-17 14:50:40 +02:00
parent 74062a37d6
commit ed4ce28921
41 changed files with 173 additions and 8 deletions

143
scripts/Core/Game.cs Normal file
View File

@@ -0,0 +1,143 @@
using Godot;
namespace TheGame
{
public partial class Game : Control
{
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");
// 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)
{
if (!_isPaused)
{
_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();
}
}
public override void _Input(InputEvent @event)
{
if (@event.IsActionPressed("ui_cancel")) // ESC key
{
TogglePause();
}
}
private void UpdateTimerDisplay()
{
int hours = (int)(_gameTime / 3600);
int minutes = (int)((_gameTime % 3600) / 60);
int seconds = (int)(_gameTime % 60);
_timerLabel.Text = $"{hours:D2}:{minutes:D2}:{seconds:D2}";
}
public void TogglePause()
{
_isPaused = !_isPaused;
_pauseMenu.Visible = _isPaused;
if (_isPaused)
{
GetTree().Paused = true;
}
else
{
GetTree().Paused = false;
}
}
private void _on_pause_button_pressed()
{
TogglePause();
}
public float GetGameTime()
{
return _gameTime;
}
}
}

1
scripts/Core/Game.cs.uid Normal file
View File

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

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

37
scripts/Core/GameState.cs Normal file
View File

@@ -0,0 +1,37 @@
using Godot;
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 @@
uid://bj2vys8c8nvvg