فهرست منبع

Add HUD and Player Death

CartBlanche 1 ماه پیش
والد
کامیت
aaf891c553
4فایلهای تغییر یافته به همراه456 افزوده شده و 138 حذف شده
  1. 202 36
      Shooter/Core/Game.cs
  2. 11 25
      Shooter/Gameplay/Components/EnemyAI.cs
  3. 208 0
      Shooter/Gameplay/Components/GameFlowManager.cs
  4. 35 77
      Shooter/Gameplay/Components/HUD.cs

+ 202 - 36
Shooter/Core/Game.cs

@@ -29,6 +29,8 @@ public partial class ShooterGame : Game
     private SpriteBatch? _spriteBatch;
     private SceneManager? _sceneManager;
     private Gameplay.Systems.PickupSystem? _pickupSystem;
+    private Gameplay.Components.GameFlowManager? _gameFlowManager;
+    private Texture2D? _pauseOverlayTexture; // Reusable 1x1 texture for pause overlay
 
     // Hit marker state
     private bool _showHitMarker = false;
@@ -107,6 +109,7 @@ public partial class ShooterGame : Game
 
         // Create scene manager
         _sceneManager = new SceneManager();
+        ServiceLocator.Register<SceneManager>(_sceneManager);
 
         // TODO: Register custom component types
         // _sceneManager.RegisterComponentType<PlayerController>("PlayerController");
@@ -167,6 +170,9 @@ public partial class ShooterGame : Game
         // Add HUD component to player
         var hud = playerEntity.AddComponent<Gameplay.Components.HUD>();
 
+        // Add GameFlowManager to player (for game state management)
+        _gameFlowManager = playerEntity.AddComponent<Gameplay.Components.GameFlowManager>();
+
         scene.AddEntity(playerEntity);
 
         // Initialize camera AFTER entity is added and components are initialized
@@ -184,27 +190,8 @@ public partial class ShooterGame : Game
 
         // Player setup complete
 
-        // Create Ground Plane (large flat box)
-        var groundEntity = new Core.Entities.Entity("Ground");
-        var groundTransform = groundEntity.AddComponent<Core.Components.Transform3D>();
-        groundTransform.Position = new System.Numerics.Vector3(0, -1, 0);
-        groundTransform.LocalScale = new System.Numerics.Vector3(20, 0.5f, 20); // Wide and flat
-        var groundRenderer = groundEntity.AddComponent<Core.Components.MeshRenderer>();
-        groundRenderer.SetCube(1.0f, new System.Numerics.Vector4(0.3f, 0.5f, 0.3f, 1.0f)); // Green ground
-        var groundRigid = groundEntity.AddComponent<Core.Components.Rigidbody>();
-        groundRigid.BodyType = Core.Plugins.Physics.BodyType.Static;
-        groundRigid.Shape = new Core.Plugins.Physics.BoxShape(20, 0.5f, 20);
-        scene.AddEntity(groundEntity);
-
-        // Create enemy entities for AI testing (bright red, taller/skinnier)
-        // Position them right next to the weapon for visibility testing
-        CreateEnemy(scene, playerEntity, "Enemy1", new System.Numerics.Vector3(-1, 1.5f, 2), 1.0f, 0.0f, 0.0f);
-        CreateEnemy(scene, playerEntity, "Enemy2", new System.Numerics.Vector3(1, 1.5f, 2), 1.0f, 0.0f, 0.0f);
-
-        // Create pickups for testing
-        CreateHealthPickup(scene, "HealthPickup1", new System.Numerics.Vector3(-5, 1.5f, 0), 25f);
-        CreateHealthPickup(scene, "HealthPickup2", new System.Numerics.Vector3(5, 1.5f, 0), 50f);
-        CreateAmmoPickup(scene, "AmmoPickup1", new System.Numerics.Vector3(0, 1.5f, -5), 30);
+        // Build multi-room level (inspired by Unity's Room_01, Room_02, Room_03 structure)
+        BuildMultiRoomLevel(scene, playerEntity);
 
         // Create pickup system
         _pickupSystem = new Gameplay.Systems.PickupSystem(playerEntity);
@@ -218,6 +205,124 @@ public partial class ShooterGame : Game
         activeSceneField?.SetValue(_sceneManager, scene);
     }
 
