refactor: standardization round 2

- ConsoleManager: lazy GameManager/AchievementManager via Instance (fixes NullRef on console commands)
- AchievementManager, GhostManager: add static Instance property
- GhostEventHandler: use GhostManager.Instance, add _ExitTree unsubscription
- SpeedRunManager: remove unused IsVisible guard; TimeUpdated now emits when running
- SpeedRunHud: use SpeedRunManager.Instance, remove dead IsVisible binding
- SaveDataDto: moved to scripts/State/SaveDataDto.cs
- GameManager.AddCoins: XML doc clarifying permanent vs session coins
This commit is contained in:
2026-03-19 01:50:20 +01:00
parent 321905e68e
commit cfef49fbc7
10 changed files with 67 additions and 51 deletions

View File

@@ -9,6 +9,8 @@ namespace Mr.BrickAdventures.Autoloads;
/// </summary> /// </summary>
public partial class AchievementManager : Node public partial class AchievementManager : Node
{ {
public static AchievementManager Instance { get; private set; }
[Export] private string AchievementsFolderPath = "res://achievements/"; [Export] private string AchievementsFolderPath = "res://achievements/";
[Export] private PackedScene AchievementPopupScene { get; set; } [Export] private PackedScene AchievementPopupScene { get; set; }
@@ -16,9 +18,15 @@ public partial class AchievementManager : Node
public override void _Ready() public override void _Ready()
{ {
Instance = this;
LoadAchievementsFromFolder(); LoadAchievementsFromFolder();
} }
public override void _ExitTree()
{
if (Instance == this) Instance = null;
}
private void LoadAchievementsFromFolder() private void LoadAchievementsFromFolder()
{ {
using var dir = DirAccess.Open(AchievementsFolderPath); using var dir = DirAccess.Open(AchievementsFolderPath);

View File

@@ -6,41 +6,39 @@ namespace Mr.BrickAdventures.Autoloads;
public partial class ConsoleManager : Node public partial class ConsoleManager : Node
{ {
private GameManager _gameManager; private GameManager GameManager => GameManager.Instance;
private AchievementManager AchievementManager => AchievementManager.Instance;
private SkillManager _skillManager; private SkillManager _skillManager;
private SkillUnlockerComponent _skillUnlockerComponent; private SkillUnlockerComponent _skillUnlockerComponent;
private AchievementManager _achievementManager;
public override void _Ready() public override void _Ready()
{ {
_gameManager = GameManager.Instance;
_achievementManager = GetNode<AchievementManager>(Constants.AchievementManagerPath);
_skillManager = SkillManager.Instance; _skillManager = SkillManager.Instance;
} }
private void AddCoinsCommand(int amount) private void AddCoinsCommand(int amount)
{ {
_gameManager.AddCoins(amount); GameManager.AddCoins(amount);
} }
private void SetCoinsCommand(int amount) private void SetCoinsCommand(int amount)
{ {
_gameManager.SetCoins(amount); GameManager.SetCoins(amount);
} }
private void SetLivesCommand(int amount) private void SetLivesCommand(int amount)
{ {
_gameManager.SetLives(amount); GameManager.SetLives(amount);
} }
private void AddLivesCommand(int amount) private void AddLivesCommand(int amount)
{ {
_gameManager.AddLives(amount); GameManager.AddLives(amount);
} }
private void SetHealthCommand(float amount) private void SetHealthCommand(float amount)
{ {
var playerHealthComponent = _gameManager.Player.GetNode<HealthComponent>("HealthComponent"); var playerHealthComponent = GameManager.Player.GetNode<HealthComponent>("HealthComponent");
if (playerHealthComponent != null) if (playerHealthComponent != null)
{ {
playerHealthComponent.Health = amount; playerHealthComponent.Health = amount;
@@ -49,7 +47,7 @@ public partial class ConsoleManager : Node
private void ResetSessionCommand() private void ResetSessionCommand()
{ {
_gameManager.ResetCurrentSessionState(); GameManager.ResetCurrentSessionState();
} }
private void UnlockSkillCommand(string skillName) private void UnlockSkillCommand(string skillName)
@@ -62,14 +60,14 @@ public partial class ConsoleManager : Node
return; return;
} }
_gameManager.UnlockSkill(skill); GameManager.UnlockSkill(skill);
_skillManager.ActivateSkill(skill); _skillManager.ActivateSkill(skill);
_skillUnlockerComponent.EmitSignal(SkillUnlockerComponent.SignalName.SkillUnlocked, skill); _skillUnlockerComponent.EmitSignal(SkillUnlockerComponent.SignalName.SkillUnlocked, skill);
} }
private bool GetSkillManagement() private bool GetSkillManagement()
{ {
var player = _gameManager.Player; var player = GameManager.Player;
if (player == null || !IsInstanceValid(player)) if (player == null || !IsInstanceValid(player))
{ {
return false; return false;
@@ -100,7 +98,7 @@ public partial class ConsoleManager : Node
return; return;
} }
_gameManager.RemoveSkill(skill.Name); GameManager.RemoveSkill(skill.Name);
_skillManager.DeactivateSkill(skill); _skillManager.DeactivateSkill(skill);
} }
@@ -110,24 +108,24 @@ public partial class ConsoleManager : Node
foreach (var skill in _skillManager.AvailableSkills) foreach (var skill in _skillManager.AvailableSkills)
{ {
_gameManager.RemoveSkill(skill.Name); GameManager.RemoveSkill(skill.Name);
_skillManager.DeactivateSkill(skill); _skillManager.DeactivateSkill(skill);
} }
} }
private void GoToNextLevelCommand() private void GoToNextLevelCommand()
{ {
_gameManager.OnLevelComplete(); GameManager.OnLevelComplete();
} }
private void UnlockAchievementCommand(string achievementId) private void UnlockAchievementCommand(string achievementId)
{ {
_achievementManager.UnlockAchievement(achievementId); AchievementManager.UnlockAchievement(achievementId);
} }
private void ResetAchievementCommand(string achievementId) private void ResetAchievementCommand(string achievementId)
{ {
_achievementManager.LockAchievement(achievementId); AchievementManager.LockAchievement(achievementId);
} }
} }

View File

@@ -52,6 +52,12 @@ public partial class GameManager : Node
#region Coin Operations #region Coin Operations
/// <summary>
/// Adds coins permanently to the player's saved total (Store.Player.Coins).
/// Use this for out-of-gameplay grants (e.g. console commands, rewards).
/// During active gameplay, coins collected in a level go through
/// <see cref="GameStateStore.AddSessionCoins"/> and are only committed on level completion.
/// </summary>
public void AddCoins(int amount) public void AddCoins(int amount)
{ {
if (Store != null) if (Store != null)

View File

@@ -7,8 +7,13 @@ namespace Mr.BrickAdventures.Autoloads;
public partial class GhostManager : Node public partial class GhostManager : Node
{ {
public static GhostManager Instance { get; private set; }
[Export] private PackedScene GhostPlayerScene { get; set; } [Export] private PackedScene GhostPlayerScene { get; set; }
public override void _Ready() => Instance = this;
public override void _ExitTree() { if (Instance == this) Instance = null; }
public bool IsRecording { get; private set; } = false; public bool IsRecording { get; private set; } = false;
public bool IsPlaybackEnabled { get; private set; } = true; public bool IsPlaybackEnabled { get; private set; } = true;

View File

@@ -174,19 +174,3 @@ public partial class SaveSystem : Node
} }
} }
} }
/// <summary>
/// Serializable DTO for save data - no Godot types.
/// </summary>
public class SaveDataDto
{
public int Version { get; set; }
public int Coins { get; set; }
public int Lives { get; set; }
public int CurrentLevel { get; set; }
public List<int> CompletedLevels { get; set; }
public List<int> UnlockedLevels { get; set; }
public List<string> UnlockedSkillNames { get; set; }
public List<string> UnlockedAchievements { get; set; }
public Dictionary<string, int> Statistics { get; set; }
}

