This is the complete code I am currently using for my camera. It is incomplete and only partially understood by me (I am working off an example), but I have managed to hack some functionality with some brute force, only to realize I went about it all wrong.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace _2DCameraPractice
{
class Camera
{
int speed;
int maxZoom;
float zoom;
float rotation;
bool moveToWorldPoint;
bool keyPressed;
Game game;
GraphicsDevice graphicsDevice;
Camera camera;
Matrix matrixTransform;
Vector2 cameraPosition;
Vector2 worldPoint;
KeyboardState currKeyboardState;
MouseState currMouseState;
MouseState prevMouseState;
public Camera(Game game, GraphicsDevice graphicsDevice)
{
this.game = game;
this.graphicsDevice = graphicsDevice;
zoom = 1;
maxZoom = 5;
rotation = 0;
cameraPosition = Vector2.Zero;
camera = this;
}
public float Zoom
{
get { return zoom; }
// Negative flips image
set { zoom = value; if (zoom < 0.1f) zoom = 0.1f; }
}
public float Rotation
{
get { return rotation; }
set { rotation = value; }
}
public Vector2 Position
{
get { return cameraPosition; }
set { cameraPosition = value; }
}
Vector2 CamCenterOffset
{
get { return new Vector2(game.Window.ClientBounds.Height
/ 2, game.Window.ClientBounds.Width / 2); }
}
Vector2 CamCenterInWorld
{
get { return cameraPosition + CamCenterOffset; }
}
Vector2 MouseCursorInWorld
{
get
{
currMouseState = Mouse.GetState();
return cameraPosition + new Vector2(currMouseState.X,
currMouseState.Y);
}
}
public void Direction(Vector2 direction)
{
cameraPosition += direction;
}
public Matrix Transform(GraphicsDevice graphicsDevice)
{
float ViewportWidth = graphicsDevice.Viewport.Width;
float ViewportHeight = graphicsDevice.Viewport.Height;
matrixTransform =
Matrix.CreateTranslation(new Vector3(-cameraPosition.X, -cameraPosition.Y, 0)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(new Vector3(Zoom, Zoom, 0)) *
Matrix.CreateTranslation(
new Vector3(ViewportWidth * 0.5f, ViewportHeight * 0.5f, 0));
return matrixTransform;
}
//Zoom in to mouse cursor.
private Vector2 ZoomToMouse(Vector2 mousePos, Vector2 cameraPos)
{
Vector2 newCamPosition = cameraPos + ((mousePos - CamCenterOffset) / 3);
//Need to figure a way to center mouse on screen as zooming.
return newCamPosition;
}
private void CenterMouseOnZoom(Vector2 mousePos)
{
//The farther from center the cursor is, the more shifts it takes to center
bool shift = false;
int X = 0;
int Y = 0;
//If the X axis is within 100 units of center, jump once.
if ( (mousePos.X - CamCenterOffset.X) <= 100
&& (mousePos.X - CamCenterOffset.X) >= -100)
{
shift = true;
X = (int)CamCenterOffset.X;
//Mouse.SetPosition((int)GetCamCenterInView.X, Y);
}
if ((mousePos.Y - CamCenterOffset.Y) <= 100
&& (mousePos.Y - CamCenterOffset.Y) >= -100)
{
shift = true;
Y = (int)CamCenterOffset.Y;
}
if(shift)
Mouse.SetPosition(X, Y);
}
public void CameraControlls()
{
MouseControl();
KeyboardControl();
KeyboardSpecials();
//Camera special commands
if (moveToWorldPoint == true)
CenterCameraOnWorldPoint();
}
private void KeyboardSpecials()
{
//Output camera and mouse coordinate details.
if(currKeyboardState.IsKeyDown(Keys.Space))
{ //Bool check to prevent repeat outputs.
if(!keyPressed)
{
System.Console.WriteLine("Camera : " + cameraPosition);
System.Console.WriteLine("Zoom : " + camera.Zoom);
System.Console.WriteLine("InWorld: " + CamCenterInWorld + "Camera");
System.Console.WriteLine();
System.Console.WriteLine("WorldPT: " + worldPoint);
System.Console.WriteLine("InWOrld: " + MouseCursorInWorld + "Mouse");
System.Console.WriteLine();
System.Console.WriteLine();
keyPressed = true;
}
}
if(currKeyboardState.IsKeyUp(Keys.Space))
keyPressed = false;
}
private void KeyboardControl()
{
//Camera movement (Keyboard)
if(currKeyboardState.IsKeyDown(Keys.Right) || currKeyboardState.IsKeyDown(Keys.A))
{
camera.Direction(new Vector2(-(1 * speed), 0));
moveToWorldPoint = false;
}
if(currKeyboardState.IsKeyDown(Keys.Left) || currKeyboardState.IsKeyDown(Keys.D))
{
camera.Direction(new Vector2(1 * speed, 0));
moveToWorldPoint = false;
}
if(currKeyboardState.IsKeyDown(Keys.Up) || currKeyboardState.IsKeyDown(Keys.S))
{
camera.Direction(new Vector2(0, 1 * speed));
moveToWorldPoint = false;
}
if(currKeyboardState.IsKeyDown(Keys.Down) || currKeyboardState.IsKeyDown(Keys.W))
{
camera.Direction(new Vector2(0, -(1 * speed)));
moveToWorldPoint = false;
}
//Camera zoom (Keyboard)
if(currKeyboardState.IsKeyDown(Keys.Z))
camera.Zoom += 0.1f;
if(currKeyboardState.IsKeyDown(Keys.X))
camera.Zoom -= 0.1f;
//Camera rotation (Keyboard)
if(currKeyboardState.IsKeyDown(Keys.C))
camera.Rotation += 0.1f;
if(currKeyboardState.IsKeyDown(Keys.V))
camera.Rotation -= 0.1f;
if(currKeyboardState.IsKeyDown(Keys.Home))
camera.Rotation = 0.0f;
//Speed up scroll when zoomed way out.
if(camera.Zoom < 0.2)
speed = 1 + ((1 - (int)camera.Zoom) * 40);
else if(camera.Zoom < 0.4)
speed = 1 + ((1 - (int)camera.Zoom) * 20);
else if(camera.Zoom < 0.7)
speed = 1 + ((1 - (int)camera.Zoom) * 8);
else if(camera.Zoom < 1)
speed = 2 + ((1 - (int)camera.Zoom) * 4);
else
speed = 6 - (int)camera.Zoom;
if(speed < 1)
speed = 1;
}
private void MouseControl()
{
Vector2 mousePosition = new Vector2(currMouseState.X, currMouseState.Y);
//Enable moveTo for world Point on [L.CTRL + L.Click] if mouse is inside
// window bounds.
if (currMouseState.LeftButton == ButtonState.Pressed
&& mousePosition.X >= 0 && mousePosition.Y >= 0
&& mousePosition.X <= game.Window.ClientBounds.Width
&& mousePosition.Y <= game.Window.ClientBounds.Height)
{
//SetWorldPoint(MouseCursorInWorld);
SetWorldPoint(MouseCursorInWorld);
}
//Enable moveTo for world Point on [L.CTRL + L.Click]
if(currMouseState.LeftButton == ButtonState.Pressed
&& currKeyboardState.IsKeyDown(Keys.LeftControl))
//if (currKeyboardState.IsKeyDown(Keys.LeftControl))
{
moveToWorldPoint = true;
}
//Camera zoom (MouseWheel)
//Mouse wheel is (up to) ~120 points per click... No shit.
if(currMouseState.ScrollWheelValue < prevMouseState.ScrollWheelValue)
{
//Holding shift while zooming 10+ times more sensative.
if(currKeyboardState.IsKeyDown(Keys.LeftShift))
{
camera.Zoom += (float)((prevMouseState.ScrollWheelValue -
currMouseState.ScrollWheelValue) / 10) * 0.001f;
}
else
{
camera.Zoom -= (float)((prevMouseState.ScrollWheelValue -
currMouseState.ScrollWheelValue) / 10) * 0.015f;
}
}
if(currMouseState.ScrollWheelValue > prevMouseState.ScrollWheelValue)
{
if(camera.Zoom <= maxZoom)
{
if(currKeyboardState.IsKeyDown(Keys.LeftShift))
{
camera.Zoom -= (float)((currMouseState.ScrollWheelValue -
prevMouseState.ScrollWheelValue) / 10) * 0.001f;
}
else
{
camera.Zoom += (float)((currMouseState.ScrollWheelValue -
prevMouseState.ScrollWheelValue) / 10) * 0.015f;
//Reposition camera towards cursor durring zoom.
// cameraPosition = ZoomToMouse(mousePosition, cameraPosition);
// CenterMouseOnZoom(mousePosition);
}
}
}
prevMouseState = currMouseState;
}
private void SetWorldPoint(Vector2 worldPoint)
{
this.worldPoint = worldPoint;
}
private void CenterCameraOnWorldPoint()
{
int pixelsLarge = 10;
int pixelsFine = 1;
//Shift Left
if (worldPoint.X < (cameraPosition.X + CamCenterOffset.X)
&& (cameraPosition.X + CamCenterOffset.X) - worldPoint.X > 20)
cameraPosition.X -= pixelsLarge;
else
cameraPosition.X -= pixelsFine;
//Shift Right
if (worldPoint.X > (cameraPosition.X + CamCenterOffset.X)
&& worldPoint.X - (cameraPosition.X + CamCenterOffset.X) > 20)
cameraPosition.X += pixelsLarge;
else
cameraPosition.X += pixelsFine;
//Shift Up
if(worldPoint.Y < (cameraPosition.Y + CamCenterOffset.Y)
&& (cameraPosition.Y + CamCenterOffset.Y) - worldPoint.Y > 20)
cameraPosition.Y -= pixelsLarge;
else
cameraPosition.Y -= pixelsFine;
//Shift Down
if (worldPoint.Y > (cameraPosition.Y + CamCenterOffset.Y)
&& worldPoint.Y - (cameraPosition.Y + CamCenterOffset.Y) > 20)
cameraPosition.Y += pixelsLarge;
else
cameraPosition.Y += pixelsFine;
if(worldPoint.X - (cameraPosition.X + CamCenterOffset.X) < 5
&& worldPoint.Y - (cameraPosition.Y + CamCenterOffset.Y) < 5)
{
moveToWorldPoint = false;
}
}
public void Update(GameTime gameTime)
{
currKeyboardState = Keyboard.GetState();
currMouseState = Mouse.GetState();
this.CameraControlls();
}
}
}
Essentially what I am trying to do is make a 2D camera with these basic features:
- [optional] Zoom out straight, Zoom in towards mouse cursor (centering on it as it goes)
- [optional] Hotkey + L.Click, shift view to click location. (Farther zoom = faster shift)
- L.Click, Select world object, at any zoom level.
- WASD & Arrow, view move(s).
As you can see I managed to hack in some very quirky examples of these (have not dome arrow select yet), but they are not working as I expected and I am sure it has to do with the Matrix transform, and my lack of understanding it in code terms. (I have read up on it in many places)
For example. At zoom 1, everything pretty much works, and X,Y coordinates line up with window resolution as it should. But if I zoom in or out, X,Y coordinates do not scale with the new size of the viewable world space. What was once 600x600 pixels is now 60x60 (at zoom 0.1) but clicking in the far bottom right corner, still returns a world vector of ~600x600 rather then the much larger distance.
I have looked for HOURS to find a good explanation on how to code the matrix in XNA, as well as why you would use one of the overloads vs. another. But all I find are simple static JRPG and platformer camera examples. Or first person shooter type cameras.
Now before you answer I hope you will understand when I say that I am a stare and compare learner. If I have a working example of code, with a minimum of explanation, I can infer most of the rest and figure out hat remains by poking it and seeing what happens. So if anyone would be willing to simply adjust the code I have posted WITH VERBOSE COMMENTING, It would help me most. But I will be grateful for any assistance you are willing to give.