diff --git a/Framework/Autoloads/AudioManager.cs b/Framework/Autoloads/AudioManager.cs index 592cfae0..ceef2041 100644 --- a/Framework/Autoloads/AudioManager.cs +++ b/Framework/Autoloads/AudioManager.cs @@ -1,115 +1,144 @@ using Godot; -using GodotUtils.UI; +using GodotUtils; using System; -using System.Collections.Generic; -using System.Diagnostics; -namespace GodotUtils; +namespace __TEMPLATE__; public class AudioManager : IDisposable { - private const float MinDefaultRandomPitch = 0.8f; - private const float MaxDefaultRandomPitch = 1.2f; - private const float RandomPitchThreshold = 0.1f; - private const int MutedVolume = -80; - private const int MutedVolumeNormalized = -40; + private const float MinDefaultRandomPitch = 0.8f; // Default minimum pitch value for SFX. + private const float MaxDefaultRandomPitch = 1.2f; // Default maximum pitch value for SFX. + private const float RandomPitchThreshold = 0.1f; // Minimum difference in pitch between repeated sounds. + private const int MutedVolume = -80; // dB value representing mute. + private const int MutedVolumeNormalized = -40; // Normalized muted volume for volume mapping. - private static AudioManager _instance; private AudioStreamPlayer _musicPlayer; private ResourceOptions _options; private Autoloads _autoloads; private float _lastPitch; - private List _activeSfxPlayers = []; + private GodotNodePool _sfxPool; + /// + /// Initializes the AudioManager by attaching a music player to the given autoload node. + /// public AudioManager(Autoloads autoloads) { - if (_instance != null) - throw new InvalidOperationException($"{nameof(AudioManager)} was initialized already"); - - _instance = this; _autoloads = autoloads; - _options = OptionsManager.GetOptions(); + _options = Game.Options.GetOptions(); _musicPlayer = new AudioStreamPlayer(); autoloads.AddChild(_musicPlayer); + + _sfxPool = new GodotNodePool(autoloads, + () => new AudioStreamPlayer2D()); } + /// + /// Frees all managed players and clears references for cleanup. + /// public void Dispose() { _musicPlayer.QueueFree(); - _activeSfxPlayers.Clear(); - - _instance = null; + _sfxPool.Clear(); } - public static void PlayMusic(AudioStream song, bool instant = true, double fadeOut = 1.5, double fadeIn = 0.5) + /// + /// Plays a music track, instantly or with optional fade between tracks. Music volume is in config scale (0-100). + /// + public void PlayMusic(AudioStream song, bool instant = true, double fadeOut = 1.5, double fadeIn = 0.5) { - if (!instant && _instance._musicPlayer.Playing) + if (!instant && _musicPlayer.Playing) { // Slowly transition to the new song - PlayAudioCrossfade(_instance._musicPlayer, song, _instance._options.MusicVolume, fadeOut, fadeIn); + PlayAudioCrossfade(_musicPlayer, song, _options.MusicVolume, fadeOut, fadeIn); } else { // Instantly switch to the new song - PlayAudio(_instance._musicPlayer, song, _instance._options.MusicVolume); + PlayAudio(_musicPlayer, song, _options.MusicVolume); } } /// - /// Plays a at . + /// Plays a sound effect at the specified global position with randomized pitch to reduce repetition. Volume is normalized (0-100). /// - /// - /// - public static void PlaySFX(AudioStream sound, Vector2 position, float minPitch = MinDefaultRandomPitch, float maxPitch = MaxDefaultRandomPitch) + public void PlaySFX(AudioStream sound, Vector2 position, float minPitch = MinDefaultRandomPitch, float maxPitch = MaxDefaultRandomPitch) { - AudioStreamPlayer2D sfxPlayer = new() - { - Stream = sound, - VolumeDb = NormalizeConfigVolume(_instance._options.SFXVolume), - PitchScale = GetRandomPitch(minPitch, maxPitch) - }; - - sfxPlayer.Finished += () => - { - sfxPlayer.QueueFree(); - _instance._activeSfxPlayers.Remove(sfxPlayer); - }; - - _instance._autoloads.AddChild(sfxPlayer); - _instance._activeSfxPlayers.Add(sfxPlayer); + AudioStreamPlayer2D sfxPlayer = _sfxPool.Get(); sfxPlayer.GlobalPosition = position; + sfxPlayer.Stream = sound; + sfxPlayer.VolumeDb = NormalizeConfigVolume(_options.SFXVolume); + sfxPlayer.PitchScale = GetRandomPitch(minPitch, maxPitch); + sfxPlayer.Finished += OnFinished; sfxPlayer.Play(); + + void OnFinished() + { + sfxPlayer.Finished -= OnFinished; + _sfxPool.Release(sfxPlayer); + } } - public static void FadeOutSFX(double fadeTime = 1) + /// + /// Fades out all currently playing sound effects over the specified duration in seconds. + /// + public void FadeOutSFX(double fadeTime = 1) { - foreach (AudioStreamPlayer2D sfxPlayer in _instance._activeSfxPlayers) + foreach (AudioStreamPlayer2D sfxPlayer in _sfxPool.ActiveNodes) { new GodotTween(sfxPlayer).Animate(AudioStreamPlayer.PropertyName.VolumeDb, MutedVolume, fadeTime); } } - public static void SetMusicVolume(float volume) + /// + /// Sets the music volume, affecting current playback. Volume is in config scale (0-100). + /// + public void SetMusicVolume(float volume) { - _instance._musicPlayer.VolumeDb = NormalizeConfigVolume(volume); - _instance._options.MusicVolume = volume; + _musicPlayer.VolumeDb = NormalizeConfigVolume(volume); + _options.MusicVolume = volume; } - public static void SetSFXVolume(float volume) + /// + /// Sets the SFX volume for all active sound effect players. Volume is in config scale (0-100). + /// + public void SetSFXVolume(float volume) { - _instance._options.SFXVolume = volume; + _options.SFXVolume = volume; float mappedVolume = NormalizeConfigVolume(volume); - foreach (AudioStreamPlayer2D sfxPlayer in _instance._activeSfxPlayers) + foreach (AudioStreamPlayer2D sfxPlayer in _sfxPool.ActiveNodes) { sfxPlayer.VolumeDb = mappedVolume; } } + /// + /// Generates a random pitch between min and max, avoiding values too similar to the previous sound. + /// + private float GetRandomPitch(float min, float max) + { + RandomNumberGenerator rng = new(); + rng.Randomize(); + + float pitch = rng.RandfRange(min, max); + + while (Mathf.Abs(pitch - _lastPitch) < RandomPitchThreshold) + { + rng.Randomize(); + pitch = rng.RandfRange(min, max); + } + + _lastPitch = pitch; + return pitch; + } + + /// + /// Instantly plays the given audio stream with the specified player and volume. + /// private static void PlayAudio(AudioStreamPlayer player, AudioStream song, float volume) { player.Stream = song; @@ -117,6 +146,9 @@ private static void PlayAudio(AudioStreamPlayer player, AudioStream song, float player.Play(); } + /// + /// Smoothly crossfades between songs by fading out the current and fading in the new one. Volume is in config scale (0-100). + /// private static void PlayAudioCrossfade(AudioStreamPlayer player, AudioStream song, float volume, double fadeOut, double fadeIn) { new GodotTween(player) @@ -126,25 +158,11 @@ private static void PlayAudioCrossfade(AudioStreamPlayer player, AudioStream son .AnimateProp(NormalizeConfigVolume(volume), fadeIn).EaseIn(); } + /// + /// Maps a config volume value (0-100) to an AudioStreamPlayer VolumeDb value, returning mute if zero. + /// private static float NormalizeConfigVolume(float volume) { return volume == 0 ? MutedVolume : volume.Remap(0, 100, MutedVolumeNormalized, 0); } - - private static float GetRandomPitch(float min, float max) - { - RandomNumberGenerator rng = new(); - rng.Randomize(); - - float pitch = rng.RandfRange(min, max); - - while (Mathf.Abs(pitch - _instance._lastPitch) < RandomPitchThreshold) - { - rng.Randomize(); - pitch = rng.RandfRange(min, max); - } - - _instance._lastPitch = pitch; - return pitch; - } } diff --git a/Framework/Autoloads/Autoloads.cs b/Framework/Autoloads/Autoloads.cs index ded2c352..8c08eb18 100644 --- a/Framework/Autoloads/Autoloads.cs +++ b/Framework/Autoloads/Autoloads.cs @@ -1,13 +1,16 @@ +using __TEMPLATE__.Debugging; +using __TEMPLATE__.UI; +using __TEMPLATE__.UI.Console; using Godot; -using GodotUtils.UI; -using GodotUtils.UI.Console; +using GodotUtils; +using System; +using System.Threading.Tasks; + #if DEBUG using GodotUtils.Debugging; #endif -using System; -using System.Threading.Tasks; -namespace GodotUtils; +namespace __TEMPLATE__; // Autoload // Access this with GetNode("/root/Autoloads") @@ -19,17 +22,17 @@ public partial class Autoloads : Node public static Autoloads Instance { get; private set; } - // Game developers should be able to access each individual manager - public ComponentManager ComponentManager { get; private set; } + public ComponentManager ComponentManager { get; private set; } // Cannot use [Export] here because Godot will bug out and unlink export path in editor after setup completes and restarts the editor + public GameConsole GameConsole { get; private set; } // Cannot use [Export] here because Godot will bug out and unlink export path in editor after setup completes and restarts the editor public AudioManager AudioManager { get; private set; } public OptionsManager OptionsManager { get; private set; } public Services Services { get; private set; } public MetricsOverlay MetricsOverlay { get; private set; } public SceneManager SceneManager { get; private set; } - public GameConsole GameConsole { get; private set; } + public Profiler Profiler { get; private set; } #if NETCODE_ENABLED - private Logger _logger; + public Logger Logger { get; private set; } #endif #if DEBUG @@ -43,13 +46,14 @@ public override void _EnterTree() Instance = this; ComponentManager = GetNode("ComponentManager"); - GameConsole = GetNode("%Console"); SceneManager = new SceneManager(this, _scenes); Services = new Services(this); MetricsOverlay = new MetricsOverlay(); + Profiler = new Profiler(); + GameConsole = GetNode("%Console"); #if NETCODE_ENABLED - _logger = new Logger(GameConsole); + Logger = new Logger(GameConsole); #endif } @@ -76,7 +80,7 @@ public override void _Process(double delta) #endif #if NETCODE_ENABLED - _logger.Update(); + Logger.Update(); #endif } @@ -85,11 +89,11 @@ public override void _PhysicsProcess(double delta) MetricsOverlay.UpdatePhysics(); } - public override async void _Notification(int what) + public override void _Notification(int what) { if (what == NotificationWMCloseRequest) { - await QuitAndCleanup(); + ExitGame().FireAndForget(); } } @@ -98,15 +102,13 @@ public override void _ExitTree() AudioManager.Dispose(); OptionsManager.Dispose(); SceneManager.Dispose(); - Services.Dispose(); - MetricsOverlay.Dispose(); #if DEBUG _visualizeAutoload.Dispose(); #endif #if NETCODE_ENABLED - _logger.Dispose(); + Logger.Dispose(); #endif Profiler.Dispose(); @@ -114,16 +116,13 @@ public override void _ExitTree() Instance = null; } - // Using deferred is always complicated... + // I'm pretty sure Deferred must be called from a script that extends from Node public void DeferredSwitchSceneProxy(string rawName, Variant transTypeVariant) { - if (SceneManager.Instance == null) - return; - - SceneManager.Instance.DeferredSwitchScene(rawName, transTypeVariant); + SceneManager.DeferredSwitchScene(rawName, transTypeVariant); } - public async Task QuitAndCleanup() + public async Task ExitGame() { GetTree().AutoAcceptQuit = false; @@ -132,10 +131,16 @@ public async Task QuitAndCleanup() { // Since the PreQuit event contains a Task only the first subscriber will be invoked // with await PreQuit?.Invoke(); so need to ensure all subs are invoked. - Delegate[] invocationList = PreQuit.GetInvocationList(); - foreach (Func subscriber in invocationList) + foreach (Func subscriber in PreQuit.GetInvocationList()) { - await subscriber(); + try + { + await subscriber(); + } + catch (Exception ex) + { + GD.PrintErr($"PreQuit subscriber failed: {ex}"); + } } } diff --git a/Framework/Autoloads/Autoloads.tscn b/Framework/Autoloads/Autoloads.tscn index ba0089a5..21b04e5e 100644 --- a/Framework/Autoloads/Autoloads.tscn +++ b/Framework/Autoloads/Autoloads.tscn @@ -17,10 +17,12 @@ Options = ExtResource("5_vm4c5") Credits = ExtResource("2_xdmsn") metadata/_custom_type_script = "uid://17yxgcswri77" -[node name="Autoloads" type="Node"] +[node name="Autoloads" type="Node" node_paths=PackedStringArray("GameConsole", "ComponentManager")] process_mode = 3 script = ExtResource("1_00yjk") _scenes = SubResource("Resource_xdmsn") +GameConsole = NodePath("Debug/Console") +ComponentManager = NodePath("ComponentManager") [node name="ComponentManager" type="Node" parent="."] process_mode = 1 diff --git a/Framework/Autoloads/BBColor.cs b/Framework/Autoloads/BBColor.cs new file mode 100644 index 00000000..4d659381 --- /dev/null +++ b/Framework/Autoloads/BBColor.cs @@ -0,0 +1,19 @@ +namespace __TEMPLATE__; + +// Full list of BBCode color tags: https://absitomen.com/index.php?topic=331.0 +public enum BBColor +{ + Gray, + DarkGray, + Green, + DarkGreen, + LightGreen, + Aqua, + DarkAqua, + Deepskyblue, + Magenta, + Red, + White, + Yellow, + Orange +} diff --git a/Framework/Autoloads/BBColor.cs.uid b/Framework/Autoloads/BBColor.cs.uid new file mode 100644 index 00000000..87bd7403 --- /dev/null +++ b/Framework/Autoloads/BBColor.cs.uid @@ -0,0 +1 @@ +uid://n1sa3nkk7iho diff --git a/Framework/Autoloads/Logger.cs b/Framework/Autoloads/Logger.cs index 7ceae1b6..80216343 100644 --- a/Framework/Autoloads/Logger.cs +++ b/Framework/Autoloads/Logger.cs @@ -1,5 +1,6 @@ +using __TEMPLATE__.UI.Console; using Godot; -using GodotUtils.UI.Console; +using GodotUtils; using System; using System.Collections.Concurrent; using System.Diagnostics; @@ -7,7 +8,7 @@ using System.Runtime.CompilerServices; using System.Text; -namespace GodotUtils; +namespace __TEMPLATE__; /* * This is meant to replace all GD.Print(...) with Logger.Log(...) to make logging multi-thread friendly. @@ -18,15 +19,16 @@ public class Logger : IDisposable { public event Action MessageLogged; - private static Logger _instance; - private ConcurrentQueue _messages = []; - private GameConsole _console; + private readonly ConcurrentQueue _messages = []; + private readonly GameConsole _console; + + public Logger() // No game console dependence + { + } public Logger(GameConsole console) { - _instance = this; _console = console; - MessageLogged += _console.AddMessage; } @@ -37,27 +39,25 @@ public void Update() public void Dispose() { - MessageLogged -= _console.AddMessage; - _instance = null; + if (_console != null) + MessageLogged -= _console.AddMessage; } /// /// Log a message /// - public static void Log(object message, BBColor color = BBColor.Gray) + public void Log(object message, BBColor color = BBColor.Gray) { - _instance._messages.Enqueue(new LogInfo(LoggerOpcode.Message, new LogMessage($"{message}"), color)); + _messages.Enqueue(new LogInfo(LoggerOpcode.Message, new LogMessage($"{message}"), color)); } /// /// Logs multiple objects by concatenating them into a single message. /// - public static void Log(params object[] objects) + public void Log(params object[] objects) { if (objects == null || objects.Length == 0) - { return; // or handle the case where no objects are provided - } StringBuilder messageBuilder = new(); @@ -71,13 +71,13 @@ public static void Log(params object[] objects) LogInfo logInfo = new(LoggerOpcode.Message, new LogMessage(message)); - _instance._messages.Enqueue(logInfo); + _messages.Enqueue(logInfo); } /// /// Log a warning /// - public static void LogWarning(object message, BBColor color = BBColor.Orange) + public void LogWarning(object message, BBColor color = BBColor.Orange) { Log($"[Warning] {message}", color); } @@ -85,7 +85,7 @@ public static void LogWarning(object message, BBColor color = BBColor.Orange) /// /// Log a todo /// - public static void LogTodo(object message, BBColor color = BBColor.White) + public void LogTodo(object message, BBColor color = BBColor.White) { Log($"[Todo] {message}", color); } @@ -93,14 +93,12 @@ public static void LogTodo(object message, BBColor color = BBColor.White) /// /// Logs an exception with trace information. Optionally allows logging a human readable hint /// - public static void LogErr - ( + public void LogErr( Exception e, string hint = default, BBColor color = BBColor.Red, [CallerFilePath] string filePath = default, - [CallerLineNumber] int lineNumber = 0 - ) + [CallerLineNumber] int lineNumber = 0) { LogDetailed(LoggerOpcode.Exception, $"[Error] {(string.IsNullOrWhiteSpace(hint) ? "" : $"'{hint}' ")}{e.Message}{e.StackTrace}", color, true, filePath, lineNumber); } @@ -108,14 +106,12 @@ public static void LogErr /// /// Logs a debug message that optionally contains trace information /// - public static void LogDebug - ( + public void LogDebug( object message, BBColor color = BBColor.Magenta, bool trace = true, [CallerFilePath] string filePath = default, - [CallerLineNumber] int lineNumber = 0 - ) + [CallerLineNumber] int lineNumber = 0) { LogDetailed(LoggerOpcode.Debug, $"[Debug] {message}", color, trace, filePath, lineNumber); } @@ -123,7 +119,7 @@ public static void LogDebug /// /// Log the time it takes to do a section of code /// - public static void LogMs(Action code) + public void LogMs(Action code) { Stopwatch watch = new(); watch.Start(); @@ -135,83 +131,63 @@ public static void LogMs(Action code) /// /// Checks to see if there are any messages left in the queue /// - public static bool StillWorking() + public bool StillWorking() { - return !_instance._messages.IsEmpty; + return !_messages.IsEmpty; } /// - /// Dequeues a Requested Message and Logs it + /// Dequeues all requested messages and logs them /// private void DequeueMessages() { - if (!_messages.TryDequeue(out LogInfo result)) - { - return; - } + while (_messages.TryDequeue(out LogInfo result)) + DequeueMessage(result); + } + /// + /// Dequeues a message and logs it. + /// + /// The information from the message to log + private void DequeueMessage(LogInfo result) + { switch (result.Opcode) { case LoggerOpcode.Message: Print(result.Data.Message, result.Color); - System.Console.ResetColor(); break; case LoggerOpcode.Exception: PrintErr(result.Data.Message); if (result.Data is LogMessageTrace exceptionData && exceptionData.ShowTrace) - { PrintErr(exceptionData.TracePath); - } - System.Console.ResetColor(); break; case LoggerOpcode.Debug: Print(result.Data.Message, result.Color); if (result.Data is LogMessageTrace debugData && debugData.ShowTrace) - { Print(debugData.TracePath, BBColor.DarkGray); - } - System.Console.ResetColor(); break; } + Console.ResetColor(); MessageLogged?.Invoke(result.Data.Message); } /// /// Logs a message that may contain trace information /// - private static void LogDetailed(LoggerOpcode opcode, string message, BBColor color, bool trace, string filePath, int lineNumber) + private void LogDetailed(LoggerOpcode opcode, string message, BBColor color, bool trace, string filePath, int lineNumber) { - string tracePath; - - if (filePath.Contains("Scripts")) - { - // Ex: Scripts/Main.cs:23 - tracePath = $" at {filePath.Substring(filePath.IndexOf("Scripts", StringComparison.Ordinal))}:{lineNumber}"; - tracePath = tracePath.Replace(Path.DirectorySeparatorChar, '/'); - } - else - { - // Main.cs:23 - string[] elements = filePath.Split(Path.DirectorySeparatorChar); - tracePath = $" at {elements[elements.Length - 1]}:{lineNumber}"; - } + string[] elements = filePath.Split(Path.DirectorySeparatorChar); + string tracePath = $" at {elements[^1]}:{lineNumber}"; // TracePath could become for example: "at Main.cs:23" - _instance._messages.Enqueue( - new LogInfo(opcode, - new LogMessageTrace( - message, - trace, - tracePath - ), - color - )); + _messages.Enqueue( + new LogInfo(opcode, new LogMessageTrace(message, trace, tracePath), color)); } private static void Print(object v, BBColor color) @@ -262,21 +238,3 @@ private enum LoggerOpcode Debug } } - -// Full list of BBCode color tags: https://absitomen.com/index.php?topic=331.0 -public enum BBColor -{ - Gray, - DarkGray, - Green, - DarkGreen, - LightGreen, - Aqua, - DarkAqua, - Deepskyblue, - Magenta, - Red, - White, - Yellow, - Orange -} diff --git a/Framework/Autoloads/ModLoaderUI.cs b/Framework/Autoloads/ModLoaderUI.cs index 92bc029b..067e817d 100644 --- a/Framework/Autoloads/ModLoaderUI.cs +++ b/Framework/Autoloads/ModLoaderUI.cs @@ -5,7 +5,7 @@ using System.Runtime.Loader; using System.Text.Json; -namespace GodotUtils.UI; +namespace __TEMPLATE__.UI; public class ModLoaderUI { @@ -27,7 +27,7 @@ public void LoadMods(Node node) if (dir == null) { - Logger.LogWarning("Failed to open Mods directory has it does not exist"); + Game.Logger.LogWarning("Failed to open Mods directory has it does not exist"); return; } @@ -52,7 +52,7 @@ public void LoadMods(Node node) if (!File.Exists(modJson)) { - Logger.LogWarning($"The mod folder '{filename}' does not have a mod.json so it will not be loaded"); + Game.Logger.LogWarning($"The mod folder '{filename}' does not have a mod.json so it will not be loaded"); goto Next; } @@ -64,7 +64,7 @@ public void LoadMods(Node node) if (_mods.ContainsKey(modInfo.Id)) { - Logger.LogWarning($"Duplicate mod id '{modInfo.Id}' was skipped"); + Game.Logger.LogWarning($"Duplicate mod id '{modInfo.Id}' was skipped"); goto Next; } @@ -89,7 +89,7 @@ public void LoadMods(Node node) if (!success) { - Logger.LogWarning($"Failed to load pck file for mod '{modInfo.Name}'"); + Game.Logger.LogWarning($"Failed to load pck file for mod '{modInfo.Name}'"); goto Next; } @@ -99,7 +99,7 @@ public void LoadMods(Node node) if (importedScene == null) { - Logger.LogWarning($"Failed to load mod.tscn for mod '{modInfo.Name}'"); + Game.Logger.LogWarning($"Failed to load mod.tscn for mod '{modInfo.Name}'"); goto Next; } diff --git a/Framework/Autoloads/SceneManager.cs b/Framework/Autoloads/SceneManager.cs index adff1be7..ebb2c95b 100644 --- a/Framework/Autoloads/SceneManager.cs +++ b/Framework/Autoloads/SceneManager.cs @@ -1,32 +1,29 @@ +using __TEMPLATE__.UI; using Godot; -using GodotUtils.UI; +using GodotUtils; using System; -namespace GodotUtils; +namespace __TEMPLATE__; // About Scene Switching: https://docs.godotengine.org/en/latest/tutorials/scripting/singletons_autoload.html -public class SceneManager : IDisposable +public class SceneManager { /// /// The event is invoked right before the scene is changed /// public event Action PreSceneChanged; - public static SceneManager Instance { get; private set; } - public MenuScenes MenuScenes { get; private set; } + public const int DefaultSceneFadeDuration = 2; + private MenuScenes _menuScenes; private SceneTree _tree; private Autoloads _autoloads; private Node _currentScene; public SceneManager(Autoloads autoloads, MenuScenes scenes) { - if (Instance != null) - throw new InvalidOperationException($"{nameof(SceneManager)} was initialized already"); - - Instance = this; _autoloads = autoloads; - MenuScenes = scenes; + _menuScenes = scenes; _tree = autoloads.GetTree(); Window root = _tree.Root; @@ -37,37 +34,38 @@ public SceneManager(Autoloads autoloads, MenuScenes scenes) PreSceneChanged += OnPreSceneChanged; } - public void Dispose() + public Node GetCurrentScene() { - PreSceneChanged -= OnPreSceneChanged; - Instance = null; + return _currentScene; } - private void OnPreSceneChanged(string scene) => AudioManager.FadeOutSFX(); - - public static Node GetCurrentScene() + public void Dispose() { - return Instance._currentScene; + PreSceneChanged -= OnPreSceneChanged; } - public static void SwitchScene(PackedScene scene, TransType transType = TransType.None) + public void SwitchToOptions(TransType transType = TransType.None) => SwitchTo(_menuScenes.Options, transType); + public void SwitchToMainMenu(TransType transType = TransType.None) => SwitchTo(_menuScenes.MainMenu, transType); + public void SwitchToModLoader(TransType transType = TransType.None) => SwitchTo(_menuScenes.ModLoader, transType); + public void SwitchToCredits(TransType transType = TransType.None) => SwitchTo(_menuScenes.Credits, transType); + + public void SwitchTo(PackedScene scene, TransType transType = TransType.None) { ArgumentNullException.ThrowIfNull(scene); string path = scene.ResourcePath; - Instance.PreSceneChanged?.Invoke(path); + PreSceneChanged?.Invoke(path); switch (transType) { case TransType.None: - Instance.ChangeScene(path, transType); + ChangeScene(path, transType); break; case TransType.Fade: - Instance.FadeTo(TransColor.Black, 2, () => Instance.ChangeScene(path, transType)); + FadeTo(TransColor.Black, DefaultSceneFadeDuration, () => ChangeScene(path, transType)); break; } } - /// /// Resets the currently active scene. /// @@ -84,12 +82,6 @@ public void ResetCurrentScene() _autoloads.CallDeferred(nameof(Autoloads.DeferredSwitchSceneProxy), sceneFilePath, Variant.From(TransType.None)); } - private void ChangeScene(string scenePath, TransType transType) - { - // Wait for engine to be ready before switching scenes - _autoloads.CallDeferred(nameof(Autoloads.DeferredSwitchSceneProxy), scenePath, Variant.From(transType)); - } - public void DeferredSwitchScene(string rawName, Variant transTypeVariant) { // Safe to remove scene now @@ -119,6 +111,14 @@ public void DeferredSwitchScene(string rawName, Variant transTypeVariant) } } + private void OnPreSceneChanged(string scene) => Game.Audio.FadeOutSFX(); + + private void ChangeScene(string scenePath, TransType transType) + { + // Wait for engine to be ready before switching scenes + _autoloads.CallDeferred(nameof(Autoloads.DeferredSwitchSceneProxy), scenePath, Variant.From(transType)); + } + private void FadeTo(TransColor transColor, double duration, Action finished = null) { // Add canvas layer to scene @@ -156,7 +156,7 @@ public enum TransType Fade } - private enum TransColor + public enum TransColor { Black, Transparent diff --git a/Framework/Autoloads/Services.cs b/Framework/Autoloads/Services.cs index f483eda0..acb8f755 100644 --- a/Framework/Autoloads/Services.cs +++ b/Framework/Autoloads/Services.cs @@ -1,49 +1,35 @@ using Godot; +using GodotUtils; using System; using System.Collections.Generic; -namespace GodotUtils; +namespace __TEMPLATE__; /// /// Services have a scene lifetime meaning they will be destroyed when the scene changes. Services /// aid as an alternative to using the static keyword everywhere. /// -public partial class Services : IDisposable +public class Services(Autoloads autoloads) { /// /// Dictionary to store registered services, keyed by their type. /// - private static Services _instance; private Dictionary _services = []; - private SceneManager _sceneManager; - - public Services(Autoloads autoloads) - { - if (_instance != null) - throw new InvalidOperationException($"{nameof(Services)} was initialized already"); - - _instance = this; - _sceneManager = autoloads.SceneManager; - } - - public void Dispose() - { - _instance = null; - } + private SceneManager _sceneManager = autoloads.SceneManager; /// /// Retrieves a service of the specified type. /// /// The type of the service to retrieve. /// The instance of the service. - public static T Get() + public T Get() { - if (!_instance._services.ContainsKey(typeof(T))) + if (!_services.ContainsKey(typeof(T))) { throw new Exception($"Unable to obtain service '{typeof(T)}'"); } - return (T)_instance._services[typeof(T)].Instance; + return (T)_services[typeof(T)].Instance; } /// @@ -55,9 +41,9 @@ public static T Get() /// /// Thrown if a service of the same has already been registered. /// - public static void Register(Node node) + public void Register(Node node) { - if (_instance._services.ContainsKey(node.GetType())) + if (_services.ContainsKey(node.GetType())) { throw new Exception($"There can only be one service of type '{node.GetType().Name}'"); } @@ -69,14 +55,14 @@ public static void Register(Node node) /// /// Adds a service to the service provider. /// - private static void AddService(Node node) + private void AddService(Node node) { Service service = new() { Instance = node }; - _instance._services.Add(node.GetType(), service); + _services.Add(node.GetType(), service); RemoveServiceOnSceneChanged(service); } @@ -84,18 +70,18 @@ private static void AddService(Node node) /// /// Removes a service when the scene changes. /// - private static void RemoveServiceOnSceneChanged(Service service) + private void RemoveServiceOnSceneChanged(Service service) { // The scene has changed, remove all services - _instance._sceneManager.PreSceneChanged += Cleanup; + _sceneManager.PreSceneChanged += Cleanup; void Cleanup(string scene) { // Stop listening to PreSceneChanged - _instance._sceneManager.PreSceneChanged -= Cleanup; + _sceneManager.PreSceneChanged -= Cleanup; // Remove the service - bool success = _instance._services.Remove(service.Instance.GetType()); + bool success = _services.Remove(service.Instance.GetType()); if (!success) { @@ -115,7 +101,7 @@ public override string ToString() /// /// A class representing a service instance /// - public class Service + private class Service { /// /// The instance of the service. diff --git a/Framework/Components/Component.cs b/Framework/Components/Component.cs index baffc356..4e9b155a 100644 --- a/Framework/Components/Component.cs +++ b/Framework/Components/Component.cs @@ -1,8 +1,9 @@ using Godot; +using GodotUtils; using System; using System.Threading.Tasks; -namespace GodotUtils; +namespace __TEMPLATE__; public class Component : IDisposable { diff --git a/Framework/Components/ComponentManager.cs b/Framework/Components/ComponentManager.cs index 94219c10..75ec796e 100644 --- a/Framework/Components/ComponentManager.cs +++ b/Framework/Components/ComponentManager.cs @@ -1,7 +1,7 @@ using Godot; using System.Collections.Generic; -namespace GodotUtils; +namespace __TEMPLATE__; /// /// Using any kind of Godot functions from C# is expensive, so we try to minimize this with centralized logic. diff --git a/Framework/Components/RotationComponent.cs b/Framework/Components/RotationComponent.cs index 7c1fd51b..83877000 100644 --- a/Framework/Components/RotationComponent.cs +++ b/Framework/Components/RotationComponent.cs @@ -1,6 +1,6 @@ using Godot; -namespace GodotUtils; +namespace __TEMPLATE__; // Useful to quickly rotate a Sprite2D node to see if the game is truly paused or not [GlobalClass] diff --git a/Framework/Console/CommandLineArgs.cs b/Framework/Console/CommandLineArgs.cs index 306d8c7c..dc5791a5 100644 --- a/Framework/Console/CommandLineArgs.cs +++ b/Framework/Console/CommandLineArgs.cs @@ -1,7 +1,7 @@ using Godot; using System.Collections.Generic; -namespace GodotUtils.UI.Console; +namespace __TEMPLATE__.UI.Console; /// /// Handles custom command line arguments set for each instance. diff --git a/Framework/Console/ConsoleCommandInfo.cs b/Framework/Console/ConsoleCommandInfo.cs index cf041c38..4dc9a455 100644 --- a/Framework/Console/ConsoleCommandInfo.cs +++ b/Framework/Console/ConsoleCommandInfo.cs @@ -1,6 +1,6 @@ using System; -namespace GodotUtils.UI.Console; +namespace __TEMPLATE__.UI.Console; public class ConsoleCommandInfo { diff --git a/Framework/Console/ConsoleHistory.cs b/Framework/Console/ConsoleHistory.cs index 0053fe45..7d720655 100644 --- a/Framework/Console/ConsoleHistory.cs +++ b/Framework/Console/ConsoleHistory.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace GodotUtils.UI.Console; +namespace __TEMPLATE__.UI.Console; public class ConsoleHistory { diff --git a/Framework/Console/GameConsole.cs b/Framework/Console/GameConsole.cs index dd81e137..4b41fe8b 100644 --- a/Framework/Console/GameConsole.cs +++ b/Framework/Console/GameConsole.cs @@ -3,14 +3,12 @@ using System.Collections.Generic; using System.Linq; -namespace GodotUtils.UI.Console; +namespace __TEMPLATE__.UI.Console; public partial class GameConsole : Node { private const int MaxTextFeed = 1000; - public static GameConsole Instance { get; private set; } - private List _commands = []; private ConsoleHistory _history = new(); private PanelContainer _mainContainer; @@ -23,11 +21,6 @@ public partial class GameConsole : Node public override void _Ready() { - if (Instance != null) - throw new InvalidOperationException($"{nameof(GameConsole)} was initialized already"); - - Instance = this; - _feed = GetNode("%Output"); _input = GetNode("%CmdsInput"); _settingsBtn = GetNode