Browse Source

Added Android, iOS and Windows Platforms.

CartBlanche 1 month ago
parent
commit
1e48bb1d6d

+ 22 - 0
Shooter/Platforms/Android/AndroidManifest.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
+          package="com.monogame.roleplayinggame" 
+          android:versionCode="1" 
+          android:versionName="1.0">
+  
+  <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="34" />
+  
+  <uses-permission android:name="android.permission.INTERNET" />
+  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+  
+  <application android:allowBackup="true" 
+               android:icon="@drawable/icon" 
+               android:label="@string/app_name"
+               android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+    
+  </application>
+  
+  <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+  
+</manifest>

+ 502 - 0
Shooter/Platforms/Android/Game.cs

@@ -0,0 +1,502 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Shooter.Core.Plugins.Graphics;
+using Shooter.Core.Plugins.Physics;
+using Shooter.Core.Scenes;
+using Shooter.Core.Services;
+using Shooter.Graphics;
+using Shooter.Physics;
+using System;
+using System.Linq;
+
+namespace Shooter;
+
+/// <summary>
+/// Main game class for MonoGame FPS.
+/// This is the entry point for the game, similar to Unity's main scene setup.
+/// 
+/// UNITY COMPARISON:
+/// Unity doesn't have a single "Game" class. Instead:
+/// - This replaces Unity's Application class
+/// - Initialize() = Scene load + Awake()
+/// - Update() = Update() across all GameObjects
+/// - Draw() = Camera rendering (handled by Unity automatically)
+/// </summary>
+public class ShooterGame : Game
+{
+    private GraphicsDeviceManager _graphics;
+    private SpriteBatch? _spriteBatch;
+    private SceneManager? _sceneManager;
+
+    public ShooterGame()
+    {
+        _graphics = new GraphicsDeviceManager(this);
+        Content.RootDirectory = "Content";
+        IsMouseVisible = false;
+        
+        // Set up window
+        _graphics.PreferredBackBufferWidth = 800;
+        _graphics.PreferredBackBufferHeight = 600;
+        _graphics.IsFullScreen = false;
+        _graphics.SynchronizeWithVerticalRetrace = true;
+        
+        Window.Title = "Unity Shooter in MonoGame";
+    }
+
+    /// <summary>
+    /// Initialize the game.
+    /// Similar to Unity's Awake() but for the entire application.
+    /// </summary>
+    protected override void Initialize()
+    {
+        // Initialize service locator
+        ServiceLocator.Initialize();
+        
+        // Phase 1: Register core services
+        var inputService = new InputService();
+        var timeService = new TimeService();
+        var audioService = new AudioService();
+        var physicsProvider = new BepuPhysicsProvider();
+        var graphicsProvider = new ForwardGraphicsProvider();
+        
+        // Phase 2: Register gameplay systems
+        var projectileSystem = new Gameplay.Systems.ProjectileSystem(physicsProvider);
+        
+        // Initialize input service with screen center for mouse locking
+        var screenCenter = new Microsoft.Xna.Framework.Point(
+            _graphics.PreferredBackBufferWidth / 2,
+            _graphics.PreferredBackBufferHeight / 2
+        );
+        inputService.Initialize(screenCenter);
+        
+        // Initialize physics and graphics providers
+        physicsProvider.Initialize();
+        
+        ServiceLocator.Register<IInputService>(inputService);
+        ServiceLocator.Register<ITimeService>(timeService);
+        ServiceLocator.Register<IAudioService>(audioService);
+        ServiceLocator.Register<IPhysicsProvider>(physicsProvider);
+        ServiceLocator.Register<IGraphicsProvider>(graphicsProvider);
+        ServiceLocator.Register<Gameplay.Systems.ProjectileSystem>(projectileSystem);
+        
+        // Initialize graphics provider with our GraphicsDevice
+        graphicsProvider.SetGraphicsDevice(GraphicsDevice);
+        
+        // Create scene manager
+        _sceneManager = new SceneManager();
+        
+        // TODO: Register custom component types
+        // _sceneManager.RegisterComponentType<PlayerController>("PlayerController");
+        
+        base.Initialize();
+        
+        Console.WriteLine("MonoGame FPS initialized!");
+        Console.WriteLine("Phase 1 services registered:");
+        Console.WriteLine("  - InputService (keyboard, mouse, gamepad)");
+        Console.WriteLine("  - TimeService (delta time, time scaling)");
+        Console.WriteLine("  - AudioService (placeholder for Phase 4)");
+        Console.WriteLine("  - BepuPhysicsProvider (multi-threaded physics)");
+        Console.WriteLine("  - ForwardGraphicsProvider (BasicEffect rendering)");
+    }
+    
+    /// <summary>
+    /// Create a simple test scene for Phase 1 validation.
+    /// This demonstrates:
+    /// - Camera setup
+    /// - Static and dynamic physics bodies
+    /// - Mesh rendering with different colors
+    /// - Basic 3D scene composition
+    /// 
+    /// PHASE 2 UPDATE:
+    /// - Player entity with FirstPersonController for movement
+    /// - Mouse look and WASD controls enabled
+    /// </summary>
+    private void CreateTestScene()
+    {
+        // Create a new scene
+        var scene = new Core.Scenes.Scene("Phase2TestScene");
+        _sceneManager!.UnloadScene();
+        
+        // Create Player Entity with FPS Controller
+        var playerEntity = new Core.Entities.Entity("Player");
+        playerEntity.Tag = "Player";
+        
+        var playerTransform = playerEntity.AddComponent<Core.Components.Transform3D>();
+        playerTransform.Position = new System.Numerics.Vector3(0, 2, 10); // Start above ground, back from origin
+        
+        // Add Camera to player
+        var camera = playerEntity.AddComponent<Core.Components.Camera>();
+        camera.FieldOfView = 75f;
+        camera.NearPlane = 0.1f;
+        camera.FarPlane = 100f;
+        
+        // Add FirstPersonController for movement (kinematic movement, no physics yet)
+        var fpsController = playerEntity.AddComponent<Gameplay.Components.FirstPersonController>();
+        fpsController.MoveSpeed = 50.0f; // Tuned for comfortable movement
+        fpsController.MouseSensitivity = 50.0f; // Higher sensitivity for normalized delta (pixel delta normalized to -1 to 1 range)
+        fpsController.JumpForce = 8.0f;
+        
+        // Add WeaponController for weapon management
+        var weaponController = playerEntity.AddComponent<Gameplay.Components.WeaponController>();
+        
+        // Add Health to player
+        var playerHealth = playerEntity.AddComponent<Gameplay.Components.Health>();
+        playerHealth.MaxHealth = 100f;
+        
+        // Add HUD component to player
+        var hud = playerEntity.AddComponent<Gameplay.Components.HUD>();
+        
+        scene.AddEntity(playerEntity);
+        
+        // Initialize camera AFTER entity is added and components are initialized
+        camera.Position = playerTransform.Position;
+        camera.Target = new System.Numerics.Vector3(0, 2, 0); // Look toward origin at same height
+        
+        // Equip weapons for testing
+        var pistol = Gameplay.Weapons.Gun.CreatePistol();
+        var rifle = Gameplay.Weapons.Gun.CreateAssaultRifle();
+        weaponController.EquipWeapon(pistol, 0);   // Slot 1 (press '1' to select)
+        weaponController.EquipWeapon(rifle, 1);    // Slot 2 (press '2' to select)
+        
+        Console.WriteLine("[Phase 2 Test Scene] Player created with FirstPersonController");
+        Console.WriteLine("  Controls: WASD = Move, Mouse = Look, Space = Jump, Shift = Sprint, Escape = Release Mouse");
+        Console.WriteLine("  Weapons: 1 = Pistol, 2 = Assault Rifle, LMB = Fire, R = Reload");
+        
+        // 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
+        Console.WriteLine($"[DEBUG] Ground cube material color: {groundRenderer.Material.Color}");
+        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 a few colored cubes (larger for easier targeting)
+    CreateCube(scene, "RedCube", new System.Numerics.Vector3(-3, 2, 0), 2.0f, Color.Red);
+    CreateCube(scene, "BlueCube", new System.Numerics.Vector3(0, 4, 0), 2.0f, Color.Blue);
+    CreateCube(scene, "YellowCube", new System.Numerics.Vector3(3, 3, 0), 2.0f, Color.Yellow);
+        
+        // Create enemy entities for AI testing (bright red, taller/skinnier to distinguish from cubes)
+        CreateEnemy(scene, playerEntity, "Enemy1", new System.Numerics.Vector3(-5, 2, -5), 1.0f, 0.0f, 0.0f);
+        CreateEnemy(scene, playerEntity, "Enemy2", new System.Numerics.Vector3(5, 2, -5), 1.0f, 0.0f, 0.0f);
+        
+        // Initialize the scene
+        scene.Initialize();
+        
+        Console.WriteLine($"[DEBUG] Scene has {scene.Entities.Count} entities after initialization");
+        foreach (var ent in scene.Entities)
+        {
+            Console.WriteLine($"[DEBUG]   - {ent.Name}: Active={ent.Active}, ComponentCount={ent.GetType().GetProperty("Components")?.GetValue(ent)}");
+        }
+        
+        // Make it the active scene (hacky but works for testing)
+        var activeSceneField = typeof(SceneManager).GetField("_activeScene", 
+            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+        activeSceneField?.SetValue(_sceneManager, scene);
+        
+        Console.WriteLine("[Phase 2 Test Scene] Created:");
+        Console.WriteLine("  - Player with FPS controller, weapons, health (100 HP), and HUD at (0, 2, 10)");
+        Console.WriteLine("  - Ground plane (20x0.5x20 static box)");
+        Console.WriteLine("  - 3 colored cubes with Health (50 HP each) for target practice");
+        Console.WriteLine("  - 2 enemy entities with AI (20 HP, 10 damage melee attacks)");
+        Console.WriteLine("  - Controls: WASD=Move, Mouse=Look, Space=Jump, Shift=Sprint, Esc=Menu");
+        Console.WriteLine("  - Weapons: 1=Pistol, 2=Assault Rifle, LMB=Fire, R=Reload");
+    }
+    
+    /// <summary>
+    /// Helper to create a cube entity.
+    /// </summary>
+    private void CreateCube(Core.Scenes.Scene scene, string name, 
+    System.Numerics.Vector3 position, float size, Color color)
+    {
+        var entity = new Core.Entities.Entity(name);
+        var transform = entity.AddComponent<Core.Components.Transform3D>();
+        transform.Position = position;
+        transform.LocalScale = new System.Numerics.Vector3(size);
+        
+    var renderer = entity.AddComponent<Core.Components.MeshRenderer>();
+    renderer.SetCube(1.0f, new System.Numerics.Vector4(color.R / 255f, color.G / 255f, color.B / 255f, 1.0f));
+        
+        var rigidbody = entity.AddComponent<Core.Components.Rigidbody>();
+        rigidbody.BodyType = Core.Plugins.Physics.BodyType.Static; // Static so cubes don't fall
+        rigidbody.Shape = new Core.Plugins.Physics.BoxShape(size, size, size);
+        rigidbody.Mass = 1.0f;
+        
+        // Add Health component for damage testing
+        var health = entity.AddComponent<Gameplay.Components.Health>();
+        health.MaxHealth = 50f;
+        health.DestroyOnDeath = false; // Keep cube visible after death for testing
+        
+        // Subscribe to death event
+        health.OnDeath += (damageInfo) =>
+        {
+            Console.WriteLine($"[Test Scene] {name} has been destroyed!");
+        };
+        
+        scene.AddEntity(entity);
+    }
+
+    /// <summary>
+    /// Helper to create an enemy entity with AI.
+    /// </summary>
+    private void CreateEnemy(Core.Scenes.Scene scene, Core.Entities.Entity player, string name, 
+        System.Numerics.Vector3 position, float r, float g, float b)
+    {
+        var entity = new Core.Entities.Entity(name);
+        entity.Tag = "Enemy";
+        
+        var transform = entity.AddComponent<Core.Components.Transform3D>();
+        transform.Position = position;
+        transform.LocalScale = new System.Numerics.Vector3(0.8f, 1.8f, 0.8f); // Taller, skinnier - more humanoid
+        
+        var renderer = entity.AddComponent<Core.Components.MeshRenderer>();
+        renderer.SetCube(1.0f, new System.Numerics.Vector4(r, g, b, 1.0f));
+        
+        var rigidbody = entity.AddComponent<Core.Components.Rigidbody>();
+        rigidbody.BodyType = Core.Plugins.Physics.BodyType.Static; // Static for now (Phase 3 will add dynamic movement)
+        rigidbody.Shape = new Core.Plugins.Physics.BoxShape(0.8f, 1.8f, 0.8f);
+        rigidbody.Mass = 70.0f; // Human-like weight
+        
+        // Add Health component
+        var health = entity.AddComponent<Gameplay.Components.Health>();
+        health.MaxHealth = 20f;
+        health.DestroyOnDeath = false; // Keep visible after death for testing
+        
+        // Subscribe to death event
+        health.OnDeath += (damageInfo) =>
+        {
+            Console.WriteLine($"[Test Scene] {name} was killed by {damageInfo.Attacker?.Name ?? "unknown"}!");
+        };
+        
+        // Add EnemyAI component
+        var enemyAI = entity.AddComponent<Gameplay.Components.EnemyAI>();
+        enemyAI.Target = player; // Set player as target
+        enemyAI.DetectionRange = 15f;
+        enemyAI.AttackRange = 2.5f;
+        enemyAI.MoveSpeed = 2.5f;
+        enemyAI.AttackDamage = 10f;
+        enemyAI.AttackCooldown = 2.0f;
+        
+        scene.AddEntity(entity);
+        
+        Console.WriteLine($"[Test Scene] Created enemy '{name}' at {position} targeting player");
+    }
+
+    /// <summary>
+    /// Load content (textures, models, sounds).
+    /// Similar to Unity's resource loading but more manual.
+    /// </summary>
+    protected override void LoadContent()
+    {
+        _spriteBatch = new SpriteBatch(GraphicsDevice);
+        
+        // TODO Phase 1: Load content via Content Pipeline
+        // TODO Phase 5: Load Gum UI screens
+        
+        Console.WriteLine("Content loaded (placeholder - Phase 1 will add actual content)");
+        
+        // Create Phase 2 test scene AFTER all services are initialized
+        CreateTestScene();
+        
+        // Load HUD content after scene is created
+        var playerEntity = _sceneManager?.ActiveScene?.FindEntitiesByTag("Player").FirstOrDefault();
+        if (playerEntity != null)
+        {
+            var hud = playerEntity.GetComponent<Gameplay.Components.HUD>();
+            hud?.LoadContent(GraphicsDevice, _spriteBatch);
+            Console.WriteLine("[Game] HUD content loaded");
+        }
+    }
+
+    /// <summary>
+    /// Update game logic.
+    /// This is called every frame, similar to Update() in Unity.
+    /// 
+    /// EDUCATIONAL NOTE - Fixed Timestep Physics:
+    /// We run physics on a fixed timestep (60 updates/sec) while
+    /// rendering runs at variable framerate. This ensures:
+    /// - Deterministic physics simulation
+    /// - Consistent behavior across different hardware
+    /// - Decoupled physics from rendering performance
+    /// Unity does this automatically. In MonoGame we manage it manually.
+    /// </summary>
+    /// <param name="gameTime">Provides timing information</param>
+    protected override void Update(GameTime gameTime)
+    {
+        // Exit on Escape (temporary - will be replaced with proper menu)
+        if (Keyboard.GetState().IsKeyDown(Keys.Escape))
+            Exit();
+
+        // Update core services
+        var timeService = ServiceLocator.Get<ITimeService>();
+        var inputService = ServiceLocator.Get<IInputService>();
+        var physicsProvider = ServiceLocator.Get<IPhysicsProvider>();
+        
+        timeService.Update(gameTime);
+        inputService.Update();
+        
+        // Update active scene
+        var coreGameTime = new Core.Components.GameTime(
+            gameTime.TotalGameTime,
+            gameTime.ElapsedGameTime
+        );
+        
+        _sceneManager?.Update(coreGameTime);
+        
+        // Step physics simulation with fixed timestep
+        // We use the scaled delta time from TimeService to support slow-motion/time effects
+        // Guard against zero/negative timestep on first frame
+        if (timeService.DeltaTime > 0)
+        {
+            physicsProvider.Step(timeService.DeltaTime);
+        }
+
+        base.Update(gameTime);
+    }
+
+    /// <summary>
+    /// Draw/render the game.
+    /// MonoGame requires you to explicitly handle rendering.
+    /// Unity does this automatically based on cameras.
+    /// 
+    /// EDUCATIONAL NOTE - Rendering Pipeline:
+    /// 1. Clear backbuffer (prevent artifacts from previous frame)
+    /// 2. BeginFrame - Set up render state
+    /// 3. RenderScene - Draw all renderable entities
+    /// 4. EndFrame - Present to screen
+    /// Unity's rendering is automatic via Camera components.
+    /// MonoGame gives you full control (and responsibility).
+    /// </summary>
+    /// <param name="gameTime">Provides timing information</param>
+    protected override void Draw(GameTime gameTime)
+    {
+        GraphicsDevice.Clear(Color.CornflowerBlue);
+
+        // Get graphics provider
+        var graphics = ServiceLocator.Get<IGraphicsProvider>();
+        
+        // Begin frame
+        graphics.BeginFrame();
+        
+        // Find active camera in scene (attached to Player entity in Phase 2)
+        // TODO: Improve camera selection (support multiple cameras, camera priorities)
+        Core.Components.Camera? camera = null;
+        
+        // Try to find camera on Player entity first
+        var playerEntity = _sceneManager?.ActiveScene?.FindEntitiesByTag("Player").FirstOrDefault();
+        if (playerEntity != null)
+        {
+            camera = playerEntity.GetComponent<Core.Components.Camera>();
+        }
+        
+        // Fallback to MainCamera tag if no player camera found
+        if (camera == null)
+        {
+            var cameraEntity = _sceneManager?.ActiveScene?.FindEntitiesByTag("MainCamera").FirstOrDefault();
+            camera = cameraEntity?.GetComponent<Core.Components.Camera>();
+        }
+        
+        if (camera != null)
+        {
+            // Collect all renderable entities from the scene
+            var renderables = new System.Collections.Generic.List<IRenderable>();
+            
+            if (_sceneManager?.ActiveScene != null)
+            {
+                foreach (var entity in _sceneManager.ActiveScene.Entities)
+                {
+                    // MeshRenderer implements IRenderable
+                    var meshRenderer = entity.GetComponent<Core.Components.MeshRenderer>();
+                    if (meshRenderer != null)
+                    {
+                        renderables.Add(meshRenderer);
+                    }
+                }
+            }
+            
+            // Render the scene
+            graphics.RenderScene(camera, renderables);
+        }
+        else
+        {
+            Console.WriteLine("[Render] ERROR: No camera found!");
+        }
+        
+        // End frame (presents to screen)
+        graphics.EndFrame();
+        
+        // Draw crosshair (simple 2D overlay)
+        DrawCrosshair();
+        
+        // Draw scene entities (UI/debug overlay - Phase 5)
+        var coreGameTime = new Core.Components.GameTime(
+            gameTime.TotalGameTime,
+            gameTime.ElapsedGameTime
+        );
+        
+        _sceneManager?.Draw(coreGameTime);
+
+        base.Draw(gameTime);
+    }
+
+    /// <summary>
+    /// Draw a simple crosshair in the center of the screen.
+    /// This helps with aiming when the mouse cursor is hidden.
+    /// </summary>
+    private void DrawCrosshair()
+    {
+        if (_spriteBatch == null) return;
+
+        _spriteBatch.Begin();
+        
+        // Get screen center
+        int centerX = _graphics.PreferredBackBufferWidth / 2;
+        int centerY = _graphics.PreferredBackBufferHeight / 2;
+        
+        // Crosshair size
+        int crosshairSize = 10;
+        int thickness = 2;
+        int gap = 3;
+        
+        // Create a 1x1 white pixel texture for drawing lines
+        Texture2D pixel = new Texture2D(GraphicsDevice, 1, 1);
+        pixel.SetData(new[] { Color.White });
+        
+        // Draw horizontal line (left and right from center)
+        _spriteBatch.Draw(pixel, new Rectangle(centerX - crosshairSize - gap, centerY - thickness / 2, crosshairSize, thickness), Color.White);
+        _spriteBatch.Draw(pixel, new Rectangle(centerX + gap, centerY - thickness / 2, crosshairSize, thickness), Color.White);
+        
+        // Draw vertical line (top and bottom from center)
+        _spriteBatch.Draw(pixel, new Rectangle(centerX - thickness / 2, centerY - crosshairSize - gap, thickness, crosshairSize), Color.White);
+        _spriteBatch.Draw(pixel, new Rectangle(centerX - thickness / 2, centerY + gap, thickness, crosshairSize), Color.White);
+        
+        _spriteBatch.End();
+        
+        pixel.Dispose();
+    }
+    
+    /// <summary>
+    /// Cleanup when game exits.
+    /// Similar to Unity's OnApplicationQuit() or OnDestroy().
+    /// </summary>
+    protected override void UnloadContent()
+    {
+        // Unload scene
+        _sceneManager?.UnloadScene();
+        
+        // Shutdown services
+        ServiceLocator.Get<IPhysicsProvider>()?.Shutdown();
+        ServiceLocator.Get<IGraphicsProvider>()?.Shutdown();
+        
+        ServiceLocator.Clear();
+        
+        Console.WriteLine("MonoGame FPS shutdown complete");
+        
+        base.UnloadContent();
+    }
+}

+ 36 - 0
Shooter/Platforms/Android/MainActivity.cs

@@ -0,0 +1,36 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+using Android.Views;
+using Microsoft.Xna.Framework;
+using Shooter;
+
+namespace Shooter.Android
+{
+    [Activity(
+        Label = "@string/app_name",
+        MainLauncher = true,
+        Icon = "@drawable/icon",
+        AlwaysRetainTaskState = true,
+        LaunchMode = LaunchMode.SingleInstance,
+        ScreenOrientation = ScreenOrientation.SensorLandscape,
+        ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden
+    )]
+    public class MainActivity : AndroidGameActivity
+    {
+        private ShooterGame _game;
+        private View _view;
+
+        protected override void OnCreate(Bundle bundle)
+        {
+            base.OnCreate(bundle);
+
+            _game = new ShooterGame();
+
+            _view = _game.Services.GetService(typeof(View)) as View;
+            SetContentView(_view);
+
+            _game.Run();
+        }
+    }
+}

BIN
Shooter/Platforms/Android/Resources/drawable/icon.png


File diff suppressed because it is too large
+ 2 - 0
Shooter/Platforms/Android/Resources/drawable/icon.svg


+ 4 - 0
Shooter/Platforms/Android/Resources/values/strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <string name="app_name">Role Playing Game</string>
+</resources>

+ 38 - 0
Shooter/Platforms/Android/Shooter.csproj

@@ -0,0 +1,38 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net9.0-android</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>Shooter.Android</RootNamespace>
+    <AssemblyName>Shooter</AssemblyName>
+    <AssemblyTitle>Shooter</AssemblyTitle>
+    <AssemblyDescription>MonoGame Port of Unity's Shooter to Android</AssemblyDescription>
+    <AssemblyCompany>MonoGame Samples</AssemblyCompany>
+    <AssemblyProduct>Shooter</AssemblyProduct>
+    <AssemblyVersion>1.0.0.0</AssemblyVersion>
+    <AndroidApplication>true</AndroidApplication>
+    <AndroidManifest>AndroidManifest.xml</AndroidManifest>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <MonoGamePlatform>Android</MonoGamePlatform>
+    <SupportedOSPlatformVersion>28</SupportedOSPlatformVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Shooter.Core\Shooter.Core.csproj" />
+    <ProjectReference Include="..\..\Shooter.Physics\Shooter.Physics.csproj" />
+    <ProjectReference Include="..\..\Shooter.Graphics\Shooter.Graphics.csproj" />
+    <ProjectReference Include="..\..\Shooter.Gameplay\Shooter.Gameplay.csproj" />
+    <ProjectReference Include="..\..\Shooter.UI\Shooter.UI.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.Android" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+  </ItemGroup>
+
+  <!-- Content pipeline disabled until Phase 1 - no assets yet -->
+  <!-- <ItemGroup>
+    <MonoGameContentReference Include="Content\Content.mgcb" />
+  </ItemGroup> -->
+
+</Project>

+ 8 - 8
Shooter/Platforms/Desktop/Game.cs

@@ -1,14 +1,14 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
 using Microsoft.Xna.Framework.Input;
-using Shooter.Core.Services;
-using Shooter.Core.Plugins.Physics;
 using Shooter.Core.Plugins.Graphics;
+using Shooter.Core.Plugins.Physics;
+using Shooter.Core.Scenes;
+using Shooter.Core.Services;
 using Shooter.Graphics;
 using Shooter.Physics;
-using Shooter.Gameplay.Systems;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-
-using Shooter.Core.Scenes;
+using System;
+using System.Linq;
 
 namespace Shooter;
 
@@ -41,7 +41,7 @@ public class ShooterGame : Game
         _graphics.IsFullScreen = false;
         _graphics.SynchronizeWithVerticalRetrace = true;
         
-        Window.Title = "MonoGame FPS - Educational 3D Shooter";
+        Window.Title = "Unity Shooter in MonoGame";
     }
 
     /// <summary>