+
+    /// <summary>
+    /// Build a multi-room dungeon level inspired by Unity's Room_01, Room_02, Room_03 structure.
+    /// Creates 3 connected rooms with walls, floors, doorways, enemies, and pickups.
+    /// </summary>
+    private void BuildMultiRoomLevel(Core.Scenes.Scene scene, Core.Entities.Entity playerEntity)
+    {
+        // Colors for visual distinction
+        var floorColor = new System.Numerics.Vector4(0.3f, 0.4f, 0.5f, 1.0f);   // Bluish-gray floor
+        var wallColor = new System.Numerics.Vector4(0.5f, 0.3f, 0.3f, 1.0f);    // Reddish-brown walls
+        var doorFrameColor = new System.Numerics.Vector4(0.4f, 0.4f, 0.4f, 1.0f); // Dark gray doorframes
+
+        // ROOM 01 - Starting room (6x6 units, similar to Unity's Floor_6x6)
+        // Player starts here at (0, 2, 10)
+        CreateFloor(scene, "Room01_Floor", new System.Numerics.Vector3(0, -1, 0), 6f, 6f, floorColor);
+
+        // Room 01 walls (4 sides, with opening on north side)
+        CreateWall(scene, "Room01_Wall_South", new System.Numerics.Vector3(0, 1, 3), 6f, 3f, 0.5f, wallColor); // South wall
+        CreateWall(scene, "Room01_Wall_West", new System.Numerics.Vector3(-3, 1, 0), 0.5f, 3f, 6f, wallColor); // West wall
+        CreateWall(scene, "Room01_Wall_East", new System.Numerics.Vector3(3, 1, 0), 0.5f, 3f, 6f, wallColor);  // East wall
+        // North wall split into two pieces with doorway in center
+        CreateWall(scene, "Room01_Wall_North_Left", new System.Numerics.Vector3(-1.75f, 1, -3), 2.5f, 3f, 0.5f, wallColor);
+        CreateWall(scene, "Room01_Wall_North_Right", new System.Numerics.Vector3(1.75f, 1, -3), 2.5f, 3f, 0.5f, wallColor);
+
+        // Room 01 enemies (2 starting enemies close to player)
+        CreateEnemy(scene, playerEntity, "Room01_Enemy1", new System.Numerics.Vector3(-2, 1.5f, 2), 1.0f, 0.0f, 0.0f);
+        CreateEnemy(scene, playerEntity, "Room01_Enemy2", new System.Numerics.Vector3(2, 1.5f, 2), 1.0f, 0.0f, 0.0f);
+
+        // ROOM 02 - Medium room (9x9 units, connected via Opening_01)
+        // Center at (0, 0, -12) so it's north of Room 01
+        CreateFloor(scene, "Room02_Floor", new System.Numerics.Vector3(0, -1, -12), 9f, 9f, floorColor);
+
+        // Room 02 walls
+        CreateWall(scene, "Room02_Wall_South_Left", new System.Numerics.Vector3(-3f, 1, -7.5f), 3f, 3f, 0.5f, wallColor); // South wall left
+        CreateWall(scene, "Room02_Wall_South_Right", new System.Numerics.Vector3(3f, 1, -7.5f), 3f, 3f, 0.5f, wallColor); // South wall right
+        CreateWall(scene, "Room02_Wall_West", new System.Numerics.Vector3(-4.5f, 1, -12), 0.5f, 3f, 9f, wallColor); // West wall
+        CreateWall(scene, "Room02_Wall_East", new System.Numerics.Vector3(4.5f, 1, -12), 0.5f, 3f, 9f, wallColor); // East wall
+        // North wall with opening on east side
+        CreateWall(scene, "Room02_Wall_North_Left", new System.Numerics.Vector3(-2.5f, 1, -16.5f), 4f, 3f, 0.5f, wallColor);
+        CreateWall(scene, "Room02_Wall_North_Center", new System.Numerics.Vector3(2f, 1, -16.5f), 3f, 3f, 0.5f, wallColor);
+
+        // Room 02 enemies (3 enemies spread across the room)
+        CreateEnemy(scene, playerEntity, "Room02_Enemy1", new System.Numerics.Vector3(-2, 1.5f, -10), 1.0f, 0.5f, 0.0f); // Orange
+        CreateEnemy(scene, playerEntity, "Room02_Enemy2", new System.Numerics.Vector3(2, 1.5f, -14), 1.0f, 0.5f, 0.0f);
+        CreateEnemy(scene, playerEntity, "Room02_Enemy3", new System.Numerics.Vector3(0, 1.5f, -12), 1.0f, 0.5f, 0.0f);
+
+        // Room 02 pickups
+        CreateHealthPickup(scene, "Room02_HealthPickup", new System.Numerics.Vector3(-3, 1.5f, -12), 25f);
+
+        // ROOM 03 - Large room (15x15 units, final challenge room)
+        // Center at (12, 0, -28) so it's northeast of Room 02
+        CreateFloor(scene, "Room03_Floor", new System.Numerics.Vector3(12, -1, -28), 15f, 15f, floorColor);
+
+        // Room 03 walls (fully enclosed for final battle)
+        CreateWall(scene, "Room03_Wall_South", new System.Numerics.Vector3(12, 1, -20.5f), 15f, 3f, 0.5f, wallColor);
+        CreateWall(scene, "Room03_Wall_North", new System.Numerics.Vector3(12, 1, -35.5f), 15f, 3f, 0.5f, wallColor);
+        CreateWall(scene, "Room03_Wall_West_Upper", new System.Numerics.Vector3(4.5f, 1, -30), 0.5f, 3f, 7f, wallColor);
+        CreateWall(scene, "Room03_Wall_West_Lower", new System.Numerics.Vector3(4.5f, 1, -24), 0.5f, 3f, 6f, wallColor);
+        CreateWall(scene, "Room03_Wall_East", new System.Numerics.Vector3(19.5f, 1, -28), 0.5f, 3f, 15f, wallColor);
+
+        // Room 03 enemies (5 enemies - harder final room)
+        CreateEnemy(scene, playerEntity, "Room03_Enemy1", new System.Numerics.Vector3(8, 1.5f, -24), 1.0f, 0.0f, 0.0f);
+        CreateEnemy(scene, playerEntity, "Room03_Enemy2", new System.Numerics.Vector3(16, 1.5f, -24), 1.0f, 0.0f, 0.0f);
+        CreateEnemy(scene, playerEntity, "Room03_Enemy3", new System.Numerics.Vector3(8, 1.5f, -32), 1.0f, 0.0f, 0.0f);
+        CreateEnemy(scene, playerEntity, "Room03_Enemy4", new System.Numerics.Vector3(16, 1.5f, -32), 1.0f, 0.0f, 0.0f);
+        CreateEnemy(scene, playerEntity, "Room03_Enemy5", new System.Numerics.Vector3(12, 1.5f, -28), 1.0f, 0.0f, 0.0f); // Center enemy
+
+        // Room 03 pickups (health and ammo for final room)
+        CreateHealthPickup(scene, "Room03_HealthPickup1", new System.Numerics.Vector3(10, 1.5f, -26), 50f);
+        CreateHealthPickup(scene, "Room03_HealthPickup2", new System.Numerics.Vector3(14, 1.5f, -30), 50f);
+        CreateAmmoPickup(scene, "Room03_AmmoPickup", new System.Numerics.Vector3(12, 1.5f, -34), 50);
+
+        Console.WriteLine("[Game] Multi-room level created: Room 01 (6x6), Room 02 (9x9), Room 03 (15x15)");
+        Console.WriteLine("[Game] Total enemies: 10 | Total health pickups: 3 | Total ammo pickups: 1");
+    }
+
+    /// <summary>
+    /// Create a floor plane for a room
+    /// </summary>
+    private void CreateFloor(Core.Scenes.Scene scene, string name, System.Numerics.Vector3 position,
+        float width, float depth, System.Numerics.Vector4 color)
+    {
+        var entity = new Core.Entities.Entity(name);
+        var transform = entity.AddComponent<Core.Components.Transform3D>();
+        transform.Position = position;
+        transform.LocalScale = new System.Numerics.Vector3(width, 0.5f, depth);
+
+        var renderer = entity.AddComponent<Core.Components.MeshRenderer>();
+        renderer.SetCube(1.0f, color);
+
+        var rigidbody = entity.AddComponent<Core.Components.Rigidbody>();
+        rigidbody.BodyType = Core.Plugins.Physics.BodyType.Static;
+        rigidbody.Shape = new Core.Plugins.Physics.BoxShape(width, 0.5f, depth);
+
+        scene.AddEntity(entity);
+    }
+
+    /// <summary>
+    /// Create a wall section
+    /// </summary>
+    private void CreateWall(Core.Scenes.Scene scene, string name, System.Numerics.Vector3 position,
+        float width, float height, float depth, System.Numerics.Vector4 color)
+    {
+        var entity = new Core.Entities.Entity(name);
+        var transform = entity.AddComponent<Core.Components.Transform3D>();
+        transform.Position = position;
+        transform.LocalScale = new System.Numerics.Vector3(width, height, depth);
+
+        var renderer = entity.AddComponent<Core.Components.MeshRenderer>();
+        renderer.SetCube(1.0f, color);
+
+        var rigidbody = entity.AddComponent<Core.Components.Rigidbody>();
+        rigidbody.BodyType = Core.Plugins.Physics.BodyType.Static;
+        rigidbody.Shape = new Core.Plugins.Physics.BoxShape(width, height, depth);
+
+        scene.AddEntity(entity);
+    }
+
     /// <summary>
     /// Helper to create a cube entity.
     /// </summary>
