diff --git a/CollisionManager.cs b/CollisionManager.cs new file mode 100644 index 0000000..a337b72 --- /dev/null +++ b/CollisionManager.cs @@ -0,0 +1,45 @@ +using Microsoft.Xna.Framework; + +namespace LunaLightXMG +{ + public class CollisionManager + { + // Method to check if a position is colliding with any platform + public bool IsColliding(Vector2 newPosition, BoundingBox boundingBox, Platform[] platforms) + { + Rectangle newBounds = new Rectangle((int)newPosition.X, (int)newPosition.Y, boundingBox.Bounds.Width, boundingBox.Bounds.Height); + foreach (var platform in platforms) + { + if (newBounds.Intersects(platform.BoundingBox.Bounds)) + { + return true; + } + } + return false; + } + + // Method to check if a position is colliding with any platform and return the colliding platform + public Platform GetCollidingPlatform(Vector2 newPosition, BoundingBox boundingBox, Platform[] platforms) + { + Rectangle newBounds = new Rectangle((int)newPosition.X, (int)newPosition.Y, boundingBox.Bounds.Width, boundingBox.Bounds.Height); + foreach (var platform in platforms) + { + if (newBounds.Intersects(platform.BoundingBox.Bounds)) + { + return platform; + } + } + return null; + } + + // Method to check if a position is colliding with any wall + public bool WallCheck(Vector2 position, BoundingBox boundingBox, Platform[] platforms) + { + if (IsColliding(position + new Vector2(1, 0), boundingBox, platforms) || IsColliding(position + new Vector2(-1, 0), boundingBox, platforms)) + { + return true; + } + return false; + } + } +} diff --git a/Content/Content.mgcb b/Content/Content.mgcb index 0ff1bfd..255914e 100644 --- a/Content/Content.mgcb +++ b/Content/Content.mgcb @@ -25,6 +25,30 @@ /processorParam:TextureFormat=Color /build:CastleWall TS16.png +#begin debugGrid.PNG +/importer:TextureImporter +/processor:TextureProcessor +/processorParam:ColorKeyColor=255,0,255,255 +/processorParam:ColorKeyEnabled=True +/processorParam:GenerateMipmaps=False +/processorParam:PremultiplyAlpha=True +/processorParam:ResizeToPowerOfTwo=False +/processorParam:MakeSquare=False +/processorParam:TextureFormat=Color +/build:debugGrid.PNG + +#begin enemy.png +/importer:TextureImporter +/processor:TextureProcessor +/processorParam:ColorKeyColor=255,0,255,255 +/processorParam:ColorKeyEnabled=True +/processorParam:GenerateMipmaps=False +/processorParam:PremultiplyAlpha=True +/processorParam:ResizeToPowerOfTwo=False +/processorParam:MakeSquare=False +/processorParam:TextureFormat=Color +/build:enemy.png + #begin fonts/debugFont.spritefont /importer:FontDescriptionImporter /processor:FontDescriptionProcessor @@ -32,6 +56,24 @@ /processorParam:TextureFormat=Compressed /build:fonts/debugFont.spritefont +#begin grid.PNG +/importer:TextureImporter +/processor:TextureProcessor +/processorParam:ColorKeyColor=255,0,255,255 +/processorParam:ColorKeyEnabled=True +/processorParam:GenerateMipmaps=False +/processorParam:PremultiplyAlpha=True +/processorParam:ResizeToPowerOfTwo=False +/processorParam:MakeSquare=False +/processorParam:TextureFormat=Color +/build:grid.PNG + +#begin music/sinkBeats.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:music/sinkBeats.wav + #begin platform.png /importer:TextureImporter /processor:TextureProcessor diff --git a/Content/debugGrid.PNG b/Content/debugGrid.PNG new file mode 100644 index 0000000..24583de Binary files /dev/null and b/Content/debugGrid.PNG differ diff --git a/Content/enemy.png b/Content/enemy.png new file mode 100644 index 0000000..ac9877c Binary files /dev/null and b/Content/enemy.png differ diff --git a/Content/grid.PNG b/Content/grid.PNG new file mode 100644 index 0000000..95a1afc Binary files /dev/null and b/Content/grid.PNG differ diff --git a/Content/music/sinkBeats.wav b/Content/music/sinkBeats.wav new file mode 100644 index 0000000..0b01d30 Binary files /dev/null and b/Content/music/sinkBeats.wav differ diff --git a/Enemy.cs b/Enemy.cs new file mode 100644 index 0000000..d990d5a --- /dev/null +++ b/Enemy.cs @@ -0,0 +1,141 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace LunaLightXMG +{ + public class Enemy + { + private float hsp; // Horizontal speed + private float vsp; // Vertical speed + private float hsp_frac; // Horizontal fractional speed + private float vsp_frac; // Vertical fractional speed + private float vspJump; + private bool grounded; + private bool walled; + private float grv; + private Texture2D texture; + public Vector2 position; + public BoundingBox boundingBox { get; private set; } + private CollisionManager collisionManager; + private static Random random; + + public Enemy(Vector2 initPosition, float initHsp) + { + hsp = initHsp; + vsp = 0; + hsp_frac = 0; + vsp_frac = 0; + vspJump = -4f; + grounded = false; + walled = false; + grv = 0.2f; + position = initPosition; + boundingBox = new BoundingBox(new Rectangle((int)position.X, (int)position.Y, 8, 8)); + collisionManager = new CollisionManager(); + random = new Random(42); + } + + public void LoadContent(ContentManager content) + { + texture = content.Load("enemy"); + } + + public void Update(GameTime gameTime, Player player, Platform[] platforms) + { + Movement(); + boundingBox.Update(position, boundingBox.Bounds.Width, boundingBox.Bounds.Height); + DumpFractions(); + CollisionChecks(platforms, player); + } + + public void Draw(SpriteBatch spriteBatch) + { + spriteBatch.Draw(texture, position, Color.White); + } + + // Method Implementation ------------------------------------------------------------------------------- + private void Movement() + { + if (hsp == 0) { hsp = 1; } + if (walled) { hsp = -hsp; } + if (!grounded) { vsp += grv; } + if (RandomJump() && grounded) { vsp = vspJump; } + } + + private void CollisionChecks(Platform[] platforms, Player player) + { + // Horizontal collision prediction + float onePixh = Math.Sign(hsp); + if (collisionManager.IsColliding(position + new Vector2(hsp, 0), boundingBox, platforms)) + { + while (!collisionManager.IsColliding(position + new Vector2(onePixh, 0), boundingBox, platforms)) + { + position.X += onePixh; + } + hsp = 0; + } + position.X += hsp; // Horizontal move + + // Vertical collision prediction + float onePixv = Math.Sign(vsp); + if (collisionManager.IsColliding(position + new Vector2(0, vsp), boundingBox, platforms)) + { + while (!collisionManager.IsColliding(position + new Vector2(0, onePixv), boundingBox, platforms)) + { + position.Y += onePixv; + } + vsp = 0; + } + position.Y += vsp; // Vertical move + + // Grounded? + grounded = collisionManager.IsColliding(position + new Vector2(0, 1), boundingBox, platforms); + + // Walled? + walled = collisionManager.WallCheck(position, boundingBox, platforms); + + // Check for collision with player + if (boundingBox.Intersects(player.boundingBox) && player.vsp > 0) + { + // Player jumps on top of the enemy, enemy dies + Die(); + } + } + + // Enemy dies + private void Die() + { + // Logic for enemy death (e.g., remove from game, play animation, etc.) + // Here we just move the enemy off-screen as a simple example + position = new Vector2(-100, -100); + } + + private void DumpFractions() + { + if (grounded) + { + float adjustedPosX = (float)Math.Round(position.X); + float adjustedPosY = (float)Math.Round(position.Y); + position.X = adjustedPosX; + position.Y = adjustedPosY; + } + + if (vsp >= 0) + { + hsp += hsp_frac; + vsp += vsp_frac; + hsp_frac = hsp - (float)Math.Floor(hsp); + vsp_frac = vsp - (float)Math.Floor(vsp); + hsp -= hsp_frac; + vsp -= vsp_frac; + } + } + public static bool RandomJump() + { + return (1 + 2 * random.NextDouble() > 2) ? true : false; + } + } +} + diff --git a/Game1.cs b/Game1.cs index e30bb02..0bfa494 100644 --- a/Game1.cs +++ b/Game1.cs @@ -1,7 +1,10 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Media; using System; +using System.Collections.Generic; namespace LunaLightXMG { @@ -10,6 +13,7 @@ namespace LunaLightXMG // Class Objects private Player player; private PlayerInput playerInput; + private List enemies; private GraphicsDeviceManager graphics; private SpriteBatch spriteBatch; @@ -23,16 +27,21 @@ namespace LunaLightXMG bool resizing; // Test sprites - eventually handle in a sprite manager? - //Texture2D sprCave1Background; - //Texture2D sprCave1Foreground; - //Texture2D sprCave1Walls; - Texture2D platformTexture; + private Texture2D platformTexture; // Test platforms - Platform[] platforms; + private Platform[] platforms; + + // Music + private SoundEffect sinkBeats; + private SoundEffectInstance backgroundMusicInstance; // Debugging - SpriteFont debugFont; + private SpriteFont debugFont; + private Texture2D debugGrid; + + // Utility + private static Random random; public Game1() { @@ -46,8 +55,12 @@ namespace LunaLightXMG // Test player input and movement with abstracted player class Vector2 initPlayerPosition = new Vector2(163, 63); + Vector2 initEnemyPosition = new Vector2(16, 16); player = new Player(initPlayerPosition); playerInput = new PlayerInput(); + enemies = new List(); + + random = new Random(42); } private void OnClientSizeChanged(object sender, EventArgs eventArgs) @@ -71,6 +84,8 @@ namespace LunaLightXMG base.Initialize(); CalculateRenderDestination(); + // Play music + PlayBackgroundMusic(sinkBeats); // Test initialize platforms -- this should be abstracted to a class // Also, I'm assuming using a tool like tiled will prevent having to @@ -139,7 +154,110 @@ namespace LunaLightXMG }; } - private void CalculateRenderDestination() + + protected override void LoadContent() + { + spriteBatch = new SpriteBatch(GraphicsDevice); + + // Test platform + platformTexture = Content.Load("platform"); + // Music + sinkBeats = Content.Load("music/sinkBeats"); + // load player sprite + player.LoadContent(Content); // player sprite is now handled in player.cs + + // Spawn 10 enemies at the start of the level + for (int i = 0; i < 10; i++) + { + Vector2 spawnPosition = new Vector2(16 + i*3,16); + Enemy enemy = new Enemy(spawnPosition, GetRandomValueBetween1And3()); + enemy.LoadContent(Content); + enemies.Add(enemy); + } + + // Test Render Target + renderTarget = new RenderTarget2D(GraphicsDevice, retroWidth, retroHeight); + + // Debugging + debugFont = Content.Load("fonts/debugFont"); + debugGrid = Content.Load("debugGrid"); + } + + protected override void Update(GameTime gameTime) + { + // Temporary quit game - eventually handle in a pause menu class + if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) + Exit(); + + // Test player input and movement with abstracted player class + playerInput.Update(); + player.Update(gameTime, playerInput, platforms); + + foreach (var enemy in enemies) { + enemy.Update(gameTime, player, platforms); + } + + base.Update(gameTime); + } + + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(Color.CornflowerBlue); + + // Test Render Target - Draw sprites to the render target + GraphicsDevice.SetRenderTarget(renderTarget); + + // Test Sprite + spriteBatch.Begin(samplerState: SamplerState.PointClamp); + // Debug Grid + spriteBatch.Draw(debugGrid, new Vector2(0, 0), Color.Blue); + + // Test draw tiles + foreach (var platform in platforms) + { + platform.Draw(spriteBatch, platformTexture); // Assuming all tiles use the same texture for now + } + + // Test player input and movement with abstracted player class + player.Draw(spriteBatch); + + foreach (var enemy in enemies) + { + enemy.Draw(spriteBatch); + } + + spriteBatch.End(); + + + // Test Render Target - Draw render target to the screen + GraphicsDevice.SetRenderTarget(null); + spriteBatch.Begin(samplerState: SamplerState.PointClamp); + spriteBatch.Draw(renderTarget, renderDestination, Color.White); + spriteBatch.End(); + + // Debugging + DrawDebugInfo(); + + base.Draw(gameTime); + } + + // Method Implementation ------------------------------------------------------------------------------- + private void PlayBackgroundMusic(SoundEffect backgroundMusic) + { + if (backgroundMusicInstance == null || backgroundMusicInstance.State != SoundState.Playing) + { + if (backgroundMusicInstance != null) + { + backgroundMusicInstance.Dispose(); + } + + backgroundMusicInstance = backgroundMusic.CreateInstance(); + backgroundMusicInstance.IsLooped = true; + backgroundMusicInstance.Play(); + } + } + + private void CalculateRenderDestination() { Point size = GraphicsDevice.Viewport.Bounds.Size; @@ -158,77 +276,7 @@ namespace LunaLightXMG renderDestination.X = (size.X - renderDestination.Width) / 2; renderDestination.Y = (size.Y - renderDestination.Height) / 2; } - protected override void LoadContent() - { - spriteBatch = new SpriteBatch(GraphicsDevice); - //sprCave1Background = Content.Load("spr_cave1_background"); - //sprCave1Foreground = Content.Load("spr_Cave1_foreground"); - //sprCave1Walls = Content.Load("spr_Cave1_walls"); - // Test platform - platformTexture = Content.Load("platform"); - - // load player sprite - player.LoadContent(Content); // player sprite is now handled in player.cs - - // Test Render Target - renderTarget = new RenderTarget2D(GraphicsDevice, retroWidth, retroHeight); - - // Debugging - debugFont = Content.Load("fonts/debugFont"); - } - - protected override void Update(GameTime gameTime) - { - // Temporary quit game - eventually handle in a pause menu class - if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) - Exit(); - - // Test player input and movement with abstracted player class - playerInput.Update(); - player.Update(gameTime, playerInput, platforms); - - base.Update(gameTime); - } - - protected override void Draw(GameTime gameTime) - { - GraphicsDevice.Clear(Color.CornflowerBlue); - - // Test Render Target - Draw sprites to the render target - GraphicsDevice.SetRenderTarget(renderTarget); - - // Test Sprite - spriteBatch.Begin(samplerState: SamplerState.PointClamp); - // Draw sprites here - //spriteBatch.Draw(sprCave1Background,new Vector2(0,0),Color.White); - - // Test draw tiles - foreach (var platform in platforms) - { - platform.Draw(spriteBatch, platformTexture); // Assuming all tiles use the same texture for now - } - - // Test player input and movement with abstracted player class - player.Draw(spriteBatch); - - //spriteBatch.Draw(sprCave1Walls, new Vector2(0, 0), Color.White); - //spriteBatch.Draw(sprCave1Foreground, new Vector2(0, 0), Color.White); - spriteBatch.End(); - - - // Test Render Target - Draw render target to the screen - GraphicsDevice.SetRenderTarget(null); - spriteBatch.Begin(samplerState: SamplerState.PointClamp); - spriteBatch.Draw(renderTarget, renderDestination, Color.White); - spriteBatch.End(); - - // Debugging - DrawDebugInfo(); - - base.Draw(gameTime); - } - - // Debugging + private void DrawDebugInfo() { spriteBatch.Begin(); @@ -241,5 +289,10 @@ namespace LunaLightXMG spriteBatch.End(); } + + public static float GetRandomValueBetween1And3() + { + return (float)(1 + 2 * random.NextDouble()); + } } } diff --git a/LunaLightXMG.csproj b/LunaLightXMG.csproj index a7fb71d..2ff06b5 100644 --- a/LunaLightXMG.csproj +++ b/LunaLightXMG.csproj @@ -22,6 +22,9 @@ + + + diff --git a/Player.cs b/Player.cs index 31d2a46..8a6ced2 100644 --- a/Player.cs +++ b/Player.cs @@ -5,9 +5,8 @@ using Microsoft.Xna.Framework.Graphics; namespace LunaLightXMG { - internal class Player + public class Player { - // Declare variables private float hsp; // Horizontal speed public float vsp; // Vertical speed private float hsp_frac; // Hold fractional position data @@ -25,16 +24,13 @@ namespace LunaLightXMG private float grvWall; // Gravity when wall sliding private float vspMaxWall; // Maximum vertical speed when wall sliding private float vspJump; // Jump speed - - // Player initilization private Texture2D texture; public Vector2 position; - public BoundingBox BoundingBox { get; private set; } + public BoundingBox boundingBox { get; private set; } + private CollisionManager collisionManager; - // Constructor to initialize variables public Player(Vector2 initPosition) { - // Initialize your variables here hsp = 0; vsp = 0; hsp_frac = 0; @@ -52,34 +48,36 @@ namespace LunaLightXMG grvWall = 0.1f; vspMaxWall = 5f; vspJump = -4f; - position = initPosition; - BoundingBox = new BoundingBox(new Rectangle((int)position.X, (int)position.Y, 8, 16)); + boundingBox = new BoundingBox(new Rectangle((int)position.X, (int)position.Y, 8, 16)); + collisionManager = new CollisionManager(); } + public void LoadContent(ContentManager content) { texture = content.Load("player"); } - // Update method to handle movement logic public void Update(GameTime gameTime, PlayerInput input, Platform[] platforms) { - // Update player position Movement(input); - // Dump fractional position data. - DumpFractions(); - // Update the player's bounding box - BoundingBox.Update(position, 8, 16); - + boundingBox.Update(position, boundingBox.Bounds.Width, boundingBox.Bounds.Height); + DumpFractions(); CollisionChecks(platforms); } + public void Draw(SpriteBatch spriteBatch) + { + // Snap position to integer values + Vector2 drawPosition = new Vector2((int)position.X, (int)position.Y); + spriteBatch.Draw(texture, drawPosition, Color.White); + } + + // Method Implementation ------------------------------------------------------------------------------- private void Movement(PlayerInput input) { - // Handle jumping if (input.KeyJump && grounded) { - grounded = false; vsp = vspJump; } @@ -113,7 +111,41 @@ namespace LunaLightXMG vsp = MathHelper.Clamp(vsp, vspJump, vspMaxFinal); } - // Helper method to smoothly approach a target value + private void CollisionChecks(Platform[] platforms) + { + // Horizontal collision prediction + float onePixh = Math.Sign(hsp); + if (collisionManager.IsColliding(position + new Vector2(hsp, 0), boundingBox, platforms)) + { + while (!collisionManager.IsColliding(position + new Vector2(onePixh, 0), boundingBox, platforms)) + { + position.X += onePixh; + } + hsp = 0; + } + position.X += hsp; // Horizontal move + + // Vertical collision prediction + float onePixv = Math.Sign(vsp); + if (collisionManager.IsColliding(position + new Vector2(0, vsp), boundingBox, platforms)) + { + while (!collisionManager.IsColliding(position + new Vector2(0, onePixv), boundingBox, platforms)) + { + position.Y += onePixv; + } + vsp = 0; + } + position.Y += vsp; // Vertical move + + // Grounded? + grounded = collisionManager.IsColliding(position + new Vector2(0, 1), boundingBox, platforms); + + // Walled? + walled = collisionManager.WallCheck(position, boundingBox, platforms); + if (walled) + wasWalled = walled; + } + private float Approach(float start, float end, float shift) { if (start < end) @@ -133,92 +165,23 @@ namespace LunaLightXMG private void DumpFractions() { - // Movement Speeds - hsp += hsp_frac; - vsp += vsp_frac; - hsp_frac = hsp - (float)Math.Floor(hsp); - vsp_frac = vsp - (float)Math.Floor(vsp); - hsp -= hsp_frac; - vsp -= vsp_frac; - } - - // Collision Checks - maybe abstract to a class --------------------------------------------------------------- - private void CollisionChecks(Platform[] platforms) - { - // Horizontal collision prediction - float onePixh = Math.Sign(hsp); - if (IsColliding(position + new Vector2(hsp, 0), platforms)) + if (grounded) { - while (!IsColliding(position + new Vector2(onePixh, 0), platforms)) - { - position.X += onePixh; - } - hsp = 0; + float adjustedPosX = (float)Math.Round(position.X); + float adjustedPosY = (float)Math.Round(position.Y); + position.X = adjustedPosX; + position.Y = adjustedPosY; } - position.X += hsp; // Horizontal move - // Vertical collision prediction - float onePixv = Math.Sign(vsp); - if (IsColliding(position + new Vector2(0, vsp), platforms)) + if (vsp >= 0) { - while (!IsColliding(position + new Vector2(0, onePixv), platforms)) - { - position.Y += onePixv; - } - vsp = 0; + hsp += hsp_frac; + vsp += vsp_frac; + hsp_frac = hsp - (float)Math.Floor(hsp); + vsp_frac = vsp - (float)Math.Floor(vsp); + hsp -= hsp_frac; + vsp -= vsp_frac; } - position.Y += vsp; // Vertical move - - // Grounded? - grounded = IsColliding(position + new Vector2(0, 1), platforms); - - // Walled? - walled = WallCheck(platforms); - if (walled) - wasWalled = walled; - } - - private bool IsColliding(Vector2 newPosition, Platform[] platforms) - { - Rectangle newBounds = new Rectangle((int)newPosition.X, (int)newPosition.Y, BoundingBox.Bounds.Width, BoundingBox.Bounds.Height); - foreach (var platform in platforms) - { - if (newBounds.Intersects(platform.BoundingBox.Bounds)) - { - return true; - } - } - return false; - } - - private Platform GetCollidingPlatform(Vector2 newPosition, Platform[] platforms) - { - Rectangle newBounds = new Rectangle((int)newPosition.X, (int)newPosition.Y, BoundingBox.Bounds.Width, BoundingBox.Bounds.Height); - foreach (var platform in platforms) - { - if (newBounds.Intersects(platform.BoundingBox.Bounds)) - { - return platform; - } - } - return null; - } - - private bool WallCheck(Platform[] platforms) - { - if (IsColliding(position + new Vector2(1, 0), platforms) || IsColliding(position + new Vector2(-1, 0), platforms)) - { - return true; - } - return false; - } - // Collision checks end --------------------------------------------------------------------------------------------------------- - - public void Draw(SpriteBatch spriteBatch) - { - // Snap position to integer values - Vector2 drawPosition = new Vector2((int)position.X, (int)position.Y); - spriteBatch.Draw(texture, drawPosition, Color.White); } } } diff --git a/PlayerInput.cs b/PlayerInput.cs index 6e3169e..0b620f6 100644 --- a/PlayerInput.cs +++ b/PlayerInput.cs @@ -67,7 +67,7 @@ namespace LunaLightXMG GamePadState gamepadState = GamePad.GetState(PlayerIndex.One); if (gamepadState.IsConnected && gamepadState.Buttons.Start == ButtonState.Pressed) { - // TODO: Insert logic to destroy the objMouseJoyStick instance + // TODO: Insert logic to destroy the objMouseJoyStick instance Controller = true; } diff --git a/README.md b/README.md index 67bcd3b..463331b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # LunaLightXMG -A Sci-fi fantaasy retro style platformer. Play as Ro and Ama, questing through the magical and treacherous realms of Uluna. An evil sorcerer has been harvesting the spirits of the forest for their own mechanisms of tyrrany! You must save the spirits of the forest and stop the evil sorcerer!!! Your adventures take you through dark woods and long forgotten temples to the highest castle towers atop the the tallest peaks of Uluna. +A Sci-fi fantasy retro style platformer. Play as Ro and Ama, questing through the magical and treacherous realms of Uluna. An evil sorcerer has been harvesting the spirits of the forest for their own mechanisms of tyrrany! You must save the spirits of the forest and stop the evil sorcerer!!! Your adventures take you through dark woods and long forgotten temples to the highest castle towers atop the the tallest peaks of Uluna. [![LunaLight - First Look](https://img.youtube.com/vi/J_KjVaEGNk4/0.jpg)](https://www.youtube.com/watch?v=J_KjVaEGNk4)