diff --git a/Autoloads/EventBus.cs b/Autoloads/EventBus.cs index 9b03718..fd881de 100644 --- a/Autoloads/EventBus.cs +++ b/Autoloads/EventBus.cs @@ -1,9 +1,130 @@ using Godot; +using Mr.BrickAdventures.scripts.components; +using Mr.BrickAdventures.scripts.Resources; namespace Mr.BrickAdventures.Autoloads; +/// +/// Global event bus for decoupled communication between game systems. +/// Use the static Instance property for easy access from anywhere. +/// public partial class EventBus : Node { + /// + /// Singleton instance. Available after the autoload is initialized. + /// + public static EventBus Instance { get; private set; } + + public override void _Ready() + { + Instance = this; + } + + public override void _ExitTree() + { + if (Instance == this) + Instance = null; + } + + #region Level Events + [Signal] public delegate void LevelStartedEventHandler(int levelIndex, Node currentScene); [Signal] public delegate void LevelCompletedEventHandler(int levelIndex, Node currentScene, double completionTime); + [Signal] public delegate void LevelRestartedEventHandler(int levelIndex); + + public static void EmitLevelStarted(int levelIndex, Node currentScene) + => Instance?.EmitSignal(SignalName.LevelStarted, levelIndex, currentScene); + + public static void EmitLevelCompleted(int levelIndex, Node currentScene, double completionTime) + => Instance?.EmitSignal(SignalName.LevelCompleted, levelIndex, currentScene, completionTime); + + public static void EmitLevelRestarted(int levelIndex) + => Instance?.EmitSignal(SignalName.LevelRestarted, levelIndex); + + #endregion + + #region Player Events + + [Signal] public delegate void PlayerSpawnedEventHandler(PlayerController player); + [Signal] public delegate void PlayerDiedEventHandler(Vector2 position); + [Signal] public delegate void PlayerDamagedEventHandler(float damage, float remainingHealth, Vector2 position); + [Signal] public delegate void PlayerHealedEventHandler(float amount, float newHealth, Vector2 position); + + public static void EmitPlayerSpawned(PlayerController player) + => Instance?.EmitSignal(SignalName.PlayerSpawned, player); + + public static void EmitPlayerDied(Vector2 position) + => Instance?.EmitSignal(SignalName.PlayerDied, position); + + public static void EmitPlayerDamaged(float damage, float remainingHealth, Vector2 position) + => Instance?.EmitSignal(SignalName.PlayerDamaged, damage, remainingHealth, position); + + public static void EmitPlayerHealed(float amount, float newHealth, Vector2 position) + => Instance?.EmitSignal(SignalName.PlayerHealed, amount, newHealth, position); + + #endregion + + #region Combat Events + + [Signal] public delegate void EnemyDefeatedEventHandler(Node enemy, Vector2 position); + [Signal] public delegate void EnemyDamagedEventHandler(Node enemy, float damage, Vector2 position); + + public static void EmitEnemyDefeated(Node enemy, Vector2 position) + => Instance?.EmitSignal(SignalName.EnemyDefeated, enemy, position); + + public static void EmitEnemyDamaged(Node enemy, float damage, Vector2 position) + => Instance?.EmitSignal(SignalName.EnemyDamaged, enemy, damage, position); + + #endregion + + #region Collection Events + + [Signal] public delegate void CoinCollectedEventHandler(int amount, Vector2 position); + [Signal] public delegate void ItemCollectedEventHandler(CollectableType itemType, float amount, Vector2 position); + [Signal] public delegate void ChildRescuedEventHandler(Vector2 position); + + public static void EmitCoinCollected(int amount, Vector2 position) + => Instance?.EmitSignal(SignalName.CoinCollected, amount, position); + + public static void EmitItemCollected(CollectableType itemType, float amount, Vector2 position) + => Instance?.EmitSignal(SignalName.ItemCollected, (int)itemType, amount, position); + + public static void EmitChildRescued(Vector2 position) + => Instance?.EmitSignal(SignalName.ChildRescued, position); + + #endregion + + #region Skill Events + + [Signal] public delegate void SkillUnlockedEventHandler(string skillName, int level); + [Signal] public delegate void SkillActivatedEventHandler(string skillName); + [Signal] public delegate void SkillDeactivatedEventHandler(string skillName); + + public static void EmitSkillUnlocked(string skillName, int level = 1) + => Instance?.EmitSignal(SignalName.SkillUnlocked, skillName, level); + + public static void EmitSkillActivated(string skillName) + => Instance?.EmitSignal(SignalName.SkillActivated, skillName); + + public static void EmitSkillDeactivated(string skillName) + => Instance?.EmitSignal(SignalName.SkillDeactivated, skillName); + + #endregion + + #region Game State Events + + [Signal] public delegate void GamePausedEventHandler(); + [Signal] public delegate void GameResumedEventHandler(); + [Signal] public delegate void GameSavedEventHandler(); + + public static void EmitGamePaused() + => Instance?.EmitSignal(SignalName.GamePaused); + + public static void EmitGameResumed() + => Instance?.EmitSignal(SignalName.GameResumed); + + public static void EmitGameSaved() + => Instance?.EmitSignal(SignalName.GameSaved); + + #endregion } \ No newline at end of file diff --git a/Autoloads/GameManager.cs b/Autoloads/GameManager.cs index f449190..6997c1a 100644 --- a/Autoloads/GameManager.cs +++ b/Autoloads/GameManager.cs @@ -10,18 +10,19 @@ namespace Mr.BrickAdventures.Autoloads; public partial class GameManager : Node { [Export] public Array LevelScenes { get; set; } = []; - - public PlayerController Player { + + public PlayerController Player + { get => GetPlayer(); private set => _player = value; } - + private List _sceneNodes = []; private PlayerController _player; private SpeedRunManager _speedRunManager; - private EventBus _eventBus; - - [Export] + + + [Export] public Dictionary PlayerState { get; set; } = new() { { "coins", 0 }, @@ -31,7 +32,7 @@ public partial class GameManager : Node { "unlocked_levels", new Array() {0}}, { "unlocked_skills", new Array() } }; - + [Export] public Dictionary CurrentSessionState { get; private set; } = new() { @@ -55,14 +56,14 @@ public partial class GameManager : Node public override void _Ready() { _speedRunManager = GetNode("/root/SpeedRunManager"); - _eventBus = GetNode("/root/EventBus"); + } private void OnNodeAdded(Node node) { _sceneNodes.Add(node); } - + private void OnNodeRemoved(Node node) { _sceneNodes.Remove(node); @@ -76,11 +77,11 @@ public partial class GameManager : Node { PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"] + amount); } - + public void SetCoins(int amount) => PlayerState["coins"] = Mathf.Max(0, amount); - + public int GetCoins() => (int)PlayerState["coins"] + (int)CurrentSessionState["coins_collected"]; - + public void RemoveCoins(int amount) { var sessionCoins = (int)CurrentSessionState["coins_collected"]; @@ -96,12 +97,12 @@ public partial class GameManager : Node } PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"]); } - + public void AddLives(int amount) => PlayerState["lives"] = (int)PlayerState["lives"] + amount; public void RemoveLives(int amount) => PlayerState["lives"] = (int)PlayerState["lives"] - amount; public void SetLives(int amount) => PlayerState["lives"] = amount; public int GetLives() => (int)PlayerState["lives"]; - + public bool IsSkillUnlocked(SkillData skill) { return ((Array)PlayerState["unlocked_skills"]).Contains(skill) @@ -120,7 +121,7 @@ public partial class GameManager : Node foreach (SkillData s in arr) { if (s.Name != skillName) continue; - + arr.Remove(s); break; } @@ -145,7 +146,7 @@ public partial class GameManager : Node { "statistics", new Godot.Collections.Dictionary()} }; } - + public void UnlockLevel(int levelIndex) { var unlocked = (Array)PlayerState["unlocked_levels"]; @@ -160,10 +161,10 @@ public partial class GameManager : Node { PlayerState["current_level"] = next; GetTree().ChangeSceneToPacked(LevelScenes[next]); - _eventBus.EmitSignal(EventBus.SignalName.LevelStarted, next, GetTree().CurrentScene); + EventBus.EmitLevelStarted(next, GetTree().CurrentScene); } } - + public void MarkLevelComplete(int levelIndex) { UnlockLevel(levelIndex + 1); @@ -179,7 +180,7 @@ public partial class GameManager : Node { "skills_unlocked", new Array() } }; } - + public void RestartGame() { ResetPlayerState(); @@ -187,19 +188,19 @@ public partial class GameManager : Node GetTree().ChangeSceneToPacked(LevelScenes[0]); GetNode("/root/SaveSystem").SaveGame(); } - + public void QuitGame() => GetTree().Quit(); public void PauseGame() => Engine.TimeScale = 0; public void ResumeGame() => Engine.TimeScale = 1; - + public void StartNewGame() { ResetPlayerState(); ResetCurrentSessionState(); - + _speedRunManager?.StartTimer(); - + GetTree().ChangeSceneToPacked(LevelScenes[0]); GetNode("/root/SaveSystem").SaveGame(); } @@ -220,19 +221,19 @@ public partial class GameManager : Node else GD.PrintErr("No levels unlocked to continue."); } - + public void OnLevelComplete() { var levelIndex = (int)PlayerState["current_level"]; MarkLevelComplete(levelIndex); - + AddCoins((int)CurrentSessionState["coins_collected"]); foreach (var s in (Array)CurrentSessionState["skills_unlocked"]) UnlockSkill((SkillData)s); var completionTime = _speedRunManager?.GetCurrentLevelTime() ?? 0.0; - _eventBus.EmitSignal(EventBus.SignalName.LevelCompleted, levelIndex, GetTree().CurrentScene, completionTime); - + EventBus.EmitLevelCompleted(levelIndex, GetTree().CurrentScene, completionTime); + ResetCurrentSessionState(); TryToGoToNextLevel(); GetNode("/root/SaveSystem").SaveGame(); @@ -253,17 +254,17 @@ public partial class GameManager : Node public PlayerController GetPlayer() { if (_player != null && IsInstanceValid(_player)) return _player; - + _player = null; foreach (var node in _sceneNodes) { if (node is not PlayerController player) continue; - + _player = player; return _player; } - + GD.PrintErr("PlayerController not found in the scene tree."); return null; } diff --git a/objects/entities/brick_player.tscn b/objects/entities/brick_player.tscn index eceaf7f..0ff0d6c 100644 --- a/objects/entities/brick_player.tscn +++ b/objects/entities/brick_player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=58 format=3 uid="uid://bqi5s710xb1ju"] +[gd_scene load_steps=57 format=3 uid="uid://bqi5s710xb1ju"] [ext_resource type="Script" uid="uid://csel4s0e4g5uf" path="res://scripts/components/PlayerController.cs" id="1_yysbb"] [ext_resource type="Shader" uid="uid://bs4xvm4qkurpr" path="res://shaders/hit_flash.tres" id="2_lgb3u"] @@ -19,7 +19,6 @@ [ext_resource type="PackedScene" uid="uid://dre1vit1m4d2n" path="res://objects/movement_abilities/grid_movement_ability.tscn" id="8_xuhvf"] [ext_resource type="Script" uid="uid://dy78ak8eykw6e" path="res://scripts/components/FlipComponent.cs" id="9_yysbb"] [ext_resource type="Script" uid="uid://mnjg3p0aw1ow" path="res://scripts/components/CanPickUpComponent.cs" id="10_yysbb"] -[ext_resource type="Script" uid="uid://ccqb8kd5m0eh7" path="res://scripts/components/ScoreComponent.cs" id="11_o1ihh"] [ext_resource type="Script" uid="uid://dgb8bqcri7nsj" path="res://scripts/components/HealthComponent.cs" id="12_ur2y5"] [ext_resource type="Script" uid="uid://byw1legrv1ep2" path="res://scripts/components/PlayerDeathComponent.cs" id="13_7til7"] [ext_resource type="Script" uid="uid://cecelixl41t3j" path="res://scripts/components/InvulnerabilityComponent.cs" id="15_xuhvf"] @@ -175,9 +174,6 @@ shape = SubResource("RectangleShape2D_vad0t") [node name="CanPickUpComponent" type="Node" parent="."] script = ExtResource("10_yysbb") -[node name="ScoreComponent" type="Node" parent="."] -script = ExtResource("11_o1ihh") - [node name="HealthComponent" type="Node2D" parent="." node_paths=PackedStringArray("HurtSfx", "HealSfx")] script = ExtResource("12_ur2y5") HurtSfx = NodePath("../sfx_hurt") diff --git a/project.godot b/project.godot index d8a3535..69c2537 100644 --- a/project.godot +++ b/project.godot @@ -46,6 +46,8 @@ EventBus="*res://Autoloads/EventBus.cs" StatisticsManager="*res://Autoloads/StatisticsManager.cs" SpeedRunManager="res://Autoloads/SpeedRunManager.cs" GhostManager="res://objects/ghost_manager.tscn" +ScoreEventHandler="*res://scripts/Events/ScoreEventHandler.cs" +StatisticsEventHandler="*res://scripts/Events/StatisticsEventHandler.cs" [debug] diff --git a/scripts/Constants.cs b/scripts/Constants.cs new file mode 100644 index 0000000..6dab51b --- /dev/null +++ b/scripts/Constants.cs @@ -0,0 +1,23 @@ +namespace Mr.BrickAdventures; + +/// +/// Constants for autoload paths and other commonly used values. +/// +public static class Constants +{ + // Autoload paths + public const string EventBusPath = "/root/EventBus"; + public const string GameManagerPath = "/root/GameManager"; + public const string SaveSystemPath = "/root/SaveSystem"; + public const string SpeedRunManagerPath = "/root/SpeedRunManager"; + public const string GhostManagerPath = "/root/GhostManager"; + public const string AchievementManagerPath = "/root/AchievementManager"; + public const string StatisticsManagerPath = "/root/StatisticsManager"; + public const string SkillManagerPath = "/root/SkillManager"; + public const string FloatingTextManagerPath = "/root/FloatingTextManager"; + public const string UIManagerPath = "/root/UIManager"; + public const string ConsoleManagerPath = "/root/ConsoleManager"; + + // Group names + public const string CoinsGroup = "coins"; +} diff --git a/scripts/Constants.cs.uid b/scripts/Constants.cs.uid new file mode 100644 index 0000000..b78e143 --- /dev/null +++ b/scripts/Constants.cs.uid @@ -0,0 +1 @@ +uid://bn7o3n3bomvrd diff --git a/scripts/Events/GhostEventHandler.cs b/scripts/Events/GhostEventHandler.cs index 6a0aab2..3fe6e5e 100644 --- a/scripts/Events/GhostEventHandler.cs +++ b/scripts/Events/GhostEventHandler.cs @@ -1,4 +1,5 @@ using Godot; +using Mr.BrickAdventures; using Mr.BrickAdventures.Autoloads; namespace Mr.BrickAdventures.scripts.Events; @@ -10,11 +11,10 @@ public partial class GhostEventHandler : Node public override void _Ready() { - _ghostManager = GetNode("/root/GhostManager"); - var eventBus = GetNode("/root/EventBus"); - - eventBus.LevelStarted += OnLevelStarted; - eventBus.LevelCompleted += OnLevelCompleted; + _ghostManager = GetNode(Constants.GhostManagerPath); + + EventBus.Instance.LevelStarted += OnLevelStarted; + EventBus.Instance.LevelCompleted += OnLevelCompleted; } private void OnLevelStarted(int levelIndex, Node currentScene) @@ -23,7 +23,7 @@ public partial class GhostEventHandler : Node _ghostManager.StartRecording(levelIndex); _ghostManager.SpawnGhostPlayer(levelIndex, currentScene); } - + private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime) { _ghostManager.StopRecording(true, completionTime); diff --git a/scripts/Events/ScoreEventHandler.cs b/scripts/Events/ScoreEventHandler.cs new file mode 100644 index 0000000..0bb7b43 --- /dev/null +++ b/scripts/Events/ScoreEventHandler.cs @@ -0,0 +1,34 @@ +using Godot; +using Mr.BrickAdventures; +using Mr.BrickAdventures.Autoloads; + +namespace Mr.BrickAdventures.scripts.Events; + +/// +/// Handles coin collection events and updates the session state. +/// Replaces the manual signal wiring in ScoreComponent. +/// +public partial class ScoreEventHandler : Node +{ + private GameManager _gameManager; + + public override void _Ready() + { + _gameManager = GetNode(Constants.GameManagerPath); + + EventBus.Instance.CoinCollected += OnCoinCollected; + } + + public override void _ExitTree() + { + if (EventBus.Instance != null) + EventBus.Instance.CoinCollected -= OnCoinCollected; + } + + private void OnCoinCollected(int amount, Vector2 position) + { + var currentCoins = (int)_gameManager.CurrentSessionState["coins_collected"]; + _gameManager.CurrentSessionState["coins_collected"] = currentCoins + amount; + GD.Print($"ScoreEventHandler: Collected {amount} coins. Total session coins: {currentCoins + amount}"); + } +} diff --git a/scripts/Events/ScoreEventHandler.cs.uid b/scripts/Events/ScoreEventHandler.cs.uid new file mode 100644 index 0000000..c168916 --- /dev/null +++ b/scripts/Events/ScoreEventHandler.cs.uid @@ -0,0 +1 @@ +uid://cs4cfk7g5vh2v diff --git a/scripts/Events/SpeedRunEventHandler.cs b/scripts/Events/SpeedRunEventHandler.cs index 93acbd2..babe841 100644 --- a/scripts/Events/SpeedRunEventHandler.cs +++ b/scripts/Events/SpeedRunEventHandler.cs @@ -1,4 +1,5 @@ using Godot; +using Mr.BrickAdventures; using Mr.BrickAdventures.Autoloads; namespace Mr.BrickAdventures.scripts.Events; @@ -10,10 +11,9 @@ public partial class SpeedRunEventHandler : Node public override void _Ready() { - _speedRunManager = GetNode("/root/SpeedRunManager"); - var eventBus = GetNode("/root/EventBus"); - - eventBus.LevelCompleted += OnLevelCompleted; + _speedRunManager = GetNode(Constants.SpeedRunManagerPath); + + EventBus.Instance.LevelCompleted += OnLevelCompleted; } private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime) diff --git a/scripts/Events/StatisticsEventHandler.cs b/scripts/Events/StatisticsEventHandler.cs new file mode 100644 index 0000000..420d6b9 --- /dev/null +++ b/scripts/Events/StatisticsEventHandler.cs @@ -0,0 +1,62 @@ +using Godot; +using Mr.BrickAdventures; +using Mr.BrickAdventures.Autoloads; + +namespace Mr.BrickAdventures.scripts.Events; + +/// +/// Handles game events and updates statistics accordingly. +/// Listens to EventBus signals and increments relevant stats. +/// +public partial class StatisticsEventHandler : Node +{ + private StatisticsManager _statisticsManager; + + public override void _Ready() + { + _statisticsManager = GetNode(Constants.StatisticsManagerPath); + + // Subscribe to events + EventBus.Instance.CoinCollected += OnCoinCollected; + EventBus.Instance.EnemyDefeated += OnEnemyDefeated; + EventBus.Instance.PlayerDied += OnPlayerDied; + EventBus.Instance.LevelCompleted += OnLevelCompleted; + EventBus.Instance.ChildRescued += OnChildRescued; + } + + public override void _ExitTree() + { + if (EventBus.Instance == null) return; + + EventBus.Instance.CoinCollected -= OnCoinCollected; + EventBus.Instance.EnemyDefeated -= OnEnemyDefeated; + EventBus.Instance.PlayerDied -= OnPlayerDied; + EventBus.Instance.LevelCompleted -= OnLevelCompleted; + EventBus.Instance.ChildRescued -= OnChildRescued; + } + + private void OnCoinCollected(int amount, Vector2 position) + { + _statisticsManager.IncrementStat("coins_collected", amount); + } + + private void OnEnemyDefeated(Node enemy, Vector2 position) + { + _statisticsManager.IncrementStat("enemies_defeated"); + } + + private void OnPlayerDied(Vector2 position) + { + _statisticsManager.IncrementStat("deaths"); + } + + private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime) + { + _statisticsManager.IncrementStat("levels_completed"); + } + + private void OnChildRescued(Vector2 position) + { + _statisticsManager.IncrementStat("children_rescued"); + } +} diff --git a/scripts/Events/StatisticsEventHandler.cs.uid b/scripts/Events/StatisticsEventHandler.cs.uid new file mode 100644 index 0000000..11d654b --- /dev/null +++ b/scripts/Events/StatisticsEventHandler.cs.uid @@ -0,0 +1 @@ +uid://l68tjau3k6bw diff --git a/scripts/components/CollectableComponent.cs b/scripts/components/CollectableComponent.cs index 2a925af..418b33d 100644 --- a/scripts/components/CollectableComponent.cs +++ b/scripts/components/CollectableComponent.cs @@ -1,5 +1,6 @@ using System; using Godot; +using Mr.BrickAdventures; using Mr.BrickAdventures.Autoloads; using Mr.BrickAdventures.scripts.Resources; @@ -35,7 +36,7 @@ public partial class CollectableComponent : Node if (Owner.HasNode("FadeAwayComponent")) _hasFadeAway = true; - _floatingTextManager = GetNode("/root/FloatingTextManager"); + _floatingTextManager = GetNode(Constants.FloatingTextManagerPath); } private async void OnArea2DBodyEntered(Node2D body) @@ -53,12 +54,18 @@ public partial class CollectableComponent : Node { case CollectableType.Coin: _floatingTextManager?.ShowCoin((int)Data.Amount, ownerNode.GlobalPosition); + EventBus.EmitCoinCollected((int)Data.Amount, ownerNode.GlobalPosition); break; case CollectableType.Health: _floatingTextManager?.ShowMessage("Healed!", ownerNode.GlobalPosition); + EventBus.EmitItemCollected(Data.Type, Data.Amount, ownerNode.GlobalPosition); break; case CollectableType.Kid: _floatingTextManager?.ShowMessage("Rescued!", ownerNode.GlobalPosition); + EventBus.EmitChildRescued(ownerNode.GlobalPosition); + break; + default: + EventBus.EmitItemCollected(Data.Type, Data.Amount, ownerNode.GlobalPosition); break; } } diff --git a/scripts/components/EnemyDeathComponent.cs b/scripts/components/EnemyDeathComponent.cs index 330e66f..2e9722a 100644 --- a/scripts/components/EnemyDeathComponent.cs +++ b/scripts/components/EnemyDeathComponent.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Godot; +using Mr.BrickAdventures.Autoloads; namespace Mr.BrickAdventures.scripts.components; @@ -23,7 +24,7 @@ public partial class EnemyDeathComponent : Node GD.PushError("EnemyDeathComponent: Health is not set."); return; } - + Health.Death += OnDeath; } @@ -34,6 +35,12 @@ public partial class EnemyDeathComponent : Node private async Task Die() { + // Emit enemy defeated event for statistics and other systems + if (Owner is Node2D ownerNode) + { + EventBus.EmitEnemyDefeated(Owner, ownerNode.GlobalPosition); + } + CollisionShape.SetDisabled(true); var tween = CreateTween(); tween.TweenProperty(Owner, "scale", Vector2.Zero, TweenDuration); diff --git a/scripts/components/HealthComponent.cs b/scripts/components/HealthComponent.cs index 49484b0..685dd2a 100644 --- a/scripts/components/HealthComponent.cs +++ b/scripts/components/HealthComponent.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Godot; +using Mr.BrickAdventures; using Mr.BrickAdventures.Autoloads; namespace Mr.BrickAdventures.scripts.components; @@ -11,22 +12,22 @@ public partial class HealthComponent : Node2D [Export] public float MaxHealth { get; set; } = 1.0f; [Export] public AudioStreamPlayer2D HurtSfx { get; set; } [Export] public AudioStreamPlayer2D HealSfx { get; set; } - + [Signal] public delegate void HealthChangedEventHandler(float delta, float totalHealth); [Signal] public delegate void DeathEventHandler(); - + private FloatingTextManager _floatingTextManager; public override void _Ready() { - _floatingTextManager = GetNode("/root/FloatingTextManager"); + _floatingTextManager = GetNode(Constants.FloatingTextManagerPath); } - + public void SetHealth(float newValue) { _ = ApplyHealthChange(newValue); } - + public void IncreaseHealth(float delta) { _ = ApplyHealthChange(Health + delta); @@ -46,7 +47,7 @@ public partial class HealthComponent : Node2D if (delta == 0.0f) return; - + if (delta < 0.0f) _floatingTextManager?.ShowDamage(Mathf.Abs(delta), GlobalPosition); else @@ -64,16 +65,27 @@ public partial class HealthComponent : Node2D await HurtSfx.ToSignal(HurtSfx, AudioStreamPlayer2D.SignalName.Finished); } } - + Health = newHealth; if (Health <= 0f) { EmitSignalDeath(); + // Emit global event if this is the player + if (Owner is PlayerController) + EventBus.EmitPlayerDied(GlobalPosition); } else { EmitSignalHealthChanged(delta, Health); + // Emit global events if this is the player + if (Owner is PlayerController) + { + if (delta < 0f) + EventBus.EmitPlayerDamaged(Mathf.Abs(delta), Health, GlobalPosition); + else + EventBus.EmitPlayerHealed(delta, Health, GlobalPosition); + } } } } \ No newline at end of file