@@ -400,6 +505,10 @@ public partial class ShooterGame : Game
     {
         _spriteBatch = new SpriteBatch(GraphicsDevice);
 
+        // Create 1x1 white texture for pause overlay (reusable)
+        _pauseOverlayTexture = new Texture2D(GraphicsDevice, 1, 1);
+        _pauseOverlayTexture.SetData(new[] { Color.White });
+
         // Load SpriteFont for HUD
         SpriteFont hudFont = Content.Load<SpriteFont>("font");
 
@@ -443,6 +552,13 @@ public partial class ShooterGame : Game
                 Console.WriteLine("[Game] HUD content loaded");
             }
 
+            // Load GameFlowManager content (for fade transitions and "You Died" message)
+            if (_gameFlowManager != null && _spriteBatch != null)
+            {
+                _gameFlowManager.LoadContent(GraphicsDevice, _spriteBatch, hudFont);
+                Console.WriteLine("[Game] GameFlowManager content loaded");
+            }
+
             // Load and attach weapon model to player's view
             LoadWeaponModel(playerEntity);
 
@@ -741,20 +857,34 @@ public partial class ShooterGame : Game
         timeService.Update(gameTime);
         inputService.Update();
 
-        // Skip game updates when paused
+        // Skip game updates when paused or during scene transition
         if (_isPaused)
         {
             base.Update(gameTime);
             return;
         }
 
