I often come across the common question, "How do I allow for movement speed in games that is unaffected by frame rate?" While pondering this same question, I recognized the same problem in some games I have written where the object movement has always been too fast when the frame rates were really high. This article is aimed at showing a simple way around this problem. There may be plenty of examples and articles like this already available, but in this article I will offer my own approach to the solution.
In solving the movement problem, there are different methods you can use. One such common approach has been to implement delays to use up extra game cycles on faster computers. An example would be:
A better approach is to change the movement based on how many frames are being rendered every second. To demonstrate this solution, I have modified NeHe's Tutorial Number 23, which I hope many of you will be familiar with. This particular NeHe tutorial already uses a function for determining time, so we won't need to perform a large number of modifications to the code.
The basic idea is to determine how much time has elapsed between frames and then change how much we want to move accordingly. As an example, in NeHe Lesson 23 you are walking around a small structure. To move you may want the character to move 2 blocks every second. To move those 2 blocks, all the movements for all the frames you are rendering in one second would have to add up to two. Thus, you must determine how much time is passing between your frames to get the frame rate.
First you must remove the old timer code from the program.
[code]float timer; //used to check the current time
float timerdiff; //used to determine the time elapsed
float secsperframe; //used to hold the value for how many seconds have elapsed between frames
float desireddistance; //desired distance to move in a second
float movementvalue; //value to move by each frame[/code]Next, set the desired distance value to move over the course of a second. I found 2 to be a good number.
[code]desireddistance=2.0f;[/code]If you are getting 80 frames a second then you would want your character to move .025 units every frame since 80*.025 = 2. What you first must do is figure out how much time has elapsed since the last frame. To do this, take the current time before the main loop starts for the first time.
[code]timer = TimerGetTime();[/code]
[b][size="5"]Determining Time Differences[/size][/b]
After rendering the scene, compare the current time with the one you last took. We will store this in the secsperframe variable.
[code]//first determine how many seconds elapsed
timerdiff = TimerGetTime()-timer;
//the function reads in milliseconds so convert to seconds by dividing by 1000
secsperframe=(float)(timerdiff/1000.0f);[/code]Let's say the number comes out to be .0125. To get the frame rate, simply divide 1 second by that .0125. This gives you an answer of 80, since 80 times that .0125 is equal to 1 second. To determine how much to move, you simply take the desired distance and divide by the frames per second.
[bquote] Movement = (desired distance)/(frames a second)[/bquote]In this example, that would be 1 divided by 80 which would give you .0125 as stated above. The equation can be changed, though, if we substitute in the equation for frames per second. Now it reads:
[bquote] Movement = (distance desired)/(1/seconds per frame)[/bquote]This can be further simplified to:
[bquote] Movement = (distance desired)*(seconds per frame)[/bquote]I find this equation to be the easiest to use. In NeHe Lesson 23, this looks like:
[code]//now compute the movement value
movementval = (float)(desireddistance*secsperframe);
//get the new time
timer = TimerGetTime();[/code]
[b][size="5"]Moving the character[/size][/b]
Now that we have our current movement value, it's time to put it into the by replacing the old movement code with the new.
[code]if ( buffer[DIK_UP] & 0x80 )
//xpos -= (float)sin(heading*piover180) * 0.05f;
//zpos -= (float)cos(heading*piover180) * 0.05f;
xpos -= (float)sin(heading*piover180) * movementval;
zpos -= (float)cos(heading*piover180) * movementval;
}[/code]These changes can easily be applied for moving in all directions.
[b][size="5"]Modifying head movement[/size][/b]
As a final change, I also changed the speed of the head bob. The default change is 10 units every frame, so at 80 frames a second, the movement is 800 degrees per second, which is quite a few of cycles (360 degrees in a circle). At a reasonable frame rate of 40 frames per second the head would bob only 400 degrees. The goal is to vary how much the angle is changed every frame based on the frame rate. To accomplish this, multiply the number of degrees you want the bob movement to change in one second by the number of seconds between frames. At 80 frames per second, there are .0125 seconds between each frame (as previously mentioned). If you want the head to move 400 degrees every second, then multiply 400 degrees by the .0125 seconds between each frame, and you arrive at the number of degrees to perform the head bob movement for the frame.
[code]walkbiasangle+=(400.0f*secsperframe);[/code]In this example the change in the angle would be 5 degrees every frame. At 80 frames per second, the total degrees would be 400 degrees every second.
In conclusion, I cannot say if this is the absolute best way to handle character movement, but it's a method that has worked for me thus far. I would like to thank Jeff Molofee and Justin Eslinger for their work on the original tutorial. If you have any questions or comments, feel free to e-mail me at [email="email@example.com"]firstname.lastname@example.org[/email].