I'm making a strategy with top-down 2D graphics. I implemented the way to move the view by holding LMB and then "dragging" the screen. It works fine with the exception of the movement being a little too fast, and I have no idea how to calculate the movement properly.
The code for camera movement (and scale) is
class CameraControls : WGComponent
{
public override void Update(GameTime gameTime)
{
if (WGame.userInput.mouse.LeftButton == ButtonState.Pressed) WGame.camera.position -= (WGame.userInput.mouse.Position - WGame.userInput.prevMouse.Position).ToVector2().InvertY() * WGame.camera.scale;
WGame.camera.scale = Math.Max(WGame.camera.scale - WGame.userInput.frameScroll * 0.1f, 1f);
}
}
WGame
is my Game
class, which holds objects I expect to have only one instance as static variables. WGComponent
just overrides the constructor to use the WGame
instance stored in the static variable.
WGame.userInput.mouse
is the MouseState
for the current frame, while WGame.userInput.prevMouse
is for the previous frame. WGame.userInput.frameScroll
is calculated with frameScroll = mouse.ScrollWheelValue - prevMouse.ScrollWheelValue
The camera code is
public class Camera
{
public Vector2 position = new();
public float scale = 1;
}
And its position is used in WGame's Update method
protected override void Update(GameTime gameTime)
{
translationMatrix = Matrix.CreateTranslation(-camera.position.X, camera.position.Y, 0);
scaleMatrix = Matrix.CreateScale(100 / (camera.scale * camera.scale));
screenSizeMatrix = Matrix.CreateTranslation(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2, 0);
resultMatrix = translationMatrix * scaleMatrix * screenSizeMatrix; //This one is later used in SpriteBatch.Begin()
base.Update(gameTime);
}
So, am I doing everything right and if I do, is there a way to make the screen be dragged exactly as much as the mouse is moved?
[UPD] The camera scale calculation code is now WGame.camera.scale = Math.Max(WGame.camera.scale - WGame.userInput.frameScroll * MathF.Abs(WGame.userInput.frameScroll) * 0.01f, 1f);
Scale matrix calculation is scaleMatrix = Matrix.CreateScale(100 / camera.scale);
I quickly created a test project to verify the math. As I assumed, you have to 'apply' the same scale as for the scaleMatrix to your mouse movement, in this case dividing by 100. I would suggest to create a variable for that or even better, incorporate it in the scale variable.
At least for top down 2D sprites, a 'more correct' way to interpret scale would be:
Scale = 1: 1 screen pixel contains 1 texture pixel (100 %)
Scale = 0.5: 1 screen pixel contains 2 texture pixels (texture size is 50%)
Scale = 2: 1 texture pixel 'fills' 2 screen pixels (texture size 200%)
and so on.
Anyway, below is the code I tested for your case (I use a slightly different input class but the names should be self-explanatory). Furthermore, there you can see how to change the scale while avoiding the 'scaling becomes slower when bigger' problem (we multiply it by a signed factor and clamp it afterwards):
// Camera zoom
CamScale *= 1 - Sign(Input.MouseWheelDelta) * .25f;
CamScale = Math.Clamp(CamScale, 0.1f, 1f);
// Camera movement
if (Input.IsMiddleMouseButtonDown) {
CamPos.X -= Input.MousePositionDelta.X * CamScale / 100;
CamPos.Y += Input.MousePositionDelta.Y * CamScale / 100;
}
Matrix resultMatrix;
var translationMatrix = Matrix.CreateTranslation(-CamPos.X, CamPos.Y, 0);
var scaleMatrix = Matrix.CreateScale(100 / (CamScale));
var screenSizeMatrix = Matrix.CreateTranslation(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2, 0);
resultMatrix = translationMatrix * scaleMatrix * screenSizeMatrix;
SpriteBatch.Begin(transformMatrix: resultMatrix);
{
SpriteBatch.Draw(TestTexture, new Vector2(0, 0), Color.White);
}
SpriteBatch.End();