+        // Skip scene updates during fade transitions (but allow GameFlowManager to update for fade animation)
+        if (_gameFlowManager != null && _gameFlowManager.IsTransitioning)
+        {
+            // Only update the GameFlowManager during transitions
+            var coreGameTime = new Core.Components.GameTime(
+                gameTime.TotalGameTime,
+                gameTime.ElapsedGameTime
+            );
+            _gameFlowManager.Update(coreGameTime);
+
+            base.Update(gameTime);
+            return;
+        }
+
         // Update active scene
-        var coreGameTime = new Core.Components.GameTime(
+        var coreGameTime2 = new Core.Components.GameTime(
             gameTime.TotalGameTime,
             gameTime.ElapsedGameTime
         );
 
-        _sceneManager?.Update(coreGameTime);
+        _sceneManager?.Update(coreGameTime2);
 
         // Update pickup system
         if (_pickupSystem != null && _sceneManager?.ActiveScene != null)
@@ -762,7 +892,7 @@ public partial class ShooterGame : Game
             var pickups = _sceneManager.ActiveScene.Entities
                 .SelectMany(e => e.GetComponents<Gameplay.Components.Pickup>())
                 .Where(p => !p.IsCollected);
-            _pickupSystem.Update(coreGameTime, pickups);
+            _pickupSystem.Update(coreGameTime2, pickups);
         }
 
         // Update hit marker timer
@@ -917,6 +1047,12 @@ public partial class ShooterGame : Game
 
         _sceneManager?.Draw(coreGameTime);
 
+        // Draw fade overlay (MUST be last so it's on top of everything)
+        if (_gameFlowManager != null)
+        {
+            _gameFlowManager.Draw(coreGameTime);
+        }
+
         base.Draw(gameTime);
     }
 
@@ -1046,30 +1182,60 @@ public partial class ShooterGame : Game
     /// </summary>
     private void DrawPauseMenu()
     {
-        if (_spriteBatch == null)
+        if (_spriteBatch == null || _pauseOverlayTexture == null)
             return;
 
-        _spriteBatch.Begin();
+        // Use AlphaBlend to ensure proper transparency (not accumulative)
+        _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
 
         int screenWidth = GraphicsDevice.Viewport.Width;
         int screenHeight = GraphicsDevice.Viewport.Height;
 
-        // Draw semi-transparent black overlay
-        var overlayTexture = new Texture2D(GraphicsDevice, 1, 1);
-        overlayTexture.SetData(new[] { Color.Black });
+        // Draw semi-transparent black overlay using persistent texture
         _spriteBatch.Draw(
-            overlayTexture,
+            _pauseOverlayTexture,
             new Rectangle(0, 0, screenWidth, screenHeight),
             Color.Black * 0.7f // 70% opacity
         );
 
-        // Note: For a full pause menu with text, we'd need to load a SpriteFont
-        // For now, we just show the dark overlay as visual feedback that the game is paused
-        // The user can see the mouse cursor appears when paused
+        // Draw pause menu text
+        try
+        {
+            var font = Content.Load<SpriteFont>("font");
+            string pauseTitle = "PAUSED";
+            string resumeText = "Press ESC to Resume";
+
+            var titleSize = font.MeasureString(pauseTitle);
+            var resumeSize = font.MeasureString(resumeText);
+
+            // Draw centered title
+            _spriteBatch.DrawString(
+                font,
+                pauseTitle,
+                new Microsoft.Xna.Framework.Vector2(
+                    screenWidth / 2 - titleSize.X / 2,
+                    screenHeight / 2 - 30
+                ),
+                Color.White
+            );
 
-        _spriteBatch.End();
+            // Draw resume instruction
+            _spriteBatch.DrawString(
+                font,
+                resumeText,
+                new Microsoft.Xna.Framework.Vector2(
+                    screenWidth / 2 - resumeSize.X / 2,
+                    screenHeight / 2 + 20
+                ),
+                Color.LightGray
+            );
+        }
+        catch
+        {
+            // Font not loaded, skip text rendering
+        }
 
-        overlayTexture.Dispose();
+        _spriteBatch.End();
     }
 
     /// <summary>

