Movement
December 11th, 2006
The problem with 3d games is they are complex beasts, even very simple ones. The (so far unpublished) sample code I have written just to display simple objects isn’t a very good starting point as it relies on the programmer (that’s you) to understand an awful lot about what’s going on already. So I’ve taken a step back from simple objects to writing code that I feel is missing from most of the samples I have seen. Today I’m going to be writing about a simple camera control mechanism that will allow you to move around any objects you create or models you display, using controls familiar to anyone who has played Doom or Quake. This will then allow you to easily explore any 3d environments you create yourself.
The implementation of Draw() here simply draws a bunch of points, in a star-shape1. The code could draw anything, we’re not really interested in that. What we’re interested in is how these are projected into the world, and how we then move around them.
Transformations
Each object in a 3d world has a world transformation. This transformation is held in a matrix. If you don’t understand matrix math’s, do not worry as XNA takes care of the complexities. A world transformation lets the renderer know how to rotate the object, and where to move the object to in the world. When we talk about moving an object, we use the word “translate”. Translation is the act of moving all the vertices (corners) of an object by the same amount in either of the three axis.
!
To create a transformation matrix, the XNA Matrix struct (a bit like a C# class, only with more limitations) has various methods to help. For instance to create a transformation matrix that translated an object 5 units across the screen (along the x-axis) you would use:
Matrix translate=Matrix.CreateTranslation(5.0F,0.0F,0.0F);
Here the ‘F’s after the number indicate to use a floating point number. If you miss an ‘F’ off, the number “0.0” would default to a double-length floating point number, and your program would refuse to compile.
The world transformation places and orientates an object in the 3d world we are creating. However we still need to choose a vantage point to view this world. This vantage point can be likened to placing a video camera to record the scene, so we refer to our observation point as a camera. Placing a camera is done using a view transformation. The view transformation is another Matrix struct, but it is created using the Matrix.CreateLookAt() method. This method takes a Vector3 (a simple struct which places the x,y and z coordinates together into one place) as a position for the camera, another Vector3 structure as a point for the camera to look at, and another Vector3 indicating which direction is up.

To move our viewpoint around the scene we simply alter the position passed as the first parameter to Matrix.CreateLookAt() How do we alter the direction we are looking in though? There are various ways of doing this, but because we are going to emulate a first-person-shooter here we only need to look up and down, and left and right. There is no need to tilt the cameras viewpoint. To take the phrases from airoplanes, looking left and right is “yawing”, and up and down it “pitching”. Thus we record the current yaw and pitch the user has selected using the mouse.
To do this, we first create a vector which points in the default camera angle. This is directly into the screen, and is given by the vector (0.0, 0.0, -1.0). The reason the z-element is negative is because XNA (by default) uses a right-handed coordinate system. This means (as can be seen on the diagrams) that whilst the x-axis goes across, and the y-axis goes up, the z-axis actually comes out of the screen towards the person viewing the display.
This default view vector is then rotated around the x-axis (pitch) and then rotated around the y-axis (yaw). The order is important. If we reversed this, rotating around the y-axis first then the x-axis, we would get into situations where we would not be able to look up and down. Imagine rotating to the left by 90 degrees. If we then tried to look up and down by rotating around the x-axis we would fail, the orientation of the vector would not change.
There are ways around this problem, using quaternion math’s – we’ll save that for a (much) later article.
From the programs UpdateCamera() method
// Copy the camera's reference vector.
Vector3 cameraLookAtVector = cameraReferenceVector;
// Create a vector pointing the direction the camera is facing.
cameraLookAtVector = Vector3.Transform(cameraLookAtVector, Matrix.CreateRotationX(cameraPitch));
cameraLookAtVector = Vector3.Transform(cameraLookAtVector, Matrix.CreateRotationY(cameraYaw));
// Calculate the position the camera is looking at.
cameraLookAtVector += cameraPosition;
// Create a view matrix for the camera, using the camera position (the coordinates
// controlled by the keyboard) and a vector pointing in the direction the user has
// chosen (controlled by the mouse)
// The third parameter is a vector which points up - this indicates the direction that "up" is in
viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraLookAtVector, new Vector3(0.0f, 1.0f, 0.0f));
The variable cameraReferenceVector is defined already to be (0.0, 0.0, -1.0), which is the default camera orientation. cameraPitch and cameraYaw are the pitch and yaw of the camera, in radians.
The line cameraLookAtVector += cameraPosition; uses vector translation to add the now-rotated camera direction to the camera position, giving a vector that indicates the point the camera is looking at. If you think about this, it will only ever be 1.0 unit away from the camera position (as we have only rotated as a vector 1.0 unit long). This does not matter as there is no focusing involved here. Looking at a point 1cm or 1m away (whatever units you wish the units to mean) is the same as looking 100cm or 100m away, as long as its in exactly the same direction.
There is another transformation used, the projection matrix. This is initialised in the method InitialiseCamera, and basically instructs the render how to project the three-dimensional world onto the two-dimensional screen – basically how to squash it against the back of your screen so it appears to have perspective.
User input
Mouse-look
Next we need to control the camera’s position, pitch and yaw. We’ll tackle pitch and yaw first. We’ll rotate the camera using something called mouse look. This means moving the mouse around will move the direction the camera looks in. This will be done in the Update() method.
To do this we need to know how much, and in which direction, the mouse has moved since the last call to Update(). We do this by remembering the position the mouse was in when update was last called. We use the instance variables xMousePosPrev and yMousePosPrev for this purpose. We then find out where the mouse currently is (using a call to Mouse.GetState()) and work out the difference.
The difference horizontally is taken to relate to the yaw angle, the difference vertically is taken to relate to the pitch angle. In the code we simply the divide the positional difference by a value to get these angles:
// Retrieve the current state of the mouse (position and buttons)
MouseState mouseState = Mouse.GetState();
// Calculate change in mouse position
int dx = xMousePosPrev - mouseState.X;
int dy = yMousePosPrev - mouseState.Y;
cameraYaw += dx / 100.0F;
// Clamp yaw angle to -180 degrees to +180 degrees (with wrap-around)
if (cameraYaw <= -Math.PI)
{
cameraYaw += (float)(2 * Math.PI);
}
if (cameraYaw > Math.PI)
{
cameraYaw -= (float)(2 * Math.PI);
}
cameraPitch -= dy / 150.0F;
// Clamp pitch angle to -90 degrees to +90 degrees (with no wrap-around)
if (cameraPitch > Math.PI / 2)
{
cameraPitch = (float)(Math.PI / 2);
}
else if (cameraPitch < -Math.PI / 2)
{
cameraPitch = (float)(-Math.PI / 2);
}
CentreMouse();
(see the MouseInput() method.)
You’re probably thinking if we’re constantly looking at the mouse position, when the mouse reaches the edge of the screen we won’t be able to continue looking up or down. To counter this we call CentreMouse(). This method (defined at the bottom of Game1.cs) centres the mouse on the game window – as the mouse is hidden over the game window this will not be intrusive. However we need to ensure this is only done when the game window has the focus. To do this we attach to two event handlers in the class constructor:
Activated += new EventHandler(GameActivated);
Deactivated += new EventHandler(GameDeactivated);
This ensures that the objects methods GameActivated() and GameDeactivated() are called when the game is brought in and out of focus. This happens when alt-tabbing. When the window is in focus, we attempt to capture the mouse. The mouse is only captured however if it is over the game window. Only when it is finally captured do we start the continuous re-centring.
Movement
Once we know the direction the camera is looking in, we can start to move the camera. This is done using the arrow keys.
Using the method Keyboard.GetState() we can detect whether the keys of interest are pressed or not. We then use some basic trigonometry to calculate the direction of movement. If your math’s skill are limited, don’t worry too much. The code works, and you shouldn’t need to change it.
Next time, we’ll be looking at displaying something a little more interesting that points.
1 Because we are using a BasicEffect object (object in the object-oriented sense) to provide the shader model, the actual colors of the points cannot be set. The BasicEffect class expects 3d-objects to be textured-map and have surface normals, but no colors. Surface normals are used to indicate the inclination of a surface to the shader, allowing objects to reflect lights correctly. Texture maps are images that are streched across a surface to give it a specific appearance. To avoid the complexities of this we are just using simple points without worrying about the color displayed.

Sorry, comments are closed for this article.