Handling Inertia
Most first-person shooters (FPSs) implement inertia on their camera controllers for increased realism. Our character accelerates progressively and also stops moving in an inertial fashion. This makes movement smoother at almost no coding cost. To add inertia, we just have to remember a few physics equations, such as this one:
acceleration=velocity/time
Solving for velocity, we get
velocity=acceleration*time
Consider how a car works: You do not actually set the speed of the vehicle but instead use the pedals to add acceleration (or braking) to it. So we really need to implement our controller in terms of acceleration, not velocity directly. Our yaw controller would thus be
yawvel+=ROTACCEL*elapsed*(input.right-input.left);
if (yawvel>ROTSPEED) yawvel=ROTSPEED;
if (yawvel<-ROTSPEED) yawvel=-ROTSPEED;
if (input.right-input.left==0) yawvel=yawvel*BRAKINGFACTOR;
yaw+=yawvel*elapsed*(input.right-input.left);
The first line is just the acceleration version of our initial controller. Then, we use two lines to limit the velocity, so we cannot rotate faster than our initial #define. The fourth line is probably the most cryptic of them all. To understand it, notice that the if condition is activated if neither left nor right are pressed. In this case, we need to slow down the rotation progressively. We achieve this result by multiplying the current speed by a braking factor (0.85, for example). The last line only updates the yaw according to the calculated rotation velocity.
Almost identical code can handle forward and backward movement:
dz=(input.up-input.down);
vel+=ACCEL*elapsed*dz;
if (vel>SPEED) vel=SPEED;
if (vel<-SPEED) vel=-SPEED;
if (dz==0) vel=vel*BRAKINGFACTOR;
playerpos.x+=vel*elapsed*dz*cos(yaw);
playerpos.z+=vel*elapsed*dz*sin(yaw);
Now that we have seen how to implement cameras for FPSs, it is time to render them. In most graphics APIs, like OpenGL and DirectX, we have macro routines that place the camera, given a camera position and a look-at point. We will use these primitives because they provide a convenient way to specify this kind of camera.
Specifying the camera location is trivial. Now, to specify the look-at point, we will use some trigonometry again. If you understood the sin and cos used to advance according to the yaw angle, the following should become obvious:
lookat.x=playerpos.x+cos(yaw);
lookat.z=playerpos.x+sin(yaw);
Intuitively, the look-at point is placed in the same direction we are aiming; thus, using the same increments to compute it is very convenient. Assuming that convention, we can specify our FPS camera in OpenGL with the following piece of code:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(fovy,aspect,nearplane,farplane);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(playerpos.x,playerpos.y,playerpos.z,lookat.x,lookat.y,
lookat.z,0,1,0);
The line that actually places the camera is the last two lines, but I provided the full camera setup code for completeness. Remember that the gluPerspective call sets optical parameters such as field of view, aspect ratio, and so on, whereas the gluLookAt places the camera by means of nine parameters grouped in triplets. The first triplet is the camera position, the second is the look-at point, and the third is a vector pointing up, which we will need for more sophisticated camera models.
Camera setting is very similar under DirectX. The syntax here is
D3DXMATRIX matView;
D3DXMatrixLookAtLH(&matView,&D3DXVECTOR3(playerpos.x,playerpos.y,
playerpos.z),&D3DXVECTOR3(lookat.x,lookat.y,lookat.z),
&D3DXVECTOR3(0,1,0));
g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
D3DXMATRIX matProj;
D3DXMatrixPerspectiveFovLH( &matProj, fovy, aspect, near, far);
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
assuming playerpos and the look-at point have all been computed using the algorithms we just explored.
|