Welcome to the third part of this tutorial.
You can download the project for all the three parts at the bottom of the page.
Today we’ll look at how to create a camera class for our engine, and how it will play along with the hero class we made.
The first thing we need to do is to create an IHero interface for the camera to query.
A nice way to do that (if you don’t have Resharper) is to enter the class designer for the project, right click the hero class and select “extract interface”.
Choose the rotation and position member to create the new interface.
It should look like this:
interface IHero
{
Vector3 Position { get; }
float Rotation { get; }
}
In the game class, right after we add the hero component, we’ll add the hero game service like this:
// Add the IHero service to the Game Services.
this.Services.AddService(typeof(IHero), hero);
Game services are objects with a typically long life cyclus. We need them when we want an object exposed to many other objects. It’s also important that we don’t expose our services too much. We don’ want our camera to change the position of our hero or our scene to change the projection matrix of our camera.
Now we can finally create the camera.
A 3D camera needs to expose a view matrix and a projection matrix for our scene.
The projection matrix typically doesn’t change a lot, so let’s concentrate on the view matrix.
To calculate the view matrix we need to know the position our camera is in, and what direction we are looking.
We get the hero’s position and rotation from our IHero game service.
The position is a point in 3d space, here represented as a Vector.
The rotation is in radians and we need that for calculating where the camera should look at.
Our camera class looks like this:
using Microsoft.Xna.Framework;
namespace XNA3DEngine
{
public class Camera : GameComponent, ICamera
{
private Vector3 _position;
private Vector3 _lookAt;
private Matrix _viewMatrix;
private Matrix _projectionMatrix;
private IHero hero;
public Camera(Game game) : base(game)
{
hero = (IHero)Game.Services.GetService(typeof(IHero));
}
public Vector3 Position
{
get { return this._position; }
set { this._position = value; }
}
public Vector3 LookAt
{
get { return this._lookAt; }
set { this._lookAt = value; }
}
public Matrix ViewMatrix
{
get { return this._viewMatrix; }
}
public Matrix ProjectionMatrix
{
get { return this._projectionMatrix; }
}
/// <param name="gameTime">Time elapsed since the last call to Microsoft.Xna.Framework.GameComponent.Update(Microsoft.Xna.Framework.GameTime)</param>
public override void Update(GameTime gameTime)
{
updatePosition();
this._viewMatrix = Matrix.CreateLookAt(this._position, this._lookAt, Vector3.Up);
}
private void updatePosition()
{
// Gets the position of our hero from the IHero service.
this._position = hero.Position;
// Updates our cameras lookAt vector.
updateLookatVector();
}
private void updateLookatVector()
{
// Turn the camera in the direction that our hero is facing
Matrix rotation = Matrix.CreateRotationY(hero.Rotation);
// Gets the displacement vector.
Vector3 transformReference = Vector3.Transform(Vector3.Forward, rotation);
// Finally changes the lookAt vector. this wants to be right in front of the character.
// It's very important to change this every time the character moves or rotates.
// if the lookAt vector isn't directly in front of the character at all times,
// it moves out of sync (and kind of funny :D).
this._lookAt = _position + transformReference;
}
public override void Initialize()
{
this._projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(40.0f),
this.Game.GraphicsDevice.DisplayMode.AspectRatio,
0.1f,
10000.0f);
base.Initialize();
}
}
We need our scene to know about the camera class, so we have to expose the camera as a service too.
Create this interface for our camera:
interface ICamera
{
Matrix ProjectionMatrix { get; }
Matrix ViewMatrix { get; }
}
Now we have to add both the camera game component and the game service.
Add these lines between our hero and scene initialization code:
// Add our camera class
Camera camera = new Camera(this);
this.Components.Add(camera);
// Add the ICamera service to the Game Services.
this.Services.AddService(typeof(ICamera), camera);
This is great, now our hero checks for input in it’s update method and our camera positions itself in its update method. The only thing that’s missing now, is to update our scene class to actually use our camera’s view and projection matrix.
Add this as a private field to our scene class:
private ICamera camera;
In the initialize method, get a reference to the camera object like so:
camera = (ICamera)Game.Services.GetService(typeof(ICamera));
Now change the Draw method:
public override void Draw(Microsoft.Xna.Framework.GameTime gameTime)
{
foreach (ModelMesh mesh in scene.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.View = camera.ViewMatrix;
effect.Projection = camera.ProjectionMatrix;
}
mesh.Draw();
}
base.Draw(gameTime);
}
Hit F5 and voila! You can move the character around with the arrow keys.
That’s it for now. The next part will be a little more advanced as we are delving into collision detection.
Until then, here’s the code I promised you:
Download Solution - XNA3DEngine.zip