Adding a Camera

So far we’ve set the World, View, and Transform matrix of each 3D object within that object. That works fine for these little demo projects, but once we start building a full-fledged game, we expect to look at everything in the world from the same perspective. This effectively means we want to use the same view and perspective matrices for all objects in a scene. Moreover, we want to move that perspective around in a well-defined manner.

What we want is a camera - an object that maintains a position and derives a view matrix from that position. Our camera also should provide a projection matrix, as we may want to tweak it in response to game activity - i.e. we might swap it for another matrix when the player uses a sniper rifle.

In fact, we may want multiple cameras in a game. We might want to change from a first-person camera to an overhead camera when the player gets into a vehicle, or we may want to present a fly-through of the level before the player starts playing. Since each of these may work in very different ways, let’s start by defining an interface of their common aspects.

The ICamera Interface

Those commonalities are our two matrices - the view and the perspective. Let’s expose them with read-only properties (properties with only a getter):

/// <summary>
/// An interface defining a camera
/// </summary>
public interface ICamera
{
    /// <summary>
    /// The view matrix
    /// </summary>
    Matrix View { get; }

    /// <summary>
    /// The projection matrix
    /// </summary>
    Matrix Projection { get; }
}

Now let’s define some cameras.

CirclingCamera

To start with, let’s duplicate something we’ve already done. Let’s create a camera that just spins around the origin. We’ll call it CirclingCamera:

/// <summary>
/// A camera that circles the origin 
/// </summary>
public class CirclingCamera : ICamera
{

}

We know from our previous work, we’ll need to keep track of the angle:

    // The camera's angle 
    float angle;

We might also hold a vector for the camera’s position:

    // The camera's position
    Vector3 position;

And a rotation speed:

    // The camera's speed 
    float speed;

And the Game (which we need to determine the aspect ratio of the screen):

    // The game this camera belongs to 
    Game game;

We’ll also define private backing variables for our view and perspective matrices:

    // The view matrix 
    Matrix view;

    // The projection matrix 
    Matrix projection;

And fulfill our interface by making them accessible as properties:

    /// <summary>
    /// The camera's view matrix 
    /// </summary>
    public Matrix View => view;
    
    /// <summary>
    /// The camera's projection matrix 
    /// </summary>
    public Matrix Projection => projection;

Then we can add our constructor:

    /// <summary>
    /// Constructs a new camera that circles the origin
    /// </summary>
    /// <param name="game">The game this camera belongs to</param>
    /// <param name="position">The initial position of the camera</param>
    /// <param name="speed">The speed of the camera</param>
    public CirclingCamera(Game game, Vector3 position, float speed) 
    {
        this.game = game;
        this.position = position;
        this.speed = speed;
        this.projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.PiOver4,
            game.GraphicsDevice.Viewport.AspectRatio,
            1,
            1000
        );
        this.view = Matrix.CreateLookAt(
            position,
            Vector3.Zero,
            Vector3.Up
        );
    }

This just sets our initial variables. Finally, we can write our update method:

    /// <summary>
    /// Updates the camera's positon
    /// </summary>
    /// <param name="gameTime">The GameTime object</param>
    public void Update(GameTime gameTime)
    {
        // update the angle based on the elapsed time and speed
        angle += speed * (float)gameTime.ElapsedGameTime.TotalSeconds;

        // Calculate a new view matrix
        this.view = 
            Matrix.CreateRotationY(angle) *
            Matrix.CreateLookAt(position, Vector3.Zero, Vector3.Up);
    }

Since our rotation is around the origin, we can simply multiply a lookat matrix by a rotation matrix representing the incremental change.

Refactoring Game1

Finally, we’ll need to add our camera to the Game1 class:

    // The camera 
    CirclingCamera camera;

Initialize it in the Game.LoadContent() method:

    // Initialize the camera 
    camera = new CirclingCamera(this, new Vector3(0, 5, 10), 0.5f);

Update it in the Game1.Update() method:

    // Update the camera 
    camera.Update(gameTime);

And in our draw method, we’ll need to supply this camera to our crate. Replace the line crate.Draw() with:

crate.Draw(camera);

Refactoring Crate

This of course means we’ll need to tweak the Draw method in Crate. Change it to this:

    /// <summary>
    /// Draws the crate
    /// </summary>
    /// <param name="camera">The camera to use to draw the crate</param>
    public void Draw(ICamera camera)
    {
        // set the view and projection matrices
        effect.View = camera.View;
        effect.Projection = camera.Projection;

        // apply the effect 
        effect.CurrentTechnique.Passes[0].Apply();
        
        // set the vertex buffer
        game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
        // set the index buffer
        game.GraphicsDevice.Indices = indexBuffer;
        // Draw the triangles
        game.GraphicsDevice.DrawIndexedPrimitives(
            PrimitiveType.TriangleList, // Tye type to draw
            0,                          // The first vertex to use
            0,                          // The first index to use
            12                          // the number of triangles to draw
        );
        
    }

Now if you run your code, you should find yourself circling the lit crate.