Page 1 of 1

2D Camera in XNA

#1 bonyjoe  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 175
  • View blog
  • Posts: 548
  • Joined: 08-September 10

Posted 03 July 2011 - 12:35 PM

*
POPULAR

Many people when creating 2D games will struggle to simulate a camera, sometimes moving all objects in the game world during the update, or other times using an offset value to calculate the position at draw time. While both these solutions work, they can become fiddly in more complex situations and do not allow for camera rotation or zooming.

This tutorial will show you how to create a simple camera class that allows for movement, rotation and zoom. It will also give an example input class and method to transform the mouse coordinates so that object picking can still work correctly.

Fields
  protected float _zoom; //Camera Zoom Value
        protected Matrix _transform; //Camera Transform Matrix
        protected Matrix _inverseTransform; //Inverse of Transform Matrix
        protected Vector2 _pos; //Camera Position
        protected float _rotation; //Camera Rotation Value (Radians)
        protected Viewport _viewport; //Cameras Viewport
        protected MouseState _mState; //Mouse state
        protected KeyboardState _keyState; //Keyboard state
        protected Int32 _scroll; //Previous Mouse Scroll Wheel Value


Some general fields are needed for the camera, most are self explanatory and the last 3 are only needed if you wish to control the camera from within this class. The InverseTransform is needed if you use any kind of mouse interaction with objects that are being transformed by the camera.

Properties
public float Zoom
        {
            get { return _zoom; }
            set { _zoom = value; }
        }
        /// <summary>
        /// Camera View Matrix Property
        /// </summary>
        public Matrix Transform
        {
            get { return _transform; }
            set { _transform = value; }
        }
        /// <summary>
        /// Inverse of the view matrix, can be used to get objects screen coordinates
        /// from its object coordinates
        /// </summary>
        public Matrix InverseTransform
        {
            get { return _inverseTransform; }
        }
        public Vector2 Pos
        {
            get { return _pos; }
            set { _pos = value; }
        }
        public float Rotation
        {
            get { return _rotation; }
            set { _rotation = value; }
        }


The protected fields need public properties so that they can be accessed from outside of the class. I have kept the properties simple and not included any logic, all the logic is handled in the update method below.

Update Method
        public void Update()
        {
            //Call Camera Input
            Input();
            //Clamp zoom value
            MathHelper.Clamp(_zoom, 0.01f, 10.0f);
            //Clamp rotation value
            _rotation = ClampAngle(_rotation);
            //Create view matrix
            _transform =    Matrix.CreateRotationZ(_rotation) * 
                            Matrix.CreateScale(new Vector3(_zoom, _zoom, 1)) * 
                            Matrix.CreateTranslation(_pos.X, _pos.Y, 0);
            //Update inverse matrix
            _inverseTransform = Matrix.Invert(_transform);
        }


The update is fairly straight forward, but I will explain some things. The zoom is clamped because if a negative value is used to scale the scene then the scene will be flipped as well as scaled, this is not what we want. The ClampAngle function just ensures that the angle will be between pi and -pi, the source can be found in the class code at the bottom of the tutorial.

The main part of this update is calculating the transform matrix, I won't go into the maths too much but it basically creates a matrix by combining a rotation matrix, scale matrix and translation matrix. The translation is done last as this means that the scene will rotate around (0,0) in object coordinates.

Constructor
  public Camera2D(Viewport viewport)
        {
            _zoom = 1.0f;
            _scroll = 1;
            _rotation = 0.0f;
            _pos = Vector2.Zero;
            _viewport = viewport;
        }


The constructor is basic and just sets the camera values.

Using the Camera to Draw

XNA 4.0
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, null, cam.Transform);
//Draw Any Scene Stuff Here
spriteBatch.End();

XNA 3.1
spriteBatch.Begin(SpriteBlendMode.AlphaBlend,
                        SpriteSortMode.Immediate,
                        SaveStateMode.SaveState,
                        cam.Transform);
//Draw Any Scene Stuff Here
spriteBatch.End();


Drawing is simple, anything that you want to be manipulated by the camera should go between these begin and end calls. If you want a GUI or anything that won't be affected by the camera view you can draw between standard Begin() and End() calls.

Transforming Mouse to Object Coordinates
      mState = Mouse.GetState();
            Vector2 mouse = new Vector2(mState.X, mState.Y);
            mouse = Vector2.Transform(mouse, cam.InverseTransform);

To use the mouse to interact with objects that are shown using the camera, first the mousestate should be captured, the x and y coordinates must then be used to populate a Vector2. This Vector2 can then be transformed using the inverse of the camera transform matrix. Now the X and Y of the Vector2 are the X and Y coordinates of the Mouse in object space. You can use these coordinates in the same way you normally use the mouse X and Y coordinates.

That's pretty much all there is to it, this can be used in pretty much any 2D game that needs to scroll, zoom or rotate and as you can see it is fairly easy to implement. Here is the full class code including the ClampAngle method and an example Input method.