+ 5 - 5
Shooter/Platforms/Desktop/Shooter.csproj

@@ -1,13 +1,13 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <OutputType>WinExe</OutputType>
-    <TargetFramework>net8.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
     <LangVersion>latest</LangVersion>
     <Nullable>enable</Nullable>
-    <ImplicitUsings>enable</ImplicitUsings>
     <PublishReadyToRun>false</PublishReadyToRun>
     <TieredCompilation>false</TieredCompilation>
+    <MonoGamePlatform>DesktopGL</MonoGamePlatform>
   </PropertyGroup>
 
   <ItemGroup>
@@ -19,8 +19,8 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.2.1105" />
-    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.2.1105" />
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
   </ItemGroup>
 
   <!-- Content pipeline disabled until Phase 1 - no assets yet -->

+ 502 - 0
Shooter/Platforms/Windows/Game.cs

@@ -0,0 +1,502 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Shooter.Core.Plugins.Graphics;
+using Shooter.Core.Plugins.Physics;
+using Shooter.Core.Scenes;
+using Shooter.Core.Services;
+using Shooter.Graphics;
+using Shooter.Physics;
+using System;
+using System.Linq;
+
+namespace Shooter;
+
+/// <summary>
+/// Main game class for MonoGame FPS.
+/// This is the entry point for the game, similar to Unity's main scene setup.
+/// 
+/// UNITY COMPARISON:
+/// Unity doesn't have a single "Game" class. Instead:
+/// - This replaces Unity's Application class
+/// - Initialize() = Scene load + Awake()
+/// - Update() = Update() across all GameObjects
+/// - Draw() = Camera rendering (handled by Unity automatically)
+/// </summary>
+public class ShooterGame : Game
+{
+    private GraphicsDeviceManager _graphics;
+    private SpriteBatch? _spriteBatch;
+    private SceneManager? _sceneManager;
+
+    public ShooterGame()
+    {
+        _graphics = new GraphicsDeviceManager(this);
+        Content.RootDirectory = "Content";
+        IsMouseVisible = false;
+        
+        // Set up window
+        _graphics.PreferredBackBufferWidth = 800;
+        _graphics.PreferredBackBufferHeight = 600;
+        _graphics.IsFullScreen = false;
+        _graphics.SynchronizeWithVerticalRetrace = true;
+        
+        Window.Title = "Unity Shooter in MonoGame";
+    }
+
+    /// <summary>
+    /// Initialize the game.
+    /// Similar to Unity's Awake() but for the entire application.
+    /// </summary>
+    protected override void Initialize()
+    {
+        // Initialize service locator
+        ServiceLocator.Initialize();
+        
+        // Phase 1: Register core services
+        var inputService = new InputService();
+        var timeService = new TimeService();
+        var audioService = new AudioService();
+        var physicsProvider = new BepuPhysicsProvider();
+        var graphicsProvider = new ForwardGraphicsProvider();
+        
+        // Phase 2: Register gameplay systems
+        var projectileSystem = new Gameplay.Systems.ProjectileSystem(physicsProvider);
+        
+        // Initialize input service with screen center for mouse locking
+        var screenCenter = new Microsoft.Xna.Framework.Point(
+            _graphics.PreferredBackBufferWidth / 2,
+            _graphics.PreferredBackBufferHeight / 2
+        );
+        inputService.Initialize(screenCenter);
+        
+        // Initialize physics and graphics providers
+        physicsProvider.Initialize();
+        
+        ServiceLocator.Register<IInputService>(inputService);
+        ServiceLocator.Register<ITimeService>(timeService);
+        ServiceLocator.Register<IAudioService>(audioService);
+        ServiceLocator.Register<IPhysicsProvider>(physicsProvider);
+        ServiceLocator.Register<IGraphicsProvider>(graphicsProvider);
+        ServiceLocator.Register<Gameplay.Systems.ProjectileSystem>(projectileSystem);
+        
+        // Initialize graphics provider with our GraphicsDevice
+        graphicsProvider.SetGraphicsDevice(GraphicsDevice);
+        
+        // Create scene manager
+        _sceneManager = new SceneManager();
+        
+        // TODO: Register custom component types
+        // _sceneManager.RegisterComponentType<PlayerController>("PlayerController");
+        
+        base.Initialize();
+        
+        Console.WriteLine("MonoGame FPS initialized!");
+        Console.WriteLine("Phase 1 services registered:");
+        Console.WriteLine("  - InputService (keyboard, mouse, gamepad)");
+        Console.WriteLine("  - TimeService (delta time, time scaling)");
+        Console.WriteLine("  - AudioService (placeholder for Phase 4)");
+        Console.WriteLine("  - BepuPhysicsProvider (multi-threaded physics)");
+        Console.WriteLine("  - ForwardGraphicsProvider (BasicEffect rendering)");
+    }
+    
+    /// <summary>
+    /// Create a simple test scene for Phase 1 validation.
+    /// This demonstrates:
+    /// - Camera setup
+    /// - Static and dynamic physics bodies
+    /// - Mesh rendering with different colors
+    /// - Basic 3D scene composition
+    /// 
+    /// PHASE 2 UPDATE:
+    /// - Player entity with FirstPersonController for movement
+    /// - Mouse look and WASD controls enabled
+    /// </summary>
+    private void CreateTestScene()
+    {
+        // Create a new scene
+        var scene = new Core.Scenes.Scene("Phase2TestScene");
+        _sceneManager!.UnloadScene();
+        
+        // Create Player Entity with FPS Controller
+        var playerEntity = new Core.Entities.Entity("Player");
+        playerEntity.Tag = "Player";
+        
+        var playerTransform = playerEntity.AddComponent<Core.Components.Transform3D>();
+        playerTransform.Position = new System.Numerics.Vector3(0, 2, 10); // Start above ground, back from origin
+        
+        // Add Camera to player
+        var camera = playerEntity.AddComponent<Core.Components.Camera>();
+        camera.FieldOfView = 75f;
+        camera.NearPlane = 0.1f;
+        camera.FarPlane = 100f;
+        
+        // Add FirstPersonController for movement (kinematic movement, no physics yet)
+        var fpsController = playerEntity.AddComponent<Gameplay.Components.FirstPersonController>();
+        fpsController.MoveSpeed = 50.0f; // Tuned for comfortable movement
+        fpsController.MouseSensitivity = 50.0f; // Higher sensitivity for normalized delta (pixel delta normalized to -1 to 1 range)
+        fpsController.JumpForce = 8.0f;
+        
+        // Add WeaponController for weapon management
+        var weaponController = playerEntity.AddComponent<Gameplay.Components.WeaponController>();
+        
+        // Add Health to player
+        var playerHealth = playerEntity.AddComponent<Gameplay.Components.Health>();
+        playerHealth.MaxHealth = 100f;
+        
+        // Add HUD component to player
+        var hud = playerEntity.AddComponent<Gameplay.Components.HUD>();
+        
+        scene.AddEntity(playerEntity);
+        
+        // Initialize camera AFTER entity is added and components are initialized
+        camera.Position = playerTransform.Position;
+        camera.Target = new System.Numerics.Vector3(0, 2, 0); // Look toward origin at same height
+        
+        // Equip weapons for testing
+        var pistol = Gameplay.Weapons.Gun.CreatePistol();
+        var rifle = Gameplay.Weapons.Gun.CreateAssaultRifle();
+        weaponController.EquipWeapon(pistol, 0);   // Slot 1 (press '1' to select)
+        weaponController.EquipWeapon(rifle, 1);    // Slot 2 (press '2' to select)
+        
+        Console.WriteLine("[Phase 2 Test Scene] Player created with FirstPersonController");
+        Console.WriteLine("  Controls: WASD = Move, Mouse = Look, Space = Jump, Shift = Sprint, Escape = Release Mouse");
+        Console.WriteLine("  Weapons: 1 = Pistol, 2 = Assault Rifle, LMB = Fire, R = Reload");
+        
+        // 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
+        Console.WriteLine($"[DEBUG] Ground cube material color: {groundRenderer.Material.Color}");
+        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 a few colored cubes (larger for easier targeting)
+    CreateCube(scene, "RedCube", new System.Numerics.Vector3(-3, 2, 0), 2.0f, Color.Red);
+    CreateCube(scene, "BlueCube", new System.Numerics.Vector3(0, 4, 0), 2.0f, Color.Blue);
+    CreateCube(scene, "YellowCube", new System.Numerics.Vector3(3, 3, 0), 2.0f, Color.Yellow);
+        
+        // Create enemy entities for AI testing (bright red, taller/skinnier to distinguish from cubes)
+        CreateEnemy(scene, playerEntity, "Enemy1", new System.Numerics.Vector3(-5, 2, -5), 1.0f, 0.0f, 0.0f);
+        CreateEnemy(scene, playerEntity, "Enemy2", new System.Numerics.Vector3(5, 2, -5), 1.0f, 0.0f, 0.0f);
+        
+        // Initialize the scene
+        scene.Initialize();
+        
+        Console.WriteLine($"[DEBUG] Scene has {scene.Entities.Count} entities after initialization");
+        foreach (var ent in scene.Entities)
+        {
+            Console.WriteLine($"[DEBUG]   - {ent.Name}: Active={ent.Active}, ComponentCount={ent.GetType().GetProperty("Components")?.GetValue(ent)}");
+        }
+        
+        // Make it the active scene (hacky but works for testing)
+        var activeSceneField = typeof(SceneManager).GetField("_activeScene", 
+            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+        activeSceneField?.SetValue(_sceneManager, scene);
+        
+        Console.WriteLine("[Phase 2 Test Scene] Created:");
+        Console.WriteLine("  - Player with FPS controller, weapons, health (100 HP), and HUD at (0, 2, 10)");
+        Console.WriteLine("  - Ground plane (20x0.5x20 static box)");
+        Console.WriteLine("  - 3 colored cubes with Health (50 HP each) for target practice");
+        Console.WriteLine("  - 2 enemy entities with AI (20 HP, 10 damage melee attacks)");
+        Console.WriteLine("  - Controls: WASD=Move, Mouse=Look, Space=Jump, Shift=Sprint, Esc=Menu");
+        Console.WriteLine("  - Weapons: 1=Pistol, 2=Assault Rifle, LMB=Fire, R=Reload");
+    }
+    
+    /// <summary>
+    /// Helper to create a cube entity.
+    /// </summary>
+    private void CreateCube(Core.Scenes.Scene scene, string name, 
+    System.Numerics.Vector3 position, float size, Color color)
+    {
+        var entity = new Core.Entities.Entity(name);
+        var transform = entity.AddComponent<Core.Components.Transform3D>();
+        transform.Position = position;
+        transform.LocalScale = new System.Numerics.Vector3(size);
+        
+    var renderer = entity.AddComponent<Core.Components.MeshRenderer>();
+    renderer.SetCube(1.0f, new System.Numerics.Vector4(color.R / 255f, color.G / 255f, color.B / 255f, 1.0f));
+        
+        var rigidbody = entity.AddComponent<Core.Components.Rigidbody>();
+        rigidbody.BodyType = Core.Plugins.Physics.BodyType.Static; // Static so cubes don't fall
+        rigidbody.Shape = new Core.Plugins.Physics.BoxShape(size, size, size);
+        rigidbody.Mass = 1.0f;
+        
+        // Add Health component for damage testing
+        var health = entity.AddComponent<Gameplay.Components.Health>();
+        health.MaxHealth = 50f;
+        health.DestroyOnDeath = false; // Keep cube visible after death for testing
+        
+        // Subscribe to death event
+        health.OnDeath += (damageInfo) =>
+        {
+            Console.WriteLine($"[Test Scene] {name} has been destroyed!");
+        };
+        
+        scene.AddEntity(entity);
+    }
+
+    /// <summary>
+    /// Helper to create an enemy entity with AI.
+    /// </summary>
+    private void CreateEnemy(Core.Scenes.Scene scene, Core.Entities.Entity player, string name, 
+        System.Numerics.Vector3 position, float r, float g, float b)
+    {
+        var entity = new Core.Entities.Entity(name);
+        entity.Tag = "Enemy";
+        
+        var transform = entity.AddComponent<Core.Components.Transform3D>();
+        transform.Position = position;
+        transform.LocalScale = new System.Numerics.Vector3(0.8f, 1.8f, 0.8f); // Taller, skinnier - more humanoid
+        
+        var renderer = entity.AddComponent<Core.Components.MeshRenderer>();
+        renderer.SetCube(1.0f, new System.Numerics.Vector4(r, g, b, 1.0f));
+        
+        var rigidbody = entity.AddComponent<Core.Components.Rigidbody>();
+        rigidbody.BodyType = Core.Plugins.Physics.BodyType.Static; // Static for now (Phase 3 will add dynamic movement)
+        rigidbody.Shape = new Core.Plugins.Physics.BoxShape(0.8f, 1.8f, 0.8f);
+        rigidbody.Mass = 70.0f; // Human-like weight
+        
+        // Add Health component
+        var health = entity.AddComponent<Gameplay.Components.Health>();
+        health.MaxHealth = 20f;
+        health.DestroyOnDeath = false; // Keep visible after death for testing
+        
+        // Subscribe to death event
+        health.OnDeath += (damageInfo) =>
+        {
+            Console.WriteLine($"[Test Scene] {name} was killed by {damageInfo.Attacker?.Name ?? "unknown"}!");
+        };
+        
+        // Add EnemyAI component
+        var enemyAI = entity.AddComponent<Gameplay.Components.EnemyAI>();
+        enemyAI.Target = player; // Set player as target
+        enemyAI.DetectionRange = 15f;
+        enemyAI.AttackRange = 2.5f;
+        enemyAI.MoveSpeed = 2.5f;
+        enemyAI.AttackDamage = 10f;
+        enemyAI.AttackCooldown = 2.0f;
+        
+        scene.AddEntity(entity);
+        
+        Console.WriteLine($"[Test Scene] Created enemy '{name}' at {position} targeting player");
+    }
+
+    /// <summary>
+    /// Load content (textures, models, sounds).
+    /// Similar to Unity's resource loading but more manual.
+    /// </summary>
+    protected override void LoadContent()
+    {
+        _spriteBatch = new SpriteBatch(GraphicsDevice);
+        
+        // TODO Phase 1: Load content via Content Pipeline
+        // TODO Phase 5: Load Gum UI screens
+        
+        Console.WriteLine("Content loaded (placeholder - Phase 1 will add actual content)");
+        
+        // Create Phase 2 test scene AFTER all services are initialized
+        CreateTestScene();
+        
+        // Load HUD content after scene is created
+        var playerEntity = _sceneManager?.ActiveScene?.FindEntitiesByTag("Player").FirstOrDefault();
+        if (playerEntity != null)
+        {
+            var hud = playerEntity.GetComponent<Gameplay.Components.HUD>();
+            hud?.LoadContent(GraphicsDevice, _spriteBatch);
+            Console.WriteLine("[Game] HUD content loaded");
+        }
+    }
+
+    /// <summary>
+    /// Update game logic.
+    /// This is called every frame, similar to Update() in Unity.
+    /// 
+    /// EDUCATIONAL NOTE - Fixed Timestep Physics:
+    /// We run physics on a fixed timestep (60 updates/sec) while
+    /// rendering runs at variable framerate. This ensures:
+    /// - Deterministic physics simulation
+    /// - Consistent behavior across different hardware
+    /// - Decoupled physics from rendering performance
+    /// Unity does this automatically. In MonoGame we manage it manually.
+    /// </summary>
+    /// <param name="gameTime">Provides timing information</param>
+    protected override void Update(GameTime gameTime)
+    {
+        // Exit on Escape (temporary - will be replaced with proper menu)
+        if (Keyboard.GetState().IsKeyDown(Keys.Escape))
+            Exit();
+
+        // Update core services
+        var timeService = ServiceLocator.Get<ITimeService>();
+        var inputService = ServiceLocator.Get<IInputService>();
+        var physicsProvider = ServiceLocator.Get<IPhysicsProvider>();
+        
+        timeService.Update(gameTime);
+        inputService.Update();
+        
+        // Update active scene
+        var coreGameTime = new Core.Components.GameTime(
+            gameTime.TotalGameTime,
+            gameTime.ElapsedGameTime
+        );
+        
+        _sceneManager?.Update(coreGameTime);
+        
+        // Step physics simulation with fixed timestep
+        // We use the scaled delta time from TimeService to support slow-motion/time effects
+        // Guard against zero/negative timestep on first frame
+        if (timeService.DeltaTime > 0)
+        {
+            physicsProvider.Step(timeService.DeltaTime);
+        }
+
+        base.Update(gameTime);
+    }
+
+    /// <summary>
+    /// Draw/render the game.
+    /// MonoGame requires you to explicitly handle rendering.
+    /// Unity does this automatically based on cameras.
+    /// 
+    /// EDUCATIONAL NOTE - Rendering Pipeline:
+    /// 1. Clear backbuffer (prevent artifacts from previous frame)
+    /// 2. BeginFrame - Set up render state
+    /// 3. RenderScene - Draw all renderable entities
+    /// 4. EndFrame - Present to screen
+    /// Unity's rendering is automatic via Camera components.
+    /// MonoGame gives you full control (and responsibility).
+    /// </summary>
+    /// <param name="gameTime">Provides timing information</param>
+    protected override void Draw(GameTime gameTime)
+    {
+        GraphicsDevice.Clear(Color.CornflowerBlue);
+
+        // Get graphics provider
+        var graphics = ServiceLocator.Get<IGraphicsProvider>();
+        
+        // Begin frame
+        graphics.BeginFrame();
+        
+        // Find active camera in scene (attached to Player entity in Phase 2)
+        // TODO: Improve camera selection (support multiple cameras, camera priorities)
+        Core.Components.Camera? camera = null;
+        
+        // Try to find camera on Player entity first
+        var playerEntity = _sceneManager?.ActiveScene?.FindEntitiesByTag("Player").FirstOrDefault();
+        if (playerEntity != null)
+        {
+            camera = playerEntity.GetComponent<Core.Components.Camera>();
+        }
+        
+        // Fallback to MainCamera tag if no player camera found
+        if (camera == null)
+        {
+            var cameraEntity = _sceneManager?.ActiveScene?.FindEntitiesByTag("MainCamera").FirstOrDefault();
+            camera = cameraEntity?.GetComponent<Core.Components.Camera>();
+        }
+        
+        if (camera != null)
+        {
+            // Collect all renderable entities from the scene
+            var renderables = new System.Collections.Generic.List<IRenderable>();
+            
+            if (_sceneManager?.ActiveScene != null)
+            {
+                foreach (var entity in _sceneManager.ActiveScene.Entities)
+                {
+                    // MeshRenderer implements IRenderable
+                    var meshRenderer = entity.GetComponent<Core.Components.MeshRenderer>();
+                    if (meshRenderer != null)
+                    {
+                        renderables.Add(meshRenderer);
+                    }
+                }
+            }
+            
+            // Render the scene
+            graphics.RenderScene(camera, renderables);
+        }
+        else
+        {
+            Console.WriteLine("[Render] ERROR: No camera found!");
+        }
+        
+        // End frame (presents to screen)
+        graphics.EndFrame();
+        
+        // Draw crosshair (simple 2D overlay)
+        DrawCrosshair();
+        
+        // Draw scene entities (UI/debug overlay - Phase 5)
+        var coreGameTime = new Core.Components.GameTime(
+            gameTime.TotalGameTime,
+            gameTime.ElapsedGameTime
+        );
+        
+        _sceneManager?.Draw(coreGameTime);
+
+        base.Draw(gameTime);
+    }
+
+    /// <summary>
+    /// Draw a simple crosshair in the center of the screen.
+    /// This helps with aiming when the mouse cursor is hidden.
+    /// </summary>
+    private void DrawCrosshair()
+    {
+        if (_spriteBatch == null) return;
+
+        _spriteBatch.Begin();
+        
+        // Get screen center
+        int centerX = _graphics.PreferredBackBufferWidth / 2;
+        int centerY = _graphics.PreferredBackBufferHeight / 2;
+        
+        // Crosshair size
+        int crosshairSize = 10;
+        int thickness = 2;
+        int gap = 3;
+        
+        // Create a 1x1 white pixel texture for drawing lines
+        Texture2D pixel = new Texture2D(GraphicsDevice, 1, 1);
+        pixel.SetData(new[] { Color.White });
+        
+        // Draw horizontal line (left and right from center)
+        _spriteBatch.Draw(pixel, new Rectangle(centerX - crosshairSize - gap, centerY - thickness / 2, crosshairSize, thickness), Color.White);
+        _spriteBatch.Draw(pixel, new Rectangle(centerX + gap, centerY - thickness / 2, crosshairSize, thickness), Color.White);
+        
+        // Draw vertical line (top and bottom from center)
+        _spriteBatch.Draw(pixel, new Rectangle(centerX - thickness / 2, centerY - crosshairSize - gap, thickness, crosshairSize), Color.White);
+        _spriteBatch.Draw(pixel, new Rectangle(centerX - thickness / 2, centerY + gap, thickness, crosshairSize), Color.White);
+        
+        _spriteBatch.End();
+        
+        pixel.Dispose();
+    }
+    
+    /// <summary>
+    /// Cleanup when game exits.
+    /// Similar to Unity's OnApplicationQuit() or OnDestroy().
+    /// </summary>
+    protected override void UnloadContent()
+    {
+        // Unload scene
+        _sceneManager?.UnloadScene();
+        
+        // Shutdown services
+        ServiceLocator.Get<IPhysicsProvider>()?.Shutdown();
+        ServiceLocator.Get<IGraphicsProvider>()?.Shutdown();
+        
+        ServiceLocator.Clear();
+        
+        Console.WriteLine("MonoGame FPS shutdown complete");
+        
+        base.UnloadContent();
+    }
+}

+ 13 - 0
Shooter/Platforms/Windows/Program.cs

@@ -0,0 +1,13 @@
+using Shooter;
+
+namespace Shooter.Windows
+{
+    internal static class Program
+    {
+        private static void Main()
+        {
+            using (var game = new ShooterGame())
+                game.Run();
+        }
+    }
+}

+ 38 - 0
Shooter/Platforms/Windows/Shooter.csproj

@@ -0,0 +1,38 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net9.0-windows</TargetFramework>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>Shooter.Windows</RootNamespace>
+    <AssemblyName>Shooter</AssemblyName>
+    <AssemblyTitle>Shooter - Windows</AssemblyTitle>
+    <AssemblyDescription>MonoGame Port of Unity's Shooter to Windows</AssemblyDescription>
+    <AssemblyCompany>MonoGame Samples</AssemblyCompany>
+    <AssemblyProduct>Shooter</AssemblyProduct>
+    <AssemblyCopyright>Dominique Louis</AssemblyCopyright>
+    <AssemblyVersion>1.0.0.0</AssemblyVersion>
+    <FileVersion>1.0.0.0</FileVersion>
+    <UseWindowsForms>true</UseWindowsForms>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <MonoGamePlatform>Windows</MonoGamePlatform>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Shooter.Core\Shooter.Core.csproj" />
+    <ProjectReference Include="..\..\Shooter.Physics\Shooter.Physics.csproj" />
+    <ProjectReference Include="..\..\Shooter.Graphics\Shooter.Graphics.csproj" />
+    <ProjectReference Include="..\..\Shooter.Gameplay\Shooter.Gameplay.csproj" />
+    <ProjectReference Include="..\..\Shooter.UI\Shooter.UI.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.WindowsDX" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+  </ItemGroup>
+
+  <!-- Content pipeline disabled until Phase 1 - no assets yet -->
+  <!-- <ItemGroup>
+    <MonoGameContentReference Include="Content\Content.mgcb" />
+  </ItemGroup> -->
+
+</Project>

+ 500 - 0
Shooter/Platforms/iOS/Game.cs

@@ -0,0 +1,500 @@
+using Microsoft.Xna.Framework.Input;
+using Shooter.Core.Services;
+using Shooter.Core.Plugins.Physics;
+using Shooter.Core.Plugins.Graphics;
+using Shooter.Graphics;
+using Shooter.Physics;
+using Shooter.Gameplay.Systems;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+using Shooter.Core.Scenes;
+using System;
+using System.Linq;
+
+namespace Shooter;
+
+/// <summary>
+/// Main game class for MonoGame FPS.
+/// This is the entry point for the game, similar to Unity's main scene setup.
+/// 
+/// UNITY COMPARISON:
+/// Unity doesn't have a single "Game" class. Instead:
+/// - This replaces Unity's Application class
+/// - Initialize() = Scene load + Awake()
+/// - Update() = Update() across all GameObjects
+/// - Draw() = Camera rendering (handled by Unity automatically)
+/// </summary>
+public class ShooterGame : Game
+{
+    private GraphicsDeviceManager _graphics;
+    private SpriteBatch? _spriteBatch;
+    private SceneManager? _sceneManager;
+
+    public ShooterGame()
+    {
+        _graphics = new GraphicsDeviceManager(this);
+        Content.RootDirectory = "Content";
+        IsMouseVisible = false;
+        
+        // Set up window
+        _graphics.PreferredBackBufferWidth = 800;
+        _graphics.PreferredBackBufferHeight = 600;
+        _graphics.IsFullScreen = false;
+        _graphics.SynchronizeWithVerticalRetrace = true;
+        
+        Window.Title = "Unity Shooter in MonoGame";
+    }
+
+    /// <summary>
+    /// Initialize the game.
+    /// Similar to Unity's Awake() but for the entire application.
+    /// </summary>
+    protected override void Initialize()
+    {
+        // Initialize service locator
+        ServiceLocator.Initialize();
+        
+        // Phase 1: Register core services
+        var inputService = new InputService();
+        var timeService = new TimeService();
+        var audioService = new AudioService();
+        var physicsProvider = new BepuPhysicsProvider();
+        var graphicsProvider = new ForwardGraphicsProvider();
+        
+        // Phase 2: Register gameplay systems
+        var projectileSystem = new Gameplay.Systems.ProjectileSystem(physicsProvider);
+        
+        // Initialize input service with screen center for mouse locking
+        var screenCenter = new Microsoft.Xna.Framework.Point(
+            _graphics.PreferredBackBufferWidth / 2,
+            _graphics.PreferredBackBufferHeight / 2
+        );
+        inputService.Initialize(screenCenter);
+        
+        // Initialize physics and graphics providers
+        physicsProvider.Initialize();
+        
+        ServiceLocator.Register<IInputService>(inputService);
+        ServiceLocator.Register<ITimeService>(timeService);
+        ServiceLocator.Register<IAudioService>(audioService);
+        ServiceLocator.Register<IPhysicsProvider>(physicsProvider);
+        ServiceLocator.Register<IGraphicsProvider>(graphicsProvider);
+        ServiceLocator.Register<Gameplay.Systems.ProjectileSystem>(projectileSystem);
+        
+        // Initialize graphics provider with our GraphicsDevice
+        graphicsProvider.SetGraphicsDevice(GraphicsDevice);
+        
+        // Create scene manager
+        _sceneManager = new SceneManager();
+        
+        // TODO: Register custom component types
+        // _sceneManager.RegisterComponentType<PlayerController>("PlayerController");
+        
+        base.Initialize();
+        
+        Console.WriteLine("MonoGame FPS initialized!");
+        Console.WriteLine("Phase 1 services registered:");
+        Console.WriteLine("  - InputService (keyboard, mouse, gamepad)");
+        Console.WriteLine("  - TimeService (delta time, time scaling)");
+        Console.WriteLine("  - AudioService (placeholder for Phase 4)");
+        Console.WriteLine("  - BepuPhysicsProvider (multi-threaded physics)");
+        Console.WriteLine("  - ForwardGraphicsProvider (BasicEffect rendering)");
+    }
+    
+    /// <summary>
+    /// Create a simple test scene for Phase 1 validation.
+    /// This demonstrates:
+    /// - Camera setup
+    /// - Static and dynamic physics bodies
+    /// - Mesh rendering with different colors
+    /// - Basic 3D scene composition
+    /// 
+    /// PHASE 2 UPDATE:
+    /// - Player entity with FirstPersonController for movement
+    /// - Mouse look and WASD controls enabled
+    /// </summary>
+    private void CreateTestScene()
+    {
+        // Create a new scene
+        var scene = new Core.Scenes.Scene("Phase2TestScene");
+        _sceneManager!.UnloadScene();
+        
+        // Create Player Entity with FPS Controller
+        var playerEntity = new Core.Entities.Entity("Player");
+        playerEntity.Tag = "Player";
+        
+        var playerTransform = playerEntity.AddComponent<Core.Components.Transform3D>();
+        playerTransform.Position = new System.Numerics.Vector3(0, 2, 10); // Start above ground, back from origin
+        
+        // Add Camera to player
+        var camera = playerEntity.AddComponent<Core.Components.Camera>();
+        camera.FieldOfView = 75f;
+        camera.NearPlane = 0.1f;
+        camera.FarPlane = 100f;
+        
+        // Add FirstPersonController for movement (kinematic movement, no physics yet)
+        var fpsController = playerEntity.AddComponent<Gameplay.Components.FirstPersonController>();
+        fpsController.MoveSpeed = 50.0f; // Tuned for comfortable movement
+        fpsController.MouseSensitivity = 50.0f; // Higher sensitivity for normalized delta (pixel delta normalized to -1 to 1 range)
+        fpsController.JumpForce = 8.0f;
+        
+        // Add WeaponController for weapon management
+        var weaponController = playerEntity.AddComponent<Gameplay.Components.WeaponController>();
+        
+        // Add Health to player
+        var playerHealth = playerEntity.AddComponent<Gameplay.Components.Health>();
+        playerHealth.MaxHealth = 100f;
+        
+        // Add HUD component to player
+        var hud = playerEntity.AddComponent<Gameplay.Components.HUD>();
+        
+        scene.AddEntity(playerEntity);
+        
+        // Initialize camera AFTER entity is added and components are initialized
+        camera.Position = playerTransform.Position;
+        camera.Target = new System.Numerics.Vector3(0, 2, 0); // Look toward origin at same height
+        
+        // Equip weapons for testing
+        var pistol = Gameplay.Weapons.Gun.CreatePistol();
+        var rifle = Gameplay.Weapons.Gun.CreateAssaultRifle();
+        weaponController.EquipWeapon(pistol, 0);   // Slot 1 (press '1' to select)
+        weaponController.EquipWeapon(rifle, 1);    // Slot 2 (press '2' to select)
+        
+        Console.WriteLine("[Phase 2 Test Scene] Player created with FirstPersonController");
+        Console.WriteLine("  Controls: WASD = Move, Mouse = Look, Space = Jump, Shift = Sprint, Escape = Release Mouse");
+        Console.WriteLine("  Weapons: 1 = Pistol, 2 = Assault Rifle, LMB = Fire, R = Reload");
+        
+        // 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
+        Console.WriteLine($"[DEBUG] Ground cube material color: {groundRenderer.Material.Color}");
+        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 a few colored cubes (larger for easier targeting)
+    CreateCube(scene, "RedCube", new System.Numerics.Vector3(-3, 2, 0), 2.0f, Color.Red);
+    CreateCube(scene, "BlueCube", new System.Numerics.Vector3(0, 4, 0), 2.0f, Color.Blue);
+    CreateCube(scene, "YellowCube", new System.Numerics.Vector3(3, 3, 0), 2.0f, Color.Yellow);
+        
+        // Create enemy entities for AI testing (bright red, taller/skinnier to distinguish from cubes)
+        CreateEnemy(scene, playerEntity, "Enemy1", new System.Numerics.Vector3(-5, 2, -5), 1.0f, 0.0f, 0.0f);
+        CreateEnemy(scene, playerEntity, "Enemy2", new System.Numerics.Vector3(5, 2, -5), 1.0f, 0.0f, 0.0f);
+        
+        // Initialize the scene
+        scene.Initialize();
+        
+        Console.WriteLine($"[DEBUG] Scene has {scene.Entities.Count} entities after initialization");
+        foreach (var ent in scene.Entities)
+        {
+            Console.WriteLine($"[DEBUG]   - {ent.Name}: Active={ent.Active}, ComponentCount={ent.GetType().GetProperty("Components")?.GetValue(ent)}");
+        }
+        
+        // Make it the active scene (hacky but works for testing)
+        var activeSceneField = typeof(SceneManager).GetField("_activeScene", 
+            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+        activeSceneField?.SetValue(_sceneManager, scene);
+        
+        Console.WriteLine("[Phase 2 Test Scene] Created:");
+        Console.WriteLine("  - Player with FPS controller, weapons, health (100 HP), and HUD at (0, 2, 10)");
+        Console.WriteLine("  - Ground plane (20x0.5x20 static box)");
+        Console.WriteLine("  - 3 colored cubes with Health (50 HP each) for target practice");
+        Console.WriteLine("  - 2 enemy entities with AI (20 HP, 10 damage melee attacks)");
+        Console.WriteLine("  - Controls: WASD=Move, Mouse=Look, Space=Jump, Shift=Sprint, Esc=Menu");
+        Console.WriteLine("  - Weapons: 1=Pistol, 2=Assault Rifle, LMB=Fire, R=Reload");
+    }
+    
+    /// <summary>
+    /// Helper to create a cube entity.
+    /// </summary>
+    private void CreateCube(Core.Scenes.Scene scene, string name, 
+    System.Numerics.Vector3 position, float size, Color color)
+    {
+        var entity = new Core.Entities.Entity(name);
+        var transform = entity.AddComponent<Core.Components.Transform3D>();
+        transform.Position = position;
+        transform.LocalScale = new System.Numerics.Vector3(size);
+        
+    var renderer = entity.AddComponent<Core.Components.MeshRenderer>();
+    renderer.SetCube(1.0f, new System.Numerics.Vector4(color.R / 255f, color.G / 255f, color.B / 255f, 1.0f));
+        
+        var rigidbody = entity.AddComponent<Core.Components.Rigidbody>();
+        rigidbody.BodyType = Core.Plugins.Physics.BodyType.Static; // Static so cubes don't fall
+        rigidbody.Shape = new Core.Plugins.Physics.BoxShape(size, size, size);
+        rigidbody.Mass = 1.0f;
+        
+        // Add Health component for damage testing
+        var health = entity.AddComponent<Gameplay.Components.Health>();
+        health.MaxHealth = 50f;
+        health.DestroyOnDeath = false; // Keep cube visible after death for testing
+        
+        // Subscribe to death event
+        health.OnDeath += (damageInfo) =>
+        {
+            Console.WriteLine($"[Test Scene] {name} has been destroyed!");
+        };
+        
+        scene.AddEntity(entity);
+    }
+
+    /// <summary>
+    /// Helper to create an enemy entity with AI.
+    /// </summary>
+    private void CreateEnemy(Core.Scenes.Scene scene, Core.Entities.Entity player, string name, 
+        System.Numerics.Vector3 position, float r, float g, float b)
+    {
+        var entity = new Core.Entities.Entity(name);
+        entity.Tag = "Enemy";
+        
+        var transform = entity.AddComponent<Core.Components.Transform3D>();
+        transform.Position = position;
+        transform.LocalScale = new System.Numerics.Vector3(0.8f, 1.8f, 0.8f); // Taller, skinnier - more humanoid
+        
+        var renderer = entity.AddComponent<Core.Components.MeshRenderer>();
+        renderer.SetCube(1.0f, new System.Numerics.Vector4(r, g, b, 1.0f));
+        
+        var rigidbody = entity.AddComponent<Core.Components.Rigidbody>();
+        rigidbody.BodyType = Core.Plugins.Physics.BodyType.Static; // Static for now (Phase 3 will add dynamic movement)
+        rigidbody.Shape = new Core.Plugins.Physics.BoxShape(0.8f, 1.8f, 0.8f);
+        rigidbody.Mass = 70.0f; // Human-like weight
+        
+        // Add Health component
+        var health = entity.AddComponent<Gameplay.Components.Health>();
+        health.MaxHealth = 20f;
+        health.DestroyOnDeath = false; // Keep visible after death for testing
+        
+        // Subscribe to death event
+        health.OnDeath += (damageInfo) =>
+        {
+            Console.WriteLine($"[Test Scene] {name} was killed by {damageInfo.Attacker?.Name ?? "unknown"}!");
+        };
+        
+        // Add EnemyAI component
+        var enemyAI = entity.AddComponent<Gameplay.Components.EnemyAI>();
+        enemyAI.Target = player; // Set player as target
+        enemyAI.DetectionRange = 15f;
+        enemyAI.AttackRange = 2.5f;
+        enemyAI.MoveSpeed = 2.5f;
+        enemyAI.AttackDamage = 10f;
+        enemyAI.AttackCooldown = 2.0f;
+        
+        scene.AddEntity(entity);
+        
+        Console.WriteLine($"[Test Scene] Created enemy '{name}' at {position} targeting player");
+    }
+
+    /// <summary>
+    /// Load content (textures, models, sounds).
+    /// Similar to Unity's resource loading but more manual.
+    /// </summary>
+    protected override void LoadContent()
+    {
+        _spriteBatch = new SpriteBatch(GraphicsDevice);
+        
+        // TODO Phase 1: Load content via Content Pipeline
+        // TODO Phase 5: Load Gum UI screens
+        
+        Console.WriteLine("Content loaded (placeholder - Phase 1 will add actual content)");
+        
+        // Create Phase 2 test scene AFTER all services are initialized
+        CreateTestScene();
+        
+        // Load HUD content after scene is created
+        var playerEntity = _sceneManager?.ActiveScene?.FindEntitiesByTag("Player").FirstOrDefault();
+        if (playerEntity != null)
+        {
+            var hud = playerEntity.GetComponent<Gameplay.Components.HUD>();
+            hud?.LoadContent(GraphicsDevice, _spriteBatch);
+            Console.WriteLine("[Game] HUD content loaded");
+        }
+    }
+
+    /// <summary>
+    /// Update game logic.
+    /// This is called every frame, similar to Update() in Unity.
+    /// 
+    /// EDUCATIONAL NOTE - Fixed Timestep Physics:
+    /// We run physics on a fixed timestep (60 updates/sec) while
+    /// rendering runs at variable framerate. This ensures:
+    /// - Deterministic physics simulation
+    /// - Consistent behavior across different hardware
+    /// - Decoupled physics from rendering performance
+    /// Unity does this automatically. In MonoGame we manage it manually.
+    /// </summary>
+    /// <param name="gameTime">Provides timing information</param>
+    protected override void Update(GameTime gameTime)
+    {
+        // Update core services
+        var timeService = ServiceLocator.Get<ITimeService>();
+        var inputService = ServiceLocator.Get<IInputService>();
+        var physicsProvider = ServiceLocator.Get<IPhysicsProvider>();
+        
+        timeService.Update(gameTime);
+        inputService.Update();
+        
+        // Update active scene
+        var coreGameTime = new Core.Components.GameTime(
+            gameTime.TotalGameTime,
+            gameTime.ElapsedGameTime
+        );
+        
+        _sceneManager?.Update(coreGameTime);
+        
+        // Step physics simulation with fixed timestep
+        // We use the scaled delta time from TimeService to support slow-motion/time effects
+        // Guard against zero/negative timestep on first frame
+        if (timeService.DeltaTime > 0)
+        {
+            physicsProvider.Step(timeService.DeltaTime);
+        }
+
+        base.Update(gameTime);
+    }
+
+    /// <summary>
+    /// Draw/render the game.
+    /// MonoGame requires you to explicitly handle rendering.
+    /// Unity does this automatically based on cameras.
+    /// 
+    /// EDUCATIONAL NOTE - Rendering Pipeline:
+    /// 1. Clear backbuffer (prevent artifacts from previous frame)
+    /// 2. BeginFrame - Set up render state
+    /// 3. RenderScene - Draw all renderable entities
+    /// 4. EndFrame - Present to screen
+    /// Unity's rendering is automatic via Camera components.
+    /// MonoGame gives you full control (and responsibility).
+    /// </summary>
+    /// <param name="gameTime">Provides timing information</param>
+    protected override void Draw(GameTime gameTime)
+    {
+        GraphicsDevice.Clear(Color.CornflowerBlue);
+
+        // Get graphics provider
+        var graphics = ServiceLocator.Get<IGraphicsProvider>();
+        
+        // Begin frame
+        graphics.BeginFrame();
+        
+        // Find active camera in scene (attached to Player entity in Phase 2)
+        // TODO: Improve camera selection (support multiple cameras, camera priorities)
+        Core.Components.Camera? camera = null;
+        
+        // Try to find camera on Player entity first
+        var playerEntity = _sceneManager?.ActiveScene?.FindEntitiesByTag("Player").FirstOrDefault();
+        if (playerEntity != null)
+        {
+            camera = playerEntity.GetComponent<Core.Components.Camera>();
+        }
+        
+        // Fallback to MainCamera tag if no player camera found
+        if (camera == null)
+        {
+            var cameraEntity = _sceneManager?.ActiveScene?.FindEntitiesByTag("MainCamera").FirstOrDefault();
+            camera = cameraEntity?.GetComponent<Core.Components.Camera>();
+        }
+        
+        if (camera != null)
+        {
+            // Collect all renderable entities from the scene
+            var renderables = new System.Collections.Generic.List<IRenderable>();
+            
+            if (_sceneManager?.ActiveScene != null)
+            {
+                foreach (var entity in _sceneManager.ActiveScene.Entities)
+                {
+                    // MeshRenderer implements IRenderable
+                    var meshRenderer = entity.GetComponent<Core.Components.MeshRenderer>();
+                    if (meshRenderer != null)
+                    {
+                        renderables.Add(meshRenderer);
+                    }
+                }
+            }
+            
+            // Render the scene
+            graphics.RenderScene(camera, renderables);
+        }
+        else
+        {
+            Console.WriteLine("[Render] ERROR: No camera found!");
+        }
+        
+        // End frame (presents to screen)
+        graphics.EndFrame();
+        
+        // Draw crosshair (simple 2D overlay)
+        DrawCrosshair();
+        
+        // Draw scene entities (UI/debug overlay - Phase 5)
+        var coreGameTime = new Core.Components.GameTime(
+            gameTime.TotalGameTime,
+            gameTime.ElapsedGameTime
+        );
+        
+        _sceneManager?.Draw(coreGameTime);
+
+        base.Draw(gameTime);
+    }
+
+    /// <summary>
+    /// Draw a simple crosshair in the center of the screen.
+    /// This helps with aiming when the mouse cursor is hidden.
+    /// </summary>
+    private void DrawCrosshair()
+    {
+        if (_spriteBatch == null) return;
+
+        _spriteBatch.Begin();
+        
+        // Get screen center
+        int centerX = _graphics.PreferredBackBufferWidth / 2;
+        int centerY = _graphics.PreferredBackBufferHeight / 2;
+        
+        // Crosshair size
+        int crosshairSize = 10;
+        int thickness = 2;
+        int gap = 3;
+        
+        // Create a 1x1 white pixel texture for drawing lines
+        Texture2D pixel = new Texture2D(GraphicsDevice, 1, 1);
+        pixel.SetData(new[] { Color.White });
+        
+        // Draw horizontal line (left and right from center)
+        _spriteBatch.Draw(pixel, new Rectangle(centerX - crosshairSize - gap, centerY - thickness / 2, crosshairSize, thickness), Color.White);
+        _spriteBatch.Draw(pixel, new Rectangle(centerX + gap, centerY - thickness / 2, crosshairSize, thickness), Color.White);
+        
+        // Draw vertical line (top and bottom from center)
+        _spriteBatch.Draw(pixel, new Rectangle(centerX - thickness / 2, centerY - crosshairSize - gap, thickness, crosshairSize), Color.White);
+        _spriteBatch.Draw(pixel, new Rectangle(centerX - thickness / 2, centerY + gap, thickness, crosshairSize), Color.White);
+        
+        _spriteBatch.End();
+        
+        pixel.Dispose();
+    }
+    
+    /// <summary>
+    /// Cleanup when game exits.
+    /// Similar to Unity's OnApplicationQuit() or OnDestroy().
+    /// </summary>
+    protected override void UnloadContent()
+    {
+        // Unload scene
+        _sceneManager?.UnloadScene();
+        
+        // Shutdown services
+        ServiceLocator.Get<IPhysicsProvider>()?.Shutdown();
+        ServiceLocator.Get<IGraphicsProvider>()?.Shutdown();
+        
+        ServiceLocator.Clear();
+        
+        Console.WriteLine("MonoGame FPS shutdown complete");
+        
+        base.UnloadContent();
+    }
+}

+ 18 - 0
Shooter/Platforms/iOS/Info.plist

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleIdentifier</key>
+	<string>com.yourcompany.RolePlayingGame</string>
+	<key>CFBundleName</key>
+	<string>RolePlayingGame</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>10.6</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+	<key>CFBundleIconFile</key>
+	<string>RolePlayingGame.icns</string>
+</dict>
+</plist>

+ 42 - 0
Shooter/Platforms/iOS/Program.cs

@@ -0,0 +1,42 @@
+using Foundation;
+using UIKit;
+
+namespace Shooter.iOS
+{
+    [Register("AppDelegate")]
+    internal class AppDelegate : UIApplicationDelegate
+    {
+        private static ShooterGame _game;
+
+        /// <summary>
+        /// Initializes and starts the game by creating an instance of the
+        /// Game class and invoking its Run method.
+        /// </summary>
+        internal static void RunGame()
+        {
+            _game = new ShooterGame();
+            _game.Run();
+        }
+
+        /// <summary>
+        /// Called when the application has finished launching.
+        /// This method starts the game by calling RunGame.
+        /// </summary>
+        /// <param name="app">The UIApplication instance representing the application.</param>
+        public override void FinishedLaunching(UIApplication app)
+        {
+            RunGame();
+        }
+
+        /// <summary>
+        /// The main entry point for the application.
+        /// This sets up the application and specifies the UIApplicationDelegate
+        /// class to handle application lifecycle events.
+        /// </summary>
+        /// <param name="args">Command-line arguments passed to the application.</param>
+        static void Main(string[] args)
+        {
+            UIApplication.Main(args, null, typeof(AppDelegate));
+        }
+    }
+}

+ 39 - 0
Shooter/Platforms/iOS/Shooter.csproj

@@ -0,0 +1,39 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net9.0-ios</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>Shooter.iOS</RootNamespace>
+    <AssemblyName>Shooter</AssemblyName>
+    <AssemblyTitle>Shooter - iOS</AssemblyTitle>
+    <AssemblyDescription>MonoGame Port of Unity's Shooter to iOS</AssemblyDescription>
+    <AssemblyCompany>MonoGame Samples</AssemblyCompany>
+    <AssemblyProduct>Shooter</AssemblyProduct>
+    <AssemblyCopyright>MonoGame Foundation</AssemblyCopyright>
+    <AssemblyVersion>1.0.0.0</AssemblyVersion>
+    <FileVersion>1.0.0.0</FileVersion>
+    <RuntimeIdentifier>ios-arm64</RuntimeIdentifier>
+    <UseInterpreter>true</UseInterpreter>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <MonoGamePlatform>iOS</MonoGamePlatform>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\Shooter.Core\Shooter.Core.csproj" />
+    <ProjectReference Include="..\..\Shooter.Physics\Shooter.Physics.csproj" />
+    <ProjectReference Include="..\..\Shooter.Graphics\Shooter.Graphics.csproj" />
+    <ProjectReference Include="..\..\Shooter.Gameplay\Shooter.Gameplay.csproj" />
+    <ProjectReference Include="..\..\Shooter.UI\Shooter.UI.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.iOS" Version="3.8.*" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
+  </ItemGroup>
+
+  <!-- Content pipeline disabled until Phase 1 - no assets yet -->
+  <!-- <ItemGroup>
+    <MonoGameContentReference Include="Content\Content.mgcb" />
+  </ItemGroup> -->
+
+</Project>

BIN
Shooter/Platforms/iOS/Shooter.icns


+ 51 - 13
Shooter/Shooter.sln

@@ -1,27 +1,35 @@
-
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 17
 VisualStudioVersion = 17.0.31903.59
 MinimumVisualStudioVersion = 10.0.40219.1
-
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter.Core", "Shooter.Core\Shooter.Core.csproj", "{A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}"
 EndProject
-
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter.Physics", "Shooter.Physics\Shooter.Physics.csproj", "{B2C3D4E5-F6A7-5B6C-9D0E-2F3A4B5C6D7E}"
 EndProject
-
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter.Graphics", "Shooter.Graphics\Shooter.Graphics.csproj", "{C3D4E5F6-A7B8-6C7D-0E1F-3A4B5C6D7E8F}"
 EndProject
-
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter.Gameplay", "Shooter.Gameplay\Shooter.Gameplay.csproj", "{D4E5F6A7-B8C9-7D8E-1F0A-4B5C6D7E8F9A}"
 EndProject
-
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter.UI", "Shooter.UI\Shooter.UI.csproj", "{E5F6A7B8-C9D0-8E9F-0A1B-5C6D7E8F9A0B}"
 EndProject
-
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter", "Platforms\Desktop\Shooter.csproj", "{F6A7B8C9-D0E1-9F0A-1B2C-6D7E8F9A0B1C}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platforms", "Platforms", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Android", "Android", "{701B9207-4D9B-44F7-8268-954E67FE853C}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "iOS", "iOS", "{1D8A945F-ED61-4CED-A588-067276909A8B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Desktop", "Desktop", "{2474D12A-904D-4D4C-9444-69356446E76F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windows", "Windows", "{D618A9B5-6DAA-4E3D-9386-9716B7946B85}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter", "Platforms\Windows\Shooter.csproj", "{3A600D32-E361-F396-2DC3-DFF1368B4ABE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter", "Platforms\Android\Shooter.csproj", "{451C0551-1586-C5BD-EBBE-EC964B54E639}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter", "Platforms\Desktop\Shooter.csproj", "{A7D31C58-C281-9161-11FF-1FC9E59ED6A2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shooter", "Platforms\iOS\Shooter.csproj", "{3F4F46BF-2FA6-438E-591C-A7B82E9D8F3F}"
 EndProject
-
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -48,9 +56,39 @@ Global
 		{E5F6A7B8-C9D0-8E9F-0A1B-5C6D7E8F9A0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E5F6A7B8-C9D0-8E9F-0A1B-5C6D7E8F9A0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{E5F6A7B8-C9D0-8E9F-0A1B-5C6D7E8F9A0B}.Release|Any CPU.Build.0 = Release|Any CPU
-		{F6A7B8C9-D0E1-9F0A-1B2C-6D7E8F9A0B1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{F6A7B8C9-D0E1-9F0A-1B2C-6D7E8F9A0B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{F6A7B8C9-D0E1-9F0A-1B2C-6D7E8F9A0B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{F6A7B8C9-D0E1-9F0A-1B2C-6D7E8F9A0B1C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3A600D32-E361-F396-2DC3-DFF1368B4ABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3A600D32-E361-F396-2DC3-DFF1368B4ABE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3A600D32-E361-F396-2DC3-DFF1368B4ABE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3A600D32-E361-F396-2DC3-DFF1368B4ABE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{451C0551-1586-C5BD-EBBE-EC964B54E639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{451C0551-1586-C5BD-EBBE-EC964B54E639}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{451C0551-1586-C5BD-EBBE-EC964B54E639}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+		{451C0551-1586-C5BD-EBBE-EC964B54E639}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{451C0551-1586-C5BD-EBBE-EC964B54E639}.Release|Any CPU.Build.0 = Release|Any CPU
+		{451C0551-1586-C5BD-EBBE-EC964B54E639}.Release|Any CPU.Deploy.0 = Release|Any CPU
+		{A7D31C58-C281-9161-11FF-1FC9E59ED6A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A7D31C58-C281-9161-11FF-1FC9E59ED6A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A7D31C58-C281-9161-11FF-1FC9E59ED6A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A7D31C58-C281-9161-11FF-1FC9E59ED6A2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3F4F46BF-2FA6-438E-591C-A7B82E9D8F3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3F4F46BF-2FA6-438E-591C-A7B82E9D8F3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3F4F46BF-2FA6-438E-591C-A7B82E9D8F3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3F4F46BF-2FA6-438E-591C-A7B82E9D8F3F}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{701B9207-4D9B-44F7-8268-954E67FE853C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+		{1D8A945F-ED61-4CED-A588-067276909A8B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+		{2474D12A-904D-4D4C-9444-69356446E76F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+		{D618A9B5-6DAA-4E3D-9386-9716B7946B85} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+		{3A600D32-E361-F396-2DC3-DFF1368B4ABE} = {D618A9B5-6DAA-4E3D-9386-9716B7946B85}
+		{451C0551-1586-C5BD-EBBE-EC964B54E639} = {701B9207-4D9B-44F7-8268-954E67FE853C}
+		{A7D31C58-C281-9161-11FF-1FC9E59ED6A2} = {2474D12A-904D-4D4C-9444-69356446E76F}
+		{3F4F46BF-2FA6-438E-591C-A7B82E9D8F3F} = {1D8A945F-ED61-4CED-A588-067276909A8B}
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {57F008B6-4451-44DB-9588-0886CBB61C95}
 	EndGlobalSection
 EndGlobal

Some files were not shown because too many files changed in this diff