Create a 2D Camera in XNA GS 4.0

A couple of weeks ago I started a project to create a game in XNA, the main purpose being to teach myself the platform.  The goal is to create a hero siege game with RTS style input and gameplay, with a couple of queues from hero arenas like League of Legends.  If you’ve played many mods for Warcraft 3 or Starcraft 2 you may know the basic idea: You control a hero or two and try to defend your castle from waves of enemies that try to destroy it.  Your hero gains new abilities and levels as they kill enemies, and generally you buy items or earn them in mini-games which make your hero more powerful and able to keep pace with the increasingly tough enemy waves.

One of the things I wanted to have for this game was the ability to have a game map that was larger than the game’s viewport.  This way I could have a map that is large enough to be interesting, but still be able to zoom in and scroll around so the game elements are not too tiny.  To do this we need to understand the basics of matrix transformations.

The basic idea is that we can take a position on our world map (the large map which includes everything) and apply a transformation matrix to it and get what the position would be for a given viewport size and camera position.  It may sound complicated, but XNA has a lot of built in support for doing these types of transformations.  Still, it is good to have an understanding of it and I found Steve’s writeup on the subject to be very helpful:

Matrix Basics

Since this game is going to only be in 2D, I will be able to take advantage of XNA’s SpriteBatch class which has the ability to accept transformations for anything it draws. What I need is a class where I can store the camera’s current position, rotation, and zoom which can output the combined transformation matrix.  Once I have the matrix I will then pass it to SpriteBatch and let it do the heavy lifting.

public class Camera2d
   private const float zoomUpperLimit = 1.5f;
   private const float zoomLowerLimit = .5f;

   private float _zoom;
   private Matrix _transform;
   private Vector2 _pos;
   private float _rotation;
   private int _viewportWidth;
   private int _viewportHeight;
   private int _worldWidth;
   private int _worldHeight;        

   public Camera2d(Viewport viewport, int worldWidth, 
      int worldHeight, float initialZoom)
      _zoom = initialZoom;
      _rotation = 0.0f;
      _pos = Vector2.Zero;
      _viewportWidth = viewport.Width;
      _viewportHeight = viewport.Height;
      _worldWidth = worldWidth;
      _worldHeight = worldHeight;

   #region Properties

   public float Zoom
       get { return _zoom; }
           _zoom = value;
           if (_zoom < zoomLowerLimit)
              _zoom = zoomLowerLimit;
           if (_zoom > zoomUpperLimit)
              _zoom = zoomUpperLimit;

   public float Rotation
       get { return _rotation; }
       set { _rotation = value; }

   public void Move(Vector2 amount)
       _pos += amount;

   public Vector2 Pos
       get { return _pos; }
           float leftBarrier = (float)_viewportWidth *
                  .5f / _zoom;
           float rightBarrier = _worldWidth -
                  (float)_viewportWidth * .5f / _zoom;
           float topBarrier = _worldHeight -
                  (float)_viewportHeight * .5f / _zoom;
           float bottomBarrier = (float)_viewportHeight *
                  .5f / _zoom;
           _pos = value;
           if (_pos.X < leftBarrier)
               _pos.X = leftBarrier;
           if (_pos.X > rightBarrier)
               _pos.X = rightBarrier;
           if (_pos.Y > topBarrier)
               _pos.Y = topBarrier;
           if (_pos.Y < bottomBarrier)
               _pos.Y = bottomBarrier;


   public Matrix GetTransformation()
     _transform = 
        Matrix.CreateTranslation(new Vector3(-_pos.X, -_pos.Y, 0)) *
        Matrix.CreateRotationZ(Rotation) *
        Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) *
        Matrix.CreateTranslation(new Vector3(_viewportWidth * 0.5f,
            _viewportHeight * 0.5f, 0));

       return _transform;

Some quick notes about this class.  I have an upper and low bounds for my zoom so the game will be locked in a reasonable view size.  I am also confining my camera’s position to the world map’s bounds.

The most complicated part of this code is calculating the matrix transformation.   As the article above makes clear a huge benefit of calculating these transforms with matrices is that the properties of that math (Linear Algebra for anyone looking to learn more) let us simply multiply the transforms to get the cumulative transform.  Plus, XNA has built in classes to handle all the math for us.

Here is an example of how we can use this class.  Start by initializing the camera class with the world and view sizes you want, and setting the camera’s position to your desired starting location.  Then, by passing the camera’s transform matrix to SpriteBatch.Begin in the Draw method of our game loop, we will only see the section of map where the camera is.

                    null, null, null, null, null,

// Draw the background texture
   new Rectangle(0, 0, worldWidth, worldHeight),
   null, Color.White, 0f, Vector2.Zero, SpriteEffects.None, 1);


To moved the camera or adjust the zoom we just need to change the properties of the camera in the game loop’s Update method.

// Adjust zoom if the mouse wheel has moved
if (mouseStateCurrent.ScrollWheelValue > previousScroll)
   Camera.Zoom += zoomIncrement;
else if (mouseStateCurrent.ScrollWheelValue < previousScroll)
   Camera.Zoom -= zoomIncrement;

previousScroll = mouseStateCurrent.ScrollWheelValue;

// Move the camera when the arrow keys are pressed
Vector2 movement = Vector2.Zero;
Viewport vp = ScreenManager.Game.GraphicsDevice.Viewport;

if (keyboardState.IsKeyDown(Keys.Left))
if (keyboardState.IsKeyDown(Keys.Right))
if (keyboardState.IsKeyDown(Keys.Up))
if (keyboardState.IsKeyDown(Keys.Down))

Camera.Pos += movement * 20;

The last thing I want to mention is mouse (or touch) input.  Any coordinates we use for moving and keeping track of sprites in our world should be done using coordinates relative to the world, not our adjustable view.  This way we can do the transformation once when we call SpriteBatch.Begin.  However, we want the coordinates of our pointing device to be relative to what we are seeing in our view, not the whole world.

For example, if our camera is all the way zoomed in and is showing us the bottom right portion of our world, if we click in the top left corner of the zoomed in view and don’t adjust the position in any way, our game is going to assume we are using world coordinates like everything else and it will not be clicking on what you are looking at in the view.

We can’t just apply our camera’s transformation to our mouse coordinates because that transformation converts world coordinates to view coordinates.  We want the opposite: view into world.  Luckily we are using matrices to perform our transforms and XNA matrices have a built in method for performing the inverse of a matrix.

// Transform mouse input from view to world position
Matrix inverse = Matrix.Invert(Camera.GetTransformation());
Vector2 mousePos = Vector2.Transform(
   new Vector2(mouseStateCurrent.X, mouseStateCurrent.Y), inverse);

I also wanted to add a few more links to articles and discussions that I found useful when puzzling these concepts out.  Hopefully this will make this information easier to find if you need more help, and feel free to comment or ask questions in the comments.

Posted in XNA | 6 Comments

Hello world

My idea for this blog is for it to be a place where I can feature helpful programming techniques that I’ve found or developed, general programming discussion, and a place to showcase my games and other work.  It will likely only be a reference for myself, though my hope is that others may find it useful as well.  I don’t intend to have any schedule for posting, just thoughts I want to preserve as I have them.  Welcome all!

Posted in Uncategorized | Leave a comment