View File

@@ -11,7 +11,6 @@ public partial class SpeedRunManager : Node
public override void _ExitTree() { if (Instance == this) Instance = null; } public override void _ExitTree() { if (Instance == this) Instance = null; }
public bool IsRunning { get; private set; } = false; public bool IsRunning { get; private set; } = false;
public bool IsVisible { get; private set; } = false;
private double _startTime; private double _startTime;
private double _levelStartTime; private double _levelStartTime;
@@ -21,7 +20,7 @@ public partial class SpeedRunManager : Node
public override void _Process(double delta) public override void _Process(double delta)
{ {
if (!IsRunning || !IsVisible) return; if (!IsRunning) return;
EmitSignalTimeUpdated(GetCurrentTotalTime(), GetCurrentLevelTime()); EmitSignalTimeUpdated(GetCurrentTotalTime(), GetCurrentLevelTime());
} }

View File

@@ -7,25 +7,27 @@ namespace Mr.BrickAdventures.scripts.Events;
[GlobalClass] [GlobalClass]
public partial class GhostEventHandler : Node public partial class GhostEventHandler : Node
{ {
private GhostManager _ghostManager;
public override void _Ready() public override void _Ready()
{ {
_ghostManager = GetNode<GhostManager>(Constants.GhostManagerPath);
EventBus.Instance.LevelStarted += OnLevelStarted; EventBus.Instance.LevelStarted += OnLevelStarted;
EventBus.Instance.LevelCompleted += OnLevelCompleted; EventBus.Instance.LevelCompleted += OnLevelCompleted;
} }
public override void _ExitTree()
{
EventBus.Instance.LevelStarted -= OnLevelStarted;
EventBus.Instance.LevelCompleted -= OnLevelCompleted;
}
private void OnLevelStarted(int levelIndex, Node currentScene) private void OnLevelStarted(int levelIndex, Node currentScene)
{ {
GD.Print($"GhostEventHandler: Level {levelIndex} started."); GD.Print($"GhostEventHandler: Level {levelIndex} started.");
_ghostManager.StartRecording(levelIndex); GhostManager.Instance.StartRecording(levelIndex);
_ghostManager.SpawnGhostPlayer(levelIndex, currentScene); GhostManager.Instance.SpawnGhostPlayer(levelIndex, currentScene);
} }
private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime) private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime)
{ {
_ghostManager.StopRecording(true, completionTime); GhostManager.Instance.StopRecording(true, completionTime);
} }
} }

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace Mr.BrickAdventures.scripts.State;
/// <summary>
/// Serializable DTO for save data - no Godot types.
/// </summary>
public class SaveDataDto
{
public int Version { get; set; }
public int Coins { get; set; }
public int Lives { get; set; }
public int CurrentLevel { get; set; }
public List<int> CompletedLevels { get; set; }
public List<int> UnlockedLevels { get; set; }
public List<string> UnlockedSkillNames { get; set; }
public List<string> UnlockedAchievements { get; set; }
public Dictionary<string, int> Statistics { get; set; }
}

View File

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

View File

@@ -9,15 +9,9 @@ public partial class SpeedRunHud : Control
{ {
[Export] private Label _timerLabel; [Export] private Label _timerLabel;
private SpeedRunManager _speedRunManager;
public override void _Ready() public override void _Ready()
{ {
_speedRunManager = GetNode<SpeedRunManager>(Constants.SpeedRunManagerPath); SpeedRunManager.Instance.TimeUpdated += OnTimerUpdated;
_speedRunManager.TimeUpdated += OnTimerUpdated;
Visible = _speedRunManager.IsVisible;
} }
private void OnTimerUpdated(double totalTime, double levelTime) private void OnTimerUpdated(double totalTime, double levelTime)