Timing and WindozeTiming. Without it your game will do some very weird things. From roller-coaster frame rates to objects that move too quickly, it all can be traced back to timing. Unfortunately finding good documentation on timing in Windows can be a chore. Typically you will uncover a function called GetTickCount(). Since this function deals with milliseconds it appears to be perfect for our needs. The only problem: it is very inaccurate and unreliable. So, we may dig a little further and come up with timeGetTime() in the multimedia library of Windows. This is a much better function, is more reliable, and still works with milliseconds ... which is what we need. It kind of makes you wonder though ... if we are writing high performance games, shouldn't we have a "high performance" timer? And, indeed we do. Getting it to work nicely can be a bit of a trick. But, we will cover that in a moment. First, what is our functionality needs? Obviously, we will want a function to initialize our timing system. We will also want a function to return the current time, and one to delay, for the amount of time that you pass in to it. Then we will want to add timer routines to control our frame rate. One to start a timer and one to wait until the specified amount of time has elapsed. That should be it. Here is the code for the Init_Time() function. This code performs the task of finding out if we have a high performance timer on the system to use. If not it falls back on timeGetTime() since that is guaranteed to be there. We start out by calling the function QueryPerformaceFrequency(). If this succeeds we have a high performance timer on the system and this call returns the frequency of it. If not we set the things up to not use the HP timer. Presuming we do have one, then the code saves the timer frequency and calculates the number of ticks per millisecond. The reason for this value is the HP timer may have a resolution far smaller than 1 millisecond. So, we need to determine how many ticks of the HP timer are equal to 1 millisecond. That is all the setup procedure needs to do to get things ready. The next function we will cover is Start_Time(). This function is used to start a timer with the variable that you pass in to it. This will be used to control our frame rate as you will see later. Here is the code ... The code is relatively simple. If we have access to the high performance timer we make a call to QueryPerformanceCounter(), if not we make the call to timeGetTime() instead. The only thing that may be confusing is the notation used to store the value that these functions return:
What this code does is move the value of ptr_time_var into the register EBX. In this case ptr_time_var is the address of a variable that was passed in to hold the starting time. Next, the code moves the value of register EAX into the address that EBX contains. In assembly that is what [XXX] means " the address of". The "DWORD PTR" will let the assembler know that we will be moving a DWORD and that [EBX] is a pointer. This will effectively store the value EAX in the location pointed to by ptr_time_var. The final timing function that we will go over is Wait_Time(). This is the sister function for Start_Time(). And, together, they are used to control our game's frame rate. Take a peek at the code. This routine is probably the most complex of the timing routines. What it does, is wait for the passed in amount of time to elapse PAST the starting time that was passed in. In other words, if your start time was 100 and you told it to wait for 50 ... the function would not return until the current time was >= 150. It returns the elapsed time between finish and start. Let's view this code a section at a time ... First, if we can use the HP timer it does so. This code first converts the passed in time to the same frequency as the HP timer. It does this by dividing 1000 by the passed time. This gives it a number relative to times per second. Then it divides the frequency by that number to retrieve the equivalent amount of time needed at our HP frequency level. This is done by way of setup since it determines the "finish" time for the function. It saves this value on the stack so we can pop evenly in our loop. Next, it enters our main compare loop. It POP's the value of elapsed time off of the stack in order to clear it. NOTE: That on the first iteration this value is not the elapsed time, but the finish time. Then we retrieve the current value of the counter. We subtract the start value from the current value we get, in order to calculate the time that has elapsed so far. We then save this "time" by pushing it onto the stack. Now, we can subtract the wait time from the elapsed time. If the result is less than zero we haven't waited long enough so we do the above step again. Otherwise we continue on down to the next step. Here we POP the elapsed time off of the stack and divide that by however many ticks were in a millisecond. That allows us to return a millisecond value even if we use the high performance timer. The code for if we do not have a high performance timer does the same exact thing, but it doesn't perform the conversions since the function is in milliseconds by default and can't be changed. That is it for timing. The other two functions merely perform variations of the two routines we just covered. So, I am not going to cover those, but will leave it to you to take a look-see at them, and comprehend what they do. Now, we can knock out our menu code.
|
||||||||||||||||||||