In this tutorial you will learn how to give the impression of movement through 3D space.
First let’s have a look at a typical game loop
//initialisation code
while(playing)
{
//if a message is waiting
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
//translate it
TranslateMessage(&msg);
//dispatch it
DispatchMessage(&msg);
}
//if no message is waiting
else
{
//update the position of player,
enemies etc.
UpDatePosition();
//draw the world
DrawWorld();
//draw the models in the world
DrawModels();
}
}
OpenGL and other graphics API’s have a single “camera” fixed at 0, 0, 0 on the Cartesian plane.
In order to give the impression that the camera is moving, the world has to be moved in the opposite direction. Therefore to give the impression that the camera is moving clockwise, the objects in the scene must be rotated anti-clockwise.
However it is much easier to pretend that the camera is movable and the world remains fixed.
To calculate the distance that the scene must be translated every scene, the rotation of the camera and the length of a step (how far the camera must move every frame) must be know. From then a bit of trigonometry can solve the problem. A reasonable step length is 0.03f.
So to find the length of translation on the x axis we to find sin(angle of rotation) * Step length. Then the z axis translation can be calculated with Pythagoras’s theorem. Z axis translation = sqrt(x axis^2 + step length^2). It is important to multiply the angle of rotation by Pi/180 before calculating the translation. This is because the trig functions take radians, not degrees.
In code:
//create some variable to aid in calculations
float rtemp, xtemp, ztemp;
//multiply the amount of rotation by pi/180 and store the
value in rtemp
rtemp = rotation * (M_PI/180);
//find the x axis translation
xtemp = sin(rtemp) * STEP;
//find z axis translation using Pythagoras
ztemp = sqrt((xtemp*xtemp) + (STEP * STEP));
//increment the zposition and xposition
zposition += ztemp;
xposition -= xtemp;
So in the DrawWorld function you would rotate the world by the angle of rotation, then translate it to the new positions in xposition and zposition.
//Clear
colour buffer and depth buffer
glClear(GL_COLOR_BIT_BUFFER | GL_DEPTH_BUFFER_BIT);
//reset
modal view matrix
glLoadIdentity();
//rotate
the world
glRotatef(rotation, 0.0f, 1.0f, 0.0f);
//translate
the world
glTranslatef(xposition, 0.5f, zposition);
//make
primitives of this color
glColor3f(0.0f, 1.0f, 0.0f);
//begin
drawing quads
glBegin(GL_QUADS);
//coordinates of quad
glVertex3f(-1.0f, 0.0f, -1.0f);
glVertex3f(-1.0f, 1.0f, -1.0f);
glVertex3f(1.0f, 1.0f, -1.0f);
glVertex3f(1.0f, 1.0f, -1.0f);
//stop drawing
glEnd();
Frame Independent Movement
Let’s imagine you have just downloaded a game created on a very powerful machine and you are trying to play it but the frame rate is too slow to move well. Or you have downloaded an old game of pong and the ball zips across the screen so fast you can barley see it. This is because the movement in these games is frame dependant, or the position of the camera is only updated every frame. To rid your applications of these irritating problems, frame independent movement must be implemented.
Frame independent movement is quite simple. It has been established that 25 frames per second (one every 40 milliseconds) is the minimum speed that frames can be displayed before the human eye can detect individual frames. So if the cameras position is updated every 35 milliseconds then the movement will look nice and smooth.
WINUSERAPI UINT WINAPI SetTimer
(HWND, UINT, UINT, TIMERPROC);
This function sets a timer. The first parameter is the hwnd of your windows. The second parameter is the id of your timer. It is used in your callback function to tell the difference between timer messages. The third is the time (in milliseconds) between the timer going off and the fourth is usually left as null (check msdn for more info). When your timer goes off, you will receive a WM_TIMER message in your call back function. The wparam of this message is the id of the timer.
So the new frame independent loop will loop something like this.
const int ID_UPDATE //this variable is global
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_TIMER:
{
If(wParam ==
ID_UPDATE)
{
UpDate();
}
}
break;
default:
return DefWindowProc(hwnd, msg, wParam,
lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE
hInstance, HINSTANCE hPrevInstance,
LPSTR cmdline, int show)
//SetTimer ID_UPDATE
to 35 milliseconds
SetTimer(hwnd, ID_UPDATE, 35, NULL);
//initialisation code
while(playing)
{
//if a message is waiting
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
//translate it
TranslateMessage(&msg);
//dispatch it
DispatchMessage(&msg);
}
//if no message is waiting
else
{
//draw the world
DrawWorld();
//draw the models in the world
DrawModels();
}
}