Microsoft XNA Framework; Creating a freelook camera
Before we continue with some real exciting stuff, we'll need to create a more interactive environment for us to navigate through. Right now, every 3d model in the world space is transformed to the screen using its own View and Projection matrices. These matrices define the Viewing Frustum of the scene (projection matrix), and the remaining transformations of the perspective (view matrix). It would be much more efficient and versatile to create a controllable camera component that has its own view and projection.
A Viewing Frustum can best be described as a region in the world space that appears on the screen. The shape of this region resembles that of a pyramid with the top and base cut off. The following image visualizes a Viewing Frustum. The planes that cut the frustum are called the near plane and far plane. Objects located closer to the camera than the near plane or farther from the camera as the far plane are not drawn. When defining a Projection matrix, the slope of the pyramid is determent by the Field of View argument, and the width and height of the near plane are considered to be the aspect ratio.

Let’s start by defining a simple Camera GameComponent that has the two matrices we need. I’ve set the View matrix to an Identity matrix because we don’t have any logic that determents the view yet.
-
public class Camera
-
{
-
protected float FOV = MathHelper.Pi / 3;
-
protected float aspectRatio = 1;
-
protected float nearClip = 1.0f;
-
protected float farClip = 10000000.0f;
-
-
protected Quaternion cameraRotation;
-
protected Vector3 cameraPosition;
-
-
public Quaternion Rotation
-
{
-
get
-
{
-
return this.cameraRotation;
-
}
-
set
-
{
-
this.cameraRotation = value;
-
}
-
}
-
public Vector3 Position
-
{
-
get
-
{
-
return this.cameraPosition;
-
}
-
set
-
{
-
this.cameraPosition = value;
-
}
-
}
-
public Matrix Projection
-
{
-
get
-
{
-
return Matrix.CreatePerspectiveFieldOfView(this.FOV, this.aspectRatio, this.nearClip, this.farClip);
-
}
-
}
-
public Matrix View
-
{
-
get
-
{
-
return Matrix.Identity;
-
}
-
}
-
-
public Camera()
-
{
-
this.cameraPosition = Vector3.Zero;
-
}
-
}
As you can see I’ve mapped four local members to the arguments of the Matrix.CreatePerspectiveFieldOfView method. I’ve placed the far plane really far from the camera to ensure every object in the 3d world is rendered. Since we want to be able to control our camera, we need a couple of methods to rotate and move it. I’ve chosen to let the rotations rely on Quaternions. Quaternions are basically matrices that store the computational members in the W member instead of directly transforming to coordinates. This is very convenient because it means the quaternion multiplication order isn’t relevant, and it prevents Gimble lock, a problem that occurs when trying to rotate multiple axis that interfere.
-
protected float yaw;
-
protected float pitch;
-
protected float roll;
-
-
public Camera()
-
{
-
this.cameraPosition = Vector3.Zero;
-
-
this.yaw = 0;
-
this.pitch = 0;
-
this.roll = 0;
-
}
-
-
public void Rotate(float xRotation, float yRotation, float zRotation)
-
{
-
this.yaw += xRotation;
-
this.pitch += yRotation;
-
this.roll += zRotation;
-
-
�
-
this.cameraRotation = q1 * q2 * q3;
-
}
-
-
public void Translate(Vector3 distance)
-
{
-
Vector3 diff = Vector3.Transform(distance, Matrix.CreateFromQuaternion(this.cameraRotation));
-
this.cameraPosition += diff;
-
}
The Rotate method creates 3 quaternions for each axis, and rotates each quaternion depending on the locally stored Yaw, Pitch and Roll members. Yaw, Pitch and Roll typically define rotations around the X, Y and Z axis in flight dynamics and computer graphics.
The Translate method uses the Vector3.Transform method to create a normal vector directing towards the rotation of the given quaternion. The position matrix can then be translated through world space in the right direction.
With that in place, we still need to update the View property to return a Matrix that has the rotation and translation, and we need a camera manager that listens to our input device and transforms the camera accordingly. The following snippet contains the updated view property, followed by the Camera Manager class.
-
public Matrix View
-
{
-
get
-
{
-
return Matrix.Invert(Matrix.CreateFromQuaternion(this.Rotation) * Matrix.CreateTranslation(this.Position));
-
}
-
}
-
public class CameraManager : Microsoft.Xna.Framework.GameComponent, ICameraManagerService
-
{
-
protected Vector2 offMousePos;
-
protected Vector2 preMousePos;
-
protected Camera camera;
-
-
public CameraManager(Game game)
-
: base(game)
-
{
-
{
-
}
-
-
}
-
-
public Camera Camera
-
{
-
get
-
{
-
return this.camera;
-
}
-
}
-
-
public override void Initialize()
-
{
-
// TODO: Add your initialization code here
-
IInputManagerService inputManagerService = (IInputManagerService)this.Game.Services.GetService(typeof(IInputManagerService));
-
{
-
}
-
-
base.Initialize();
-
}
-
-
protected void inputManagerService_OnKeyDown(InputManager sender, List keys)
-
{
-
// Variable that controls the speed
-
float speed = 35f;
-
-
if (keys.Contains(Keys.LeftShift))
-
speed *= 2;
-
-
// Check for specified key presses
-
if (keys.Contains(Keys.W))
-
-
if (keys.Contains(Keys.S))
-
-
if (keys.Contains(Keys.A))
-
-
if (keys.Contains(Keys.D))
-
}
-
-
public override void Update(GameTime gameTime)
-
{
-
// Retrieve the mousestate
-
MouseState ms = Mouse.GetState();
-
-
// Check if pressed the left mouse button
-
if (ms.LeftButton == ButtonState.Pressed)
-
{
-
// Rotate camera
-
this.camera.Rotate(offMousePos.X * 0.005f, offMousePos.Y * 0.005f, 0);
-
}
-
// Save the offset between mousecoordinates, and the current mouse pos
-
-
// TODO: Add your update code here
-
base.Update(gameTime);
-
}
-
}
Notice that the CameraManager implements an interface that allows access to the Camera itself, and that I’ve registered the service in the constructor of the class. That way, our 3d objects can retrieve the CameraManager instance and use the Camera property to read the View and Projection matrices. Also, in the Initialize method, I retrieved the previously created InputManager component and used the OnKeyDown event to listen to the keystrokes. The Update method contains logic to rotate the camera depending on the movement of the mouse. I’m not going to dive into this code since it should be pretty easy for you to understand. Add the CameraManager component to the components collection of our game, and run the application.
Oh oww! Nothing different seems to happen? How could this be? Well, we haven’t updated the code of our models yet to incorporate the View and Projection matrices of our camera. Update the code in the MySpaceWarShip component to retrieve the camera component through the interface type on the services collection, and use the correct matrices.
-
ICameraManagerService cameraManagerService = (ICameraManagerService)this.Game.Services.GetService(typeof(ICameraManagerService));
-
foreach (BasicEffect effect in mesh.Effects)
-
{
-
effect.World = Matrix.CreateRotationY(this.modelRotation) * Matrix.CreateTranslation(this.modelPosition) ;
-
effect.View = cameraManagerService.Camera.View;
-
effect.Projection = cameraManagerService.Camera.Projection;
-
effect.EnableDefaultLighting();
-
}
Congratulations, we now have a free look camera component that allows us the use the keyboard and mouse to ‘fly’ through the world space of our XNA project. The sources can be downloaded here; 05 - Creating a freelook camera
Leave a Reply