Basic 2D Camera

This chapter is a continuation of the shaders guide. Its advised you check that out first, but its not a necessity. This code should be easily modable into your own code. This guide wraps up the bare matrices and adds a few convienience functions to allow basic manipulation of the Camera's movement.

Camera Class

Create a new class with the following imports and members:

 
				 
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

 class  Camera
{
    Matrix      mProjection;
    Matrix      mView;

    Vector3     mPosition;
    Vector3     mUp;
    Vector3     mCenter;

    Vector3     mOffset;    
}
				
			

This camera will use orthagraphic projection. Define the constructor like below.

 
				 
public Camera(int width, int height, Vector3 pos, float zOffset = 10.0f)
{    
    mUp = Vector3.Up;
    mPosition = pos;
    mOffset = new Vector3(0,0,zOffset);
    mCenter = mPosition + mOffset;

    float nearClip = 1.0f;
    float farClip = -50.0f;

    Matrix.CreateOrthographic(width, height, nearClip, farClip, out mProjection);
    mView = Matrix.CreateLookAt(mPosition, mCenter, mUp);
}
				
			

The width and height are the dimensions of your viewport. Obviously this can be whatever size you desire, but most of the time its best kept to the dimensions of your window/rendering area.
Even though the offset isn't used directly, its useful for preserving the position of the camera in relation to its focal point when panning/moving.

Position & Passing to the Shader

Obviously, one of the perks of a camera is it makes moving the view easier, so define a SetPosition function:

 
				 
public void SetPosition(Vector3 pos)
{
    mPosition = pos;      
    mCenter = mPosition + mOffset;   
    mView = Matrix.CreateLookAt(mPosition, mCenter, mUp);
}
				
			

The last method we'll add is PassParamters. We'll define an Effect as a parameter, as this'll work with the shader from the shaders guide.

 
				 
public void PassParameters(Effect effect)
{
    effect.Parameters["View"].SetValue(mView);
    effect.Parameters["Projection"].SetValue(mProjection);           
}
				
			

Here's the code if you want to just use the default Monogame basic effect:

 
				 
public void PassParameters(BasicEffect effect)
{
    effect.View = mView;
    effect.Projection = mProjection;           
}
				
			

Full source for the camera.

Integrating the Camera

Now all thats left to do is use our camera. In the Game class, define a Camera member and remove the Projection and View matrices:

 
				 
public class Game1  : Game
    {
        GraphicsDeviceManager   graphics;
        Texture2D               boxman;
        Vector3                 boxmanPos = new Vector3(0,0,0);

        VertexBuffer           spriteVertexBuffer;
        Effect                 shader;

        Matrix                  World;

        Camera                camera;

        int screenWidth = 800;
        int screenHeight = 600;
				
			

Initialise method, note the removal of the View and Projection matrices:

 
				 
protected override void Initialize()
{
    camera = new Camera(screenWidth,screenHeight,Vector3.Zero);

    float width = 0.5f;
    float height = 0.5f;
    float repetitions = 1f;

    var spriteVertices = new VertexPositionNormalTexture[6];

    spriteVertices[0] = new VertexPositionNormalTexture( new Vector3(-width,height,0f), Vector3.Forward, new Vector2(0,0));
    spriteVertices[1] = new VertexPositionNormalTexture( new Vector3(-width,-height,0f),    Vector3.Forward, new Vector2(0,repetitions));
    spriteVertices[2] = new VertexPositionNormalTexture( new Vector3(width,-height,0f), Vector3.Forward, new Vector2(repetitions,repetitions));
    spriteVertices[3] = new VertexPositionNormalTexture( new Vector3(width,-height,0f), Vector3.Forward, new Vector2(repetitions,repetitions));
    spriteVertices[4] = new VertexPositionNormalTexture( new Vector3(width,height,0f),  Vector3.Forward, new Vector2(repetitions,0));
    spriteVertices[5] = new VertexPositionNormalTexture( new Vector3(-width,height,0f), Vector3.Forward, new Vector2(0,0));

    spriteVertexBuffer = new VertexBuffer(graphics.GraphicsDevice,typeof(VertexPositionNormalTexture),6,BufferUsage.WriteOnly);
    spriteVertexBuffer.Name = "Sprite Vertex Buffer";
    spriteVertexBuffer.SetData<VertexPositionNormalTexture>(spriteVertices);


    GraphicsDevice.RasterizerState = RasterizerState.CullClockwise;

    base.Initialize();
}
				
			

Finally, the draw method.

 
				 
 protected override void Draw(GameTime gameTime)
{   
    GraphicsDevice.Clear(Color.CornflowerBlue);
    World = Matrix.Identity;
    World *= Matrix.CreateScale(32);
    World *= Matrix.CreateTranslation(boxmanPos);
    shader.Parameters["World"].SetValue(World);

    camera.PassParameters(shader);

    shader.Parameters["SpriteSheet"].SetValue(boxman);

    GraphicsDevice.SetVertexBuffer(spriteVertexBuffer);

    foreach(EffectPass pass in shader.CurrentTechnique.Passes)
    {
        pass.Apply();
        GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList,0,4);
    }

   base.Draw(gameTime);
}
				
			

If you run, you should now see boxman once more drawn on screen as before.

Moving the camera

If we now alter our controls, we can move the camera instead of the sprite.

 
				 
protected override void Update(GameTime gameTime)
{
    if(Keyboard.GetState().IsKeyDown(Keys.Up))
        camera.SetPosition(camera.GetPos() + Vector3.Up);

    if(Keyboard.GetState().IsKeyDown(Keys.Down))
        camera.SetPosition(camera.GetPos() + Vector3.Down);

    if(Keyboard.GetState().IsKeyDown(Keys.Left))
        camera.SetPosition(camera.GetPos() + Vector3.Left);

    if(Keyboard.GetState().IsKeyDown(Keys.Right))
        camera.SetPosition(camera.GetPos() + Vector3.Right);

    base.Update(gameTime);
}
				
			

So, because we're now moving the camera instead of the sprite, he should move in the opposite direction to which you're pressing. Ie: camera moves up, he'll appear to move down.

Email: magellanicDev@gmail.com
Last Updated: 16/02/2019
Copyright: Magellanic Games LTD

Generated with SpeedyHtml.