A difficult part of game programming is setting up proper timing. We can't
simply update our object positions every time we render a frame, because fast
computers would move the objects faster than slower computers. Because of this
we need to be able to update our objects based on timing. In this chapter we
will setup a Frame Timer to control animation based on time.
Topics Covered in this Chapter:
Animation Timing
Creating A Frame Timer
Creating an Animation Test Class
Frame based animation is easy, at every frame you adjust the position of the
object based on what ever logic you are using. Time based animation is more
complicated because the distance the objects move is based on how many timer
intervals (usually fractions of a second) have passed since the last time a
call to update the object was made. The FrameTimer is my alternative and lets
you program using frame timing (easy), but maintains a timer based frame rate.
What the frame timer does is calculate how many frames need to be updated based
on how much time has passed. For example if you set the frame timer to maintain
60 frames per second, for every 60th of a second that passes it will report
that a frame needs to be updated. The frame timer is designed to be called every
time the game update loop begins; The number of frames that need to be updated
is passed to the objects that need to be updated, and they are updated accordingly.
The results being, if you have a slower computer that can only maintain 30 fps,
each update is asked to move the game objects 2 frames worth of distance. If
you have a faster computer that can maintain 120 fps, the the game objects are
only requested to move every 2 frames. This maintains a constant movement rate
on any computer you run it on. Of course the drawback to this method is if your
computer can't maintain more than 1 frame per second the whole thing would freeze
up. However the game wouldn't be playable on a system like that anyway.
Take a look at the frame timer from the chapter 6 code. The frame timer is
actually a very simple class with 2 functions, init,
and framesToUpdate. Init
takes one parameter, the frame rate to maintain. Init
uses the performance counter to do timing. The performance counter is different
with every chip, so the first thing we do is find out what the frequency of
the timer is, that is, how many counts per second. Then to determine how many
counts per frame, we divide that frequency by the frame rate.
//The number of intervals in the given timer, per frame at the requested
rate.
intervalsPerFrame = ( (float)timerFreq.QuadPart / Requested_FPS );
}
When framesToUpdate is called the performance
counter is called to get the current count. The intervals since the last time
the framesToUpdate function was called are calculated.
Then, using the intervals per frame we calculated above, it calculates how many
frames need to be updated. We then record the current time for use the next
time framesToUpdate is called, and then return
the number of frames that need to be updated.
int FrameTimer::framesToUpdate(){
int framesToUpdate = 0;
QueryPerformanceCounter(&timeNow);
//If we are not updateing any frames, keep the old previous timer count
if (framesToUpdate != 0){
QueryPerformanceCounter(&timePrevious);
}
return framesToUpdate;
}
That's all there is to the frame timer. The only thing you need to keep in
mind when programming using the frame timer is that you are designing your game
to run at a certain frame rate, if you wished to increase or decrease that frame
rate, adjusting the frame timer after you write your game would just make it
run faster or slower.
In order to use the frame timer, we need to setup a controller class for the
object we wish to animate. The controller will receive the frames to update
and change the position of its object accordingly. The controller I have written
is called AnimationTest. It is a very simple controller that moves the sphere
model back and forth on the X axis between 25 and -25 units. This class can
be used as an example for a controller class, but is not part of the reusable
classes we are building in this part of the book.
The AnimationTest class starts with its init function, which loads the sphere
model, and sets the starting position of the model.
bool AnimationTest::init(LPDIRECT3DDEVICE9
device){
model = new Model();
model->loadModel(device,"sphere.x");
if (!model){
return false;
}
position = D3DXVECTOR3(0.0f,0.0f,0.0f);
model->setPosition(position);
direction = 1;
}
The next function is updateFrames. UpdateFrames
takes the number of frames requested to be updated. This value will be the value
returned by the frame timer. When we write the controller classes we do so based
on the frame rate we know we will be setting in the frame timer. It would be
possible to easily set a frame rate option and adjust the movements made per
frame based on that frame rate. I prefer to set a preferred rate of 60 fps and
program to that. In updateFrames the frame loop
calculates what changes would be made per frame, and once all the calculations
have been made it actually makes its changes. In this example it doesn't look
important to loop each frame update, and the changes could be calculated as
just a multiple of the frame rate. However, later when doing collision detection
it becomes important to do each update frame by frame.
void AnimationTest::updateFrames(int numberOfFrames){
//for a simple test of animation the model will be moved from +25x to -25x
//for each frame to update, calculate changes to the model position
for (int i = 0; i<numberOfFrames; i++){
if (direction == 1){
position.x+=0.3f; //change the x position by 0.3 units per frame
} else {
position.x-=0.3f; //change the x position by 0.3 units per frame
}
if (position.x > 25.0f || position.x < -25.0f){
direction = direction * -1;
}
}
//update the model position
model->setPosition(position);
}
The last function in AnimationTest is render. Render actually just propagates
the render call to the model.
Now we have a FrameTimer, as well as a class to test animation using
it. The next step is to add both a FrameTimer and the AnimationTest to the GameMain.
By now you should understand we need to add both the class definitions to the
GameMain header as well as to the top of the GameMain. So, I will skip to GameMain's
Init. I have removed the test model from the previous chapter and replaced it
with AnimationTest, as well as added a call to create a FrameTimer.
bool GameMain::init(HWND wndHandle)
{
...
//create a new animation test object
animTest = new AnimationTest();
animTest->init(dxManager->getD3DDevice());
//create a new frame timer
timer = new FrameTimer();
timer->init(60); //init to maintain 60 frames per second
return true;
}
Lastly, we need to add an update, and render call to the update
function. The timer is called, and the frames that need to be updated are passed
to the AnimationTest object. Once the object has been updated, the render call
renders the AnimationTest object in its new position.
void GameMain::update(void)
{ int framesToUpdate;
//call our update function framesToUpdate = timer->framesToUpdate();
animTest->updateFrames(framesToUpdate); // begin rendering
dxManager->beginRender();
//game render calls go here
That concludes the changes required to test the timer. Compiling and
running the project should now result in a single sphere that moves from one
side of the screen to the other. The sphere should take just over 3 seconds
to get from one side to the other no matter what computer it is running on.
Summing Up - Chapter 6
In this chapter we have created a FrameTimer; a timing system that returns
the frames we should update based on how much time has passed since we last
updated. This allows us to program our game in a frame based method, which is
easier than writing our controllers to work on a time based model. This method
still reliably maintains the same game speed on different speed computers. Before
moving on, play with the frame rates, see what happens when you increase or
decrease the frame rate. Also, try adjusting the distance updated in the AnimationTest.
You should be able to increase the smoothness of the movement by reducing the
distance moved and increasing the frame rate. Of course this will only work
if your computer can keep up.