+ 11 - 25
Shooter/Gameplay/Components/EnemyAI.cs

@@ -187,12 +187,6 @@ public class EnemyAI : EntityComponent
         {
             _health.OnDeath += OnDeath;
         }
-
-        // Reduced logging - only show critical warnings
-        if (_enemyController == null)
-        {
-            Console.WriteLine($"[EnemyAI] WARNING: {Owner?.Name} has no EnemyController");
-        }
     }
 
     /// <summary>
@@ -202,24 +196,24 @@ public class EnemyAI : EntityComponent
     {
         base.Update(gameTime);
 
+        // Don't update AI during scene transitions (death, reload, etc.)
+        if (_target != null)
+        {
+            var gameFlowManager = _target.GetComponent<GameFlowManager>();
+            if (gameFlowManager != null && gameFlowManager.IsTransitioning)
+            {
+                return; // Skip AI updates during fade transitions
+            }
+        }
+
         // Lazy-load target transform if we have a target but no transform cached
         if (_target != null && _targetTransform == null)
         {
             _targetTransform = _target.GetComponent<Transform3D>();
-            if (_targetTransform != null)
-            {
-                Console.WriteLine($"[EnemyAI] {Owner?.Name} successfully loaded target transform");
-            }
         }
 
         if (_transform == null || _target == null || _targetTransform == null)
         {
-            // DEBUG: Log why update is skipped (only once per second to avoid spam)
-            if (_stateTimer == 0f || _stateTimer > 1f)
-            {
-                Console.WriteLine($"[EnemyAI] {Owner?.Name} Update skipped - Transform:{_transform != null}, Target:{_target != null}, TargetTransform:{_targetTransform != null}");
-                _stateTimer = 0.01f; // Reset to avoid immediate re-log
-            }
             return;
         }
 
@@ -262,13 +256,6 @@ public class EnemyAI : EntityComponent
 
         float distanceToTarget = Vector3.Distance(_transform.Position, _targetTransform.Position);
 
-        // DEBUG: Log detection checks every 2 seconds
-        if (_stateTimer > 2f)
-        {
-            Console.WriteLine($"[EnemyAI] {Owner?.Name} Idle - Distance:{distanceToTarget:F1}, LOS:{_hasLineOfSight}, Range:{_detectionRange}");
-            _stateTimer = 0f;
-        }
-
         // If player is in range and visible, start chasing
         if (distanceToTarget <= _detectionRange && _hasLineOfSight)
         {
@@ -516,8 +503,7 @@ public class EnemyAI : EntityComponent
     /// </summary>
     private void OnStateEnter(AIState state)
     {
-        // DEBUG: Track AI state changes
-        Console.WriteLine($"[EnemyAI] {Owner?.Name} -> {state}");
+        // Could add state entry logic here
     }
 
     /// <summary>

+ 208 - 0
Shooter/Gameplay/Components/GameFlowManager.cs

@@ -0,0 +1,208 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Shooter.Core.Components;
+using Shooter.Core.Scenes;
+using Shooter.Core.Services;
+using Shooter.Gameplay.Systems;
+
+namespace Shooter.Gameplay.Components
+{
+    /// <summary>
+    /// Manages game flow: win/loss conditions, scene transitions, and fade effects.
+    /// Similar to Unity's GameFlowManager in the FPS Microgame.
+    ///
+    /// USAGE:
+    /// - Attach to a persistent entity in the scene (or player)
+    /// - Listens for player death event
+    /// - Handles fade transitions and scene reloading
+    /// </summary>
+    public class GameFlowManager : EntityComponent
+    {
+        // Configuration
+        private float _fadeOutDuration = 1.5f;  // Time to fade to black (seconds)
+        private float _displayMessageDuration = 10.0f; // Time to show "You Died" before exiting
+
+        // State
+        private bool _isTransitioning = false;
+        private float _fadeAlpha = 0f;  // 0 = transparent, 1 = fully black
+        private float _transitionTimer = 0f;
+        private TransitionState _transitionState = TransitionState.None;
+
+        // References
+        private GraphicsDevice? _graphicsDevice;
+        private SpriteBatch? _spriteBatch;
+        private Texture2D? _pixel;  // 1x1 white texture for fade overlay
+        private SpriteFont? _font;  // Font for "You Died" message
+
+        private enum TransitionState
+        {
+            None,
+            FadingOut,      // Fading to black
+            ShowingMessage  // Showing "You Died" message before exit
+        }
+
+        public override void Initialize()
+        {
+            base.Initialize();
+
+            // Subscribe to health component on this entity (GameFlowManager should be attached to the player)
+            if (Owner != null)
+            {
+                var health = Owner.GetComponent<Health>();
+                if (health != null)
+                {
+                    health.OnDeath += OnPlayerDeath;
+                    Console.WriteLine("[GameFlowManager] Subscribed to player death event");
+                }
+                else
+                {
+                    Console.WriteLine("[GameFlowManager] WARNING: Owner entity has no Health component!");
+                }
+            }
+            else
+            {
+                Console.WriteLine("[GameFlowManager] WARNING: GameFlowManager has no owner entity!");
+            }
+        }
+
+        /// <summary>
+        /// Load content for fade overlay and "You Died" message.
+        /// Call this from Game.LoadContent() after SpriteBatch is created.
+        /// </summary>
+        public void LoadContent(GraphicsDevice graphicsDevice, SpriteBatch spriteBatch, SpriteFont font)
+        {
+            _graphicsDevice = graphicsDevice;
+            _spriteBatch = spriteBatch;
+            _font = font;
+
+            // Create 1x1 white pixel for fade overlay
+            _pixel = new Texture2D(graphicsDevice, 1, 1);
+            _pixel.SetData(new[] { Color.White });
+
+            Console.WriteLine("[GameFlowManager] Content loaded");
+        }
+
+        /// <summary>
+        /// Handle player death - start fade to "You Died" screen
+        /// </summary>
+        private void OnPlayerDeath(DamageInfo killingBlow)
+        {
+            if (_isTransitioning)
+                return; // Already transitioning
+
+            Console.WriteLine("[GameFlowManager] Player died! Starting death sequence...");
+
+            // Start fade out transition
+            _isTransitioning = true;
+            _transitionState = TransitionState.FadingOut;
+            _transitionTimer = 0f;
+            _fadeAlpha = 0f;
+
+            // Make player invincible during transition
+            if (Owner != null)
+            {
+                var health = Owner.GetComponent<Health>();
+                if (health != null)
+                {
+                    health.CanTakeDamage = false;
+                    Console.WriteLine("[GameFlowManager] Player made invincible during transition");
+                }
+            }
+        }
+
+        public override void Update(Core.Components.GameTime gameTime)
+        {
+            base.Update(gameTime);
+
+            if (!_isTransitioning)
+                return;
+
+            float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
+            _transitionTimer += deltaTime;
+
+            switch (_transitionState)
+            {
+                case TransitionState.FadingOut:
+                    UpdateFadeOut();
+                    break;
+
+                case TransitionState.ShowingMessage:
+                    UpdateShowingMessage();
+                    break;
+            }
+        }
+
+        private void UpdateFadeOut()
+        {
+            // Increase fade alpha from 0 to 1
+            _fadeAlpha = Math.Min(1f, _transitionTimer / _fadeOutDuration);
+
+            if (_fadeAlpha >= 1f)
+            {
+                // Fade out complete, start showing "You Died" message
+                _transitionState = TransitionState.ShowingMessage;
+                _transitionTimer = 0f;
+                Console.WriteLine("[GameFlowManager] Fade out complete, showing death message...");
+            }
+        }
+
+        private void UpdateShowingMessage()
+        {
+            if (_transitionTimer >= _displayMessageDuration)
+            {
+                // Message display complete, exit the game
+                Console.WriteLine("[GameFlowManager] Exiting game...");
+                Environment.Exit(0);
+            }
+        }
+
+        /// <summary>
+        /// Draw the fade overlay and "You Died" message.
+        /// Call this from your Draw() method AFTER everything else (so it's on top).
+        /// </summary>
+        public override void Draw(Core.Components.GameTime gameTime)
+        {
+            base.Draw(gameTime);
+
+            if (_spriteBatch == null || _pixel == null || _graphicsDevice == null)
+                return;
+
+            if (_fadeAlpha <= 0f)
+                return; // Nothing to draw
+
+            _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
+
+            var viewport = _graphicsDevice.Viewport;
+            var screenRect = new Rectangle(0, 0, viewport.Width, viewport.Height);
+            var fadeColor = Color.Black * _fadeAlpha;
+
+            // Draw black overlay
+            _spriteBatch.Draw(_pixel, screenRect, fadeColor);
+
+            // Draw "You Died" message if fully faded and in message state
+            if (_fadeAlpha >= 1f && _transitionState == TransitionState.ShowingMessage && _font != null)
+            {
+                string message = "You Died";
+                Vector2 textSize = _font.MeasureString(message);
+                Vector2 textPos = new Vector2(
+                    (viewport.Width - textSize.X) / 2,
+                    (viewport.Height - textSize.Y) / 2
+                );
+
+                // Draw with slight shadow for readability
+                _spriteBatch.DrawString(_font, message, textPos + new Vector2(2, 2), new Color(0, 0, 0, 255));
+                _spriteBatch.DrawString(_font, message, textPos, Color.White);
+            }
+
+            _spriteBatch.End();
+        }
+
+        /// <summary>
+        /// Get whether a transition is currently in progress
+        /// </summary>
+        public bool IsTransitioning => _isTransitioning;
+    }
+}

+ 35 - 77
Shooter/Gameplay/Components/HUD.cs

@@ -25,14 +25,12 @@ public class HUD : EntityComponent
     private GraphicsDevice? _graphicsDevice;
 
     // HUD element positions and sizes
-    // Move HUD to top left for debug visibility
-    private Vector2 _healthBarSize = new Vector2(200, 30);
-    private Vector2 _ammoBarSize = new Vector2(200, 18);
-    private Vector2 _weaponBarSize = new Vector2(200, 18);
+    // Match Unity layout: health bottom-left, ammo bottom-right
+    private Vector2 _healthBarSize = new Vector2(250, 35);  // Larger to match Unity
+    private Vector2 _ammoBarSize = new Vector2(150, 30);    // Compact ammo display
     private int _margin = 20;
-    private Vector2 _healthBarPosition = new Vector2(20, 20);
-    private Vector2 _ammoBarPosition = new Vector2(20, 60);
-    private Vector2 _weaponBarPosition = new Vector2(20, 85);
+    private Vector2 _healthBarPosition = new Vector2(20, 20);  // Placeholder, set in LoadContent
+    private Vector2 _ammoBarPosition = new Vector2(20, 60);    // Placeholder, set in LoadContent
 
     // References to player components
     private Health? _playerHealth;
@@ -81,12 +79,15 @@ public class HUD : EntityComponent
         _pixel = new Texture2D(graphicsDevice, 1, 1);
         _pixel.SetData(new[] { Color.White });
 
-        // Position bars at lower right after graphics device is available
+        // Position bars to match Unity layout
         int screenW = _graphicsDevice.Viewport.Width;
         int screenH = _graphicsDevice.Viewport.Height;
-        _healthBarPosition = new Vector2(screenW - _healthBarSize.X - _margin, screenH - _healthBarSize.Y - _ammoBarSize.Y - _weaponBarSize.Y - _margin);
-        _ammoBarPosition = new Vector2(screenW - _ammoBarSize.X - _margin, screenH - _ammoBarSize.Y - _weaponBarSize.Y - _margin);
-        _weaponBarPosition = new Vector2(screenW - _weaponBarSize.X - _margin, screenH - _weaponBarSize.Y - _margin);
+
+        // Health bar: bottom-left corner
+        _healthBarPosition = new Vector2(_margin, screenH - _healthBarSize.Y - _margin);
+
+        // Ammo bar: bottom-right corner
+        _ammoBarPosition = new Vector2(screenW - _ammoBarSize.X - _margin, screenH - _ammoBarSize.Y - _margin);
 
         Console.WriteLine("[HUD] Content loaded");
     }
@@ -104,37 +105,14 @@ public class HUD : EntityComponent
         _spriteBatch.Begin();
 
         DrawHealthBar();
-        
-        // Debug: Check weapon status
-        if (_weaponController == null)
-        {
-            // Draw debug text
-            if (_font != null)
-            {
-                _spriteBatch.DrawString(_font, "NO WEAPON CONTROLLER", new Vector2(20, 100), Color.Red);
-            }
-        }
-        else if (_weaponController.CurrentWeapon == null)
-        {
-            // Draw debug text
-            if (_font != null)
-            {
-                _spriteBatch.DrawString(_font, $"WEAPON CONTROLLER OK, NO CURRENT WEAPON (Index: {_weaponController.CurrentWeaponIndex})", new Vector2(20, 100), Color.Yellow);
-            }
-        }
-        else
-        {
-            DrawAmmoBar();
-            DrawWeaponBar();
-        }
+        DrawAmmoBar();
 
         _spriteBatch.End();
     }
 
     /// <summary>