Full Class Code
#region Version History (1.0)
// 03.07.11 ~ Created
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace CameraTest
{
    public class Camera2D
    {
        #region Fields

        protected float _zoom;
        protected Matrix _transform;
        protected Matrix _inverseTransform;
        protected Vector2 _pos;
        protected float _rotation;
        protected Viewport _viewport;
        protected MouseState _mState;
        protected KeyboardState _keyState;
        protected Int32 _scroll;

        #endregion

        #region Properties

        public float Zoom
        {
            get { return _zoom; }
            set { _zoom = value; }
        }
        /// <summary>
        /// Camera View Matrix Property
        /// </summary>
        public Matrix Transform
        {
            get { return _transform; }
            set { _transform = value; }
        }
        /// <summary>
        /// Inverse of the view matrix, can be used to get objects screen coordinates
        /// from its object coordinates
        /// </summary>
        public Matrix InverseTransform
        {
            get { return _inverseTransform; }
        }
        public Vector2 Pos
        {
            get { return _pos; }
            set { _pos = value; }
        }
        public float Rotation
        {
            get { return _rotation; }
            set { _rotation = value; }
        }

        #endregion

        #region Constructor

        public Camera2D(Viewport viewport)
        {
            _zoom = 1.0f;
            _scroll = 1;
            _rotation = 0.0f;
            _pos = Vector2.Zero;
            _viewport = viewport;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Update the camera view
        /// </summary>
        public void Update()
        {
            //Call Camera Input
            Input();
            //Clamp zoom value
            _zoom = MathHelper.Clamp(_zoom, 0.0f, 10.0f);
            //Clamp rotation value
            _rotation = ClampAngle(_rotation);
            //Create view matrix
            _transform =    Matrix.CreateRotationZ(_rotation) * 
                            Matrix.CreateScale(new Vector3(_zoom, _zoom, 1)) * 
                            Matrix.CreateTranslation(_pos.X, _pos.Y, 0);
            //Update inverse matrix
            _inverseTransform = Matrix.Invert(_transform);
        }

        /// <summary>
        /// Example Input Method, rotates using cursor keys and zooms using mouse wheel
        /// </summary>
        protected virtual void Input()
        {
            _mState = Mouse.GetState();
            _keyState = Keyboard.GetState();
            //Check zoom
            if (_mState.ScrollWheelValue > _scroll)
            {
                _zoom += 0.1f;
                _scroll = _mState.ScrollWheelValue;
            }
            else if (_mState.ScrollWheelValue < _scroll)
            {
                _zoom -= 0.1f;
                _scroll = _mState.ScrollWheelValue;
            }
            //Check rotation
            if (_keyState.IsKeyDown(Keys.Left))
            {
                _rotation -= 0.1f;
            }
            if (_keyState.IsKeyDown(Keys.Right))
            {
                _rotation += 0.1f;
            }
            //Check Move
            if (_keyState.IsKeyDown(Keys.A))
            {
                _pos.X += 0.5f;
            }
            if (_keyState.IsKeyDown(Keys.D))
            {
                _pos.X -= 0.5f;
            }
            if (_keyState.IsKeyDown(Keys.W))
            {
                _pos.Y += 0.5f;
            }
            if (_keyState.IsKeyDown(Keys.S))
            {
                _pos.Y -= 0.5f;
            }
        }

        /// <summary>
        /// Clamps a radian value between -pi and pi
        /// </summary>
        /// <param name="radians">angle to be clamped</param>
        /// <returns>clamped angle</returns>
        protected float ClampAngle(float radians)
        {
            while (radians < -MathHelper.Pi)
            {
                radians += MathHelper.TwoPi;
            }
            while (radians > MathHelper.Pi)
            {
                radians -= MathHelper.TwoPi;
            }
            return radians;
        }

        #endregion
    }
}



Is This A Good Question/Topic? 5
  • +

Replies To: 2D Camera in XNA

#2 stayscrisp  Icon User is offline

  • フカユ
  • member icon

Reputation: 1000
  • View blog
  • Posts: 4,181
  • Joined: 14-February 08

Posted 07 July 2011 - 02:41 AM

Good work! This is extremely useful stuff :) Keep it up.
Was This Post Helpful? 0
  • +
  • -

#3 Kilorn  Icon User is offline

  • XNArchitect
  • member icon



Reputation: 1356
  • View blog
  • Posts: 3,528
  • Joined: 03-May 10

Posted 07 July 2011 - 05:34 AM

Well done, bonyjoe! Now if only I could convince Chris to create a separate section for XNA Tutorials.
Was This Post Helpful? 0
  • +
  • -

#4 KingofSpace  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 9
  • Joined: 04-August 13

Posted 06 December 2013 - 06:11 PM

Hello,
I hate to bump an old thread, but I am really thankful for this.
This was the only 2D camera tutorial that helped me get my game going. It was a really big leap for me, I was having lots of issues with implementing a camera system.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1