diff --git a/Camera2D.cs b/Camera2D.cs new file mode 100644 index 0000000..0fd4852 --- /dev/null +++ b/Camera2D.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace LunaLightXMG +{ + public class Camera2D + { + // Fields + private readonly Viewport viewport; + private float zoom; + private float rotation; + private Vector2 position; + private Rectangle levelBounds; + + // Getters and setters + public Vector2 Position + { + get => position; + set => position = value; + } + public float Zoom + { + get => zoom; + set => zoom = (int)Math.Floor(MathHelper.Clamp(value, 1f, 3f)); + } + public float Rotation + { + get => rotation; + set => rotation = value; + } + + // constructor + public Camera2D(int width, int height) + { + this.viewport = new Viewport(0, 0, width, height); + levelBounds = new Rectangle(0, 0, 320, 180); // Default + zoom = 1f; + rotation = 0f; + position = Vector2.Zero; + } + + public Matrix GetViewMatrix() + { + return + Matrix.CreateTranslation(new Vector3(-position, 0f)) * + Matrix.CreateRotationZ(rotation) * + Matrix.CreateScale(zoom, zoom, 1f) * + Matrix.CreateTranslation(new Vector3(viewport.Width * 0.5f, viewport.Height * 0.5f, 0f)); + } + + public void SetLevelBounds(Rectangle bounds) + { + levelBounds = bounds; + } + + public void Follow(Vector2 targetPostion) + { + // temp code + Vector2 smoothPosition = Vector2.Lerp(position, targetPostion, 0.1f); + position = smoothPosition; + ClampToBounds(); + } + + private void ClampToBounds() + { + // Calculate the cam's half viewport size + var halfViewportWidth = viewport.Width * 0.5f / zoom; + var halfViewportHeight = viewport.Height * 0.5f / zoom; + + // Clamp cam to level bounds + float minX = levelBounds.Left + halfViewportWidth; + float maxX = levelBounds.Right - halfViewportWidth; + float minY = levelBounds.Top + halfViewportHeight; + float maxY = levelBounds.Bottom - halfViewportHeight; + + // Set cam position within level bounds + position.X = MathHelper.Clamp(position.X, minX, maxX); + position.Y = MathHelper.Clamp(position.Y, minY, maxY); + } + } +} \ No newline at end of file diff --git a/LunaLightGame.cs b/LunaLightGame.cs index 243f93d..ac32ec4 100644 --- a/LunaLightGame.cs +++ b/LunaLightGame.cs @@ -5,6 +5,7 @@ using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Media; using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace LunaLightXMG { @@ -16,13 +17,12 @@ namespace LunaLightXMG private List enemies; private int numOfEnemies; private GraphicsDeviceManager graphics; + private Camera2D camera2D; private SpriteBatch spriteBatch; // Native retro scale private int retroWidth = 320; private int retroHeight = 180; - // private int retroWidth = 480; - // private int retroHeight = 270; // Test Render Target private RenderTarget2D renderTarget; @@ -47,7 +47,7 @@ namespace LunaLightXMG // Utility private static Random random; - + public LunaLightGame() { graphics = new GraphicsDeviceManager(this); @@ -70,26 +70,58 @@ namespace LunaLightXMG numOfPlatforms = 4; numOfEnemies = 10; } - + protected override void Initialize() { // Set initial window size graphics.PreferredBackBufferWidth = 1920; graphics.PreferredBackBufferHeight = 1080; graphics.ApplyChanges(); - + playerInput.HasControl = true; // May need to find a new home for this - + base.Initialize(); + + // Initialize camera + camera2D = new Camera2D(retroWidth, retroHeight); + // camera test - set level bounds + camera2D.SetLevelBounds(new Rectangle(0,0,480,270)); // default 320x180 + CalculateRenderDestination(); // Play music // PlayBackgroundMusic(sinkBeats); - + // Generate random platforms - test this idea out... platforms = PlatformGenerator.CreatePlatforms(numOfPlatforms); } - - + + // Testing camera ------------------------ + private void TestCameraWhilePlaying() + { + KeyboardState keyboardState = Keyboard.GetState(); + var KeyI = keyboardState.IsKeyDown(Keys.I) ? true : false; + var KeyO = keyboardState.IsKeyDown(Keys.O) ? true : false; + var KeyK = keyboardState.IsKeyDown(Keys.K) ? true : false; + var KeyL = keyboardState.IsKeyDown(Keys.L) ? true : false; + + if (KeyI) + { + camera2D.Zoom += 1f; + } + if (KeyO) + { + camera2D.Zoom -= 1f; + } + if (KeyK) + { + camera2D.Rotation -= 0.01f; + } + if (KeyL) + { + camera2D.Rotation += 0.01f; + } + } + // --------------------------------------- protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); @@ -129,23 +161,32 @@ namespace LunaLightXMG foreach (var enemy in enemies) { enemy.Update(gameTime, player, platforms); } - + + // Update camera position + camera2D.Follow(player.position); + // camera testing + TestCameraWhilePlaying(); + 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); + // Add camera to draw batch + spriteBatch.Begin( + transformMatrix: camera2D.GetViewMatrix(), + samplerState: SamplerState.PointClamp + ); + // Debug Grid // spriteBatch.Draw(debugGrid, new Vector2(0, 0), Color.Blue); // spriteBatch.Draw(sinkArea, new Vector2(0, 0), Color.White); - + // Test draw tiles foreach (var platform in platforms) { @@ -242,7 +283,10 @@ namespace LunaLightXMG spriteBatch.DrawString(debugFont, $"Grounded: {player.grounded}", new Vector2(10, 30), Color.White); spriteBatch.DrawString(debugFont, $"Walled: {player.walled}", new Vector2(10, 50), Color.White); spriteBatch.DrawString(debugFont, $"Position: {player.position}", new Vector2(10, 70), Color.White); - + spriteBatch.DrawString(debugFont, $"Camera Position: {camera2D.Position}", new Vector2(10, 90), Color.White); + spriteBatch.DrawString(debugFont, $"Camera Zoom: {camera2D.Zoom}", new Vector2(10, 110), Color.White); + spriteBatch.DrawString(debugFont, $"Camera Rotation: {camera2D.Rotation}", new Vector2(10, 130), Color.White); + spriteBatch.End(); } diff --git a/PlayerInput.cs b/PlayerInput.cs index 0b620f6..31e1f7d 100644 --- a/PlayerInput.cs +++ b/PlayerInput.cs @@ -73,7 +73,7 @@ namespace LunaLightXMG // Keyboard input KeyboardState keyboardState = Keyboard.GetState(); - + KeyLeft = keyboardState.IsKeyDown(Keys.A) ? 1 : 0; KeyRight = keyboardState.IsKeyDown(Keys.D) ? 1 : 0; KeyUp = keyboardState.IsKeyDown(Keys.W) ? 1 : 0; diff --git a/UpdateNotes/Camera.md b/UpdateNotes/Camera.md index d56b219..89b040e 100644 --- a/UpdateNotes/Camera.md +++ b/UpdateNotes/Camera.md @@ -2,9 +2,32 @@ Currentlly the camera is static, or non-existtent really.. we need to create a camera class that handles creating and managing a viewport that follows the player wihin the level. -- If the level is the same size as te viewport then the camera is static. +- If the level is the same size as the viewport then the camera is static. - We also want to have a zoom capability that zooms between 320x180 and 480x270. The current view is 320x180. - We should be able to use a lot of the code from LunaLightGML however we will need to create the class objects and restructure to work with MonoGame. +## March 16th, 2025 - JME + +Created intitial Camera2D class. We will need to add in code from GML for the smooth following effect, screen shake, etc. I did add some other stuff that is cool though. + +- Zoom, we can set the camera zoom between 0.1f and 10f. These values may need to be changed after testing. + +- Rotation, I thought this might be fun.. might come in handy for an effect later on. The camera is 2D but rotates on what woudl be the z-axis. + +- We can set the level bounds that the camera exists in when moving from level to level. + +## March 17th, 2025 - JME + +**Zoom Weirdness** +Testing shows some serious pixel distortion when zooming. Rotation is odd.. but could still be cool at somepoint, lets focus on fixing the zoom. + +I think the probelm is due to the fixed render target. We are changing the veiwport width and height when zooming but the render target stays the same. We are also zooming at sub-pixel values so it is possible to land on a zoom value of 1.5f for example which isn't pixel perfect and causes distortion. + +Solutions: + +- We could abandon zoom in the camera and control zoom by changing the render target values. +- We should in either case only zoom at integer values to preserve pixel-perfect zoom. + +Leaning towards abstracting the render target code to its own class and handling zoom there. Overall I think this will be better as it is what draws to the backbuffer. \ No newline at end of file