-    /// Draw the ammo counter
+    /// Draw the ammo counter (Unity style - text based with background)
     /// </summary>
-    // Rectangle-based ammo bar
     private void DrawAmmoBar()
     {
         if (_spriteBatch == null || _pixel == null || _weaponController == null)
@@ -144,57 +122,37 @@ public class HUD : EntityComponent
         if (weapon == null)
             return;
 
-        float ammoPercent = weapon.CurrentAmmoInMag / (float)Math.Max(weapon.MagazineSize, 1);
-        ammoPercent = Math.Clamp(ammoPercent, 0f, 1f);
-
         Rectangle backgroundRect = new Rectangle((int)_ammoBarPosition.X, (int)_ammoBarPosition.Y, (int)_ammoBarSize.X, (int)_ammoBarSize.Y);
-        Rectangle fillRect = new Rectangle((int)_ammoBarPosition.X + 2, (int)_ammoBarPosition.Y + 2, (int)((_ammoBarSize.X - 4) * ammoPercent), (int)_ammoBarSize.Y - 4);
-        Rectangle borderRect = new Rectangle((int)_ammoBarPosition.X, (int)_ammoBarPosition.Y, (int)_ammoBarSize.X, (int)_ammoBarSize.Y);
-
-        Color ammoBarBackground = new Color(30, 30, 60, 200);
-        Color ammoBarFill = weapon.IsReloading ? new Color(255, 220, 40, 255) : (weapon.CurrentAmmoInMag == 0 ? new Color(220, 50, 50, 255) : new Color(40, 180, 255, 255));
+        Color ammoBarBackground = new Color(40, 40, 40, 200);
         Color ammoBarBorder = new Color(255, 255, 255, 255);
 
         // Draw background
         _spriteBatch.Draw(_pixel, backgroundRect, ammoBarBackground);
-        // Draw fill
-        _spriteBatch.Draw(_pixel, fillRect, ammoBarFill);
         // Draw border
-        DrawRectangleBorder(borderRect, 2, ammoBarBorder);
-    }
-
-    /// <summary>
-    /// Draw the weapon name
-    /// </summary>
-    // Rectangle-based weapon bar
-    private void DrawWeaponBar()
-    {
-        if (_spriteBatch == null || _pixel == null || _weaponController == null)
-            return;
-
-        var weapon = _weaponController.CurrentWeapon;
-        if (weapon == null)
-            return;
+        DrawRectangleBorder(backgroundRect, 2, ammoBarBorder);
 
-        Rectangle backgroundRect = new Rectangle((int)_weaponBarPosition.X, (int)_weaponBarPosition.Y, (int)_weaponBarSize.X, (int)_weaponBarSize.Y);
-        Rectangle borderRect = new Rectangle((int)_weaponBarPosition.X, (int)_weaponBarPosition.Y, (int)_weaponBarSize.X, (int)_weaponBarSize.Y);
+        // Draw ammo text (similar to Unity: "32 / 90" format)
+        if (_font != null)
+        {
+            string ammoText = weapon.IsReloading
+                ? "RELOADING"
+                : $"{weapon.CurrentAmmoInMag} / {weapon.CurrentReserveAmmo}";
 
-        Color weaponBarBackground = new Color(40, 40, 40, 200);
-        Color weaponBarBorder = new Color(255, 255, 255, 255);
+            Vector2 textSize = _font.MeasureString(ammoText);
+            Vector2 textPos = new Vector2(
+                _ammoBarPosition.X + (_ammoBarSize.X - textSize.X) / 2,
+                _ammoBarPosition.Y + (_ammoBarSize.Y - textSize.Y) / 2
+            );
 
-        // Draw background
-        _spriteBatch.Draw(_pixel, backgroundRect, weaponBarBackground);
-        // Draw border
-        DrawRectangleBorder(borderRect, 2, weaponBarBorder);
+            // Color based on ammo status
+            Color ammoColor = weapon.IsReloading
+                ? new Color(255, 220, 40)
+                : (weapon.CurrentAmmoInMag == 0 ? new Color(220, 50, 50) : Color.White);
 
-        // Draw weapon name text (if font available)
-        if (_font != null)
-        {
-            string weaponText = weapon.Name.ToUpper();
-            Vector2 textSize = _font.MeasureString(weaponText);
-            Vector2 textPos = new Vector2(_weaponBarPosition.X + (_weaponBarSize.X - textSize.X) / 2, _weaponBarPosition.Y + (_weaponBarSize.Y - textSize.Y) / 2);
-            _spriteBatch.DrawString(_font, weaponText, textPos + new Vector2(1, 1), _textShadowColor);
-            _spriteBatch.DrawString(_font, weaponText, textPos, _textColor);
+            // Draw shadow
+            _spriteBatch.DrawString(_font, ammoText, textPos + new Vector2(1, 1), _textShadowColor);
+            // Draw text
+            _spriteBatch.DrawString(_font, ammoText, textPos, ammoColor);
         }
     }