Third-Person Game Cameras
Question submitted by (18 May 2001)
|Return to The Archives|
I'm trying to write a 3rd person game and i'm using a Quake 2 style engine.
My problem is to have the camera follow my actor properly. I want the camera
to move like in Zelda (N64) or Tomb raider (PC) etc. I've tried different
thing, but i can't get the coding right. I'm using Delphi 5.
I need the camera x,y,z to correspond the actor x,y,z + height and distance from the actor of course.
Do you have a good math code to show me ?
I'm really stuck here, because i'm no math wizard.
There are a few ways to go about doing this. I'll cover the simplest way
first, and then explain a few options.
First things first: we need a location for our 3rd person camera. Let's say you want your camera behind the hero and above him/her. In order to accomplish this, you'll need the hero's direction vector (preferably a unit vector.) This is important because the camera's position is relative to the direction the hero is facing.
The nice thing about vectors is that they're easy to manipulate. You simply change the signs of the z/y/z components, and the vector points in the exact opposite direction. If you negate the hero's direction, the vector will point in the exact opposite direction, a.k.a. directly behind the hero. When that vector is added to the hero's location, it will result in a point behind the hero. However, since we're dealing with a unit vector, the point may be too close or too far from the hero (depending on how large your hero is, relative to a single unit of space in your world.) If your units are in feet, and you want your camera six feet behind the hero, then simply multiply the x/y/z components of the vector by 6.0 before adding it to the hero's position. And if you want the camera above the hero, simply add some value to the Y component of the resulting point (vector + hero_location).
At this point, the math guys reading this are probably screaming at me for such a naive explanation. Don't worry folks, I'm not going to let him off that easily. =)
There are a lot of problems with this approach. First of all, it's a hard-coded approach, and you don't have much control over the placement of the camera. Also, this approach doesn't handle collision detection (i.e. the camera might pass through a wall, which is always ugly.) And, if you look at your typical 3rd person games, they usually have at least two other features. (1) they allow the player to control where the camera is and (2) the cameras have a filtered motion (i.e. turn the hero around fast enough, and the camera tends to lag behind a little bit before catching up.)
So let's consider a different approach using matrices.
If we build a matrix that has a 180-degree rotation about the Y axis followed by a rotation on the X axis (of say, 45 degrees) and multiply that by our hero's direction vector, we'll end up with a vector that points behind and above the hero. By scaling the x/y/z components of that vector (by say... 6.0) and adding it to the hero's location, we'll end up with a point in space directly behind and above the hero that is exactly 6.0 units away from the hero's origin.
The advantage of this approach is that the two rotational values (the Y axis and X axis rotations) allow you to control the amount of rotation, and hence the placement, of your 3rd person camera. If you tie these values to a couple keys on the keyboard, you can allow the user to change the placement of the camera (say, over the shoulder, rather than directly behind the hero.)
Let's take a moment to discuss this 3rd person camera rotation interface... It's the small things that give the finished product a "polished" feel. As you write the code to handle this 3rd person camera, you'll notice that if you simply apply positive and negative rotation to a few keys, the movement of your 3rd person camera will be jerky and either too slow, or too fast. It will not have the same feel that most games have, because their camera interface is always smooth.
If you want to smooth out your interface, you'll need to ramp your camera movements. More specifically, let's talk about the desired effect. If you hold down the key (or controller button) for the 3rd person camera movement, the camera begins to turn, but slowly. The longer you hold down that key, the faster the camera begins to turn until it saturates to a max speed. When you release the button, the camera rotation slows down until it stops. The same thing happens when you turn the camera the other way. And if you are turning the camera one direction and immediately change directions, the camera slows down faster until the direction changes. This is ramping. The concept is simple. Here's a simple piece of code:
Studying this code, it should become quite clear as to exactly what's going on. We ramp up/down the value stored in rotationDeltaY and eventually add that to the camera's rotational value. Simply play with the constants to change the speed of the camera's movement. If you really want to be picky about this code, you should also consider clamping the rotationY value, so the camera can't swing all the way in front of the hero (unless, of course, you want this ability.) And also, you'll want to multiply the rotationDeltaY variable by your time vector so that the speed of rotation isn't tied to the frame rate.
While I'm on the topic of specifics, I should also mention that rotationY (the 3rd person camera's rotation) should be tied to the hero's rotation on the same axis. This way, as you turn the hero, the 3rd person camera stays behind (or relative to the current user's settings) our hero. You can accomplish this by simply adding rotationY to your hero's Y rotation. This is the value used to create your 3rd person camera's rotation matrix.
We're not done polishing yet; this only smoothes out the keyboard interface. If we swing our hero around, we also want this same kind of filtered feel to the camera rotation as the hero turns. Though the effect may be the same, the code is much different. Though I won't show the code here, there's a rather simple technique. After each frame, record the final result of the camera's rotation (specifically, the rotationY variable.) Keep this information in an array for, say, the last 15 frames. Each frame, after calculating the rotationY variable, put it in the array, removing the oldest value (you can treat the array like a circular queue for efficiency.) Instead of adding the rotationY to the hero's rotation (for the final rotational value), average the values in the array and use that value. The larger your array is, the smoother your movement will be and the farther behind it will lag. As you might imagine, turning the hero and then stopping will require up to 15 frames for the camera to catch up.
We've still got two important topics to cover: collision detection and which direction the camera is looking.
I won't cover collision detection here, because it can be rather involved. Instead, I'll assume you already have collision detection in your code. If you don't you can look http://www.FluidStudios.com/publications.html>here (click the link titled "A demonstration of the enhanced Fluid Studios collision detection algorithm".) In order to use your collision detection, you probably need a location and a velocity vector. So once you've generated your 3rd person camera origin (i.e. after applying the matrix to the hero's scaled direction vector), simply subtract that point from your hero's origin. This will give you a collision-safe velocity vector (i.e. a vector that points from the hero to the camera, with the length of that vector being the distance between them.) Run this through your collision detection. The collision response (i.e. collide and slide, collide and stop, etc.) is up to you. You'll find that if you do it properly, the filtering will still come through, giving you that nicely polished feel.
The only topic left (that I can think of) is to handle the camera direction. Up until now, we've only concentrated on where the camera is located, not the direction it's pointing (a 3rd person camera with a lazy eye isn't of much use. =) If you don't have code to generate a "look-at" matrix, you can look http://www.FluidStudios.com/publications.html>here (click on the link titled "A powerful NxM matrix template class which includes full support for matrices, vectors, points homogenous transforms and more" - find the "lookat()" member in the downloaded source file.)
In order to generate a look-at matrix, you'll need a vector (the direction you want to look.) This vector is the inverse of the vector used for collision detection (rather than pointing from the hero to the camera, it points from the camera to the hero.) Feed this vector into your lookat() routine, and out pops a bouncy baby matrix that will get your camera looking right at your hero, no matter where the camera is placed. You may decide that you want the camera to point someplace in front of the hero. This is handy for those times where the camera is directly beside the hero. Not doing so could hide too much of the environment from the player (hiding a nearby threat, for example.)
Don't be afraid to experiment. For example, you could tie the vector-scale to a key as well, so the player can control how close or how far the camera is from the hero. Don't forget to polish your interface - it makes a huge difference.
Response provided by Paul Nettle
This article was originally an entry in flipCode's Ask Midnight, a Question and Answer column with Paul Nettle that's no longer active.