|
The Problem at HandEven if our application is throttled to a specific frame rate through a timer, the actual time between frames will vary. Often this leads to a jerky appearance in graphics, as the apparent motion of the game speeds up and slows down erratically. This can also lead to difficulties in matching the playing characteristics between users in a multi-player environment. To compound this issue, the screen is divided into discrete pixels, making compensation for slight variations difficult. In this article we will look at ways to compensate for timing variations, as well as exploring the use of floating point coordinates in 2D applications to allow for continuous motion control. Three Types of GranularityWhile we are usually quick to blame system overhead and machine performance, much of our problems arise from a characteristic known as "granularity" - that is, they cannot be measured or controlled in a linear fashion, but rather suffer from limits in resolution or precision. Let's start off by taking a look at three factors that exhibit granularity, and then we will explore some solutions.
Now that we have an idea of the nature of the problem we face, let's move on to look at some solutions. Providing a Time BaseThe first step we must take is to provide our object motion and rendering routines with a measure of the time that has elapsed for each frame, so that we can compensate for variations in frame time. We will use the following code to provide a time base: Global Variables BOOL perf_flag;
// Timer Selection Flag Initialization - Perform once at at start of program // is there a performance counter available? LONGLONG perf_cnt; // yes, timer choice flag
} else {
} Execute Each Frame to Establish Time Base LONGLONG cur_time;
// current timer value // read appropriate counter if (perf_flag) // scale time value and save time_span=(cur_time-last_time)*time_factor; // save new time reading for next pass through the loop last_time=cur_time; We now have a measure of the elapsed time for the previous frame, which we can in turn pass on to our functions for object motion, for example: move_objects(time_span); So now let's take a look at how we can apply this time base.... continue on to the next page of this article for more. Calculating Motion at a Constant VelocityWe are probably all fairly familiar with the calculation of distance traveled at a constant velocity: Distance = Velocity x Time Given that we are providing our motion control routine with a time base that reflects the amount of time that has elapsed since the last frame, we can easily apply this information to compute the distance that an object will travel during that frame. The first thing that we must do, if we are not already, is use floating point values to represent the positions of our objects. If you are using Direct3D, you are already using a floating point representation for vectors. However, many 2D applications start their lives based on integer based routines. Once we are working with a floating point position, calculating movement is simply a matter of multiplying speed of travel (in units/pixels per second) by the time elapsed since the last frame. For example:
Implementing the Acceleration CurveCalculating motion at a constant velocity helps, but how did we get to that speed? If we change velocity suddenly, in fixed steps, this will appear jerky. If you need a visual, think back to "Space Invaders" with it's single velocity in either direction. To avoid this, we need to implement an acceleration curve. The calculation for the effect of acceleration on velocity is very similar to our previous equation for velocity to distance : Velocity = Initial Velocity + Acceleration x Time By nature, acceleration is a constant change, defined by this linear function. However, in our program loop, we deal with this in finite increments of time. If we are re-calculating the velocity several times a second, this is normally not a problem. The error is minimal, and will not be noticeable in most applications. In this case, we can just augment our functions from above to utilize acceleration: x_vel += x_acc * time_span; In some cases, however, this incremental approach will not be sufficient. These include functions that will be calculated at long or sporadic intervals, and applications where acceleration is a deciding factor in the game, such as racing games or multiplayer games that must use dead-reckoning to estimate player positions between updates. In such cases, we must pull a bit of calculus out of the closet, and integrate the acceleration over time. The final velocity is still calculated in the same manner, but we must calculate the final position differently: x_pos += x_vel * time_span + x_acc * time_span * time_span *
0.5f; Note that in this case we wait to calculate the final velocity until after calculating position, as we need starting velocity in the distance calculation. Next, we'll take a look at some real world uses of acceleration. What Goes Up, Must Come DownOne use for acceleration in games is the simulation of gravity. Though we may tend to think of the effect of gravity as applying weight, it is basically an accelerating force. Normally it does not cause us to accelerate, as we are prevented from accelerating towards the Earth's core by solid objects that limit our downward movement - the floor, a chair, etc. When we are falling, however, it becomes quite apparent that we are accelerating. Gravity acts as a constant accelerating force, causing an acceleration of 9.8 meters per second / per second. Simulating gravity is relatively simple. Assuming an object is not blocked from falling, all we need to do is increment the velocity by a constant that provides the desired acceleration : y_vel -= 9.8; // assumes down to be negative, with 1 unit per meter However, falling objects don't accelerate forever, assuming there is an atmosphere around them. Wind resistance acts upon an object, providing a decelerating force that is proportionate to the velocity of the object. Thus, the resistance increases as speed increases, to a point that it becomes equal to the acceleration of gravity. This point is called "terminal velocity", and is the fastest that a falling object will achieve. To provide for wind resistance, after accelerating the object we multiply it by a constant, equal to 1.0 minus a drag coefficient, which is the percentage of velocity that is encountered as a resisting force: y_vel *= 0.9; // assumes a 10% coefficient of drag Wind resistance will also apply on the X and Z axis as well, which are not effected by gravity. So, if we wished to act upon an object with an initial trajectory already established, we could combine these forces like so: vel.y -= 9.8 * time_span; Often, such an operation will be individually on a collection of small objects, in the case of particle systems. For example, it we wanted an animated fountain, we can launch droplets at a random arc from a common point, and track their trajectory. When a droplet reaches the ground, it is "recycled", launching it from the fountain nozzle again to keep a steady volume of particles in suspension. Smooth Motion Control from User InputOne last caveat before we wrap this up.... Another great use for acceleration curves is in providing smooth motion from user input. This is especially useful in scenarios such as keyboard or switch based gamepad controls. Simply setting a velocity when a key is pressed leads to very jerky motion. Instead, we can set an acceleration while the control is pressed, followed by a deceleration when the control is released. For example : if (right_pressed && vel<10.0) {
} else if (left_pressed && vel>-10.0) {
} else {
} pos += vel; The above code will provide acceleration for as long as the control is depressed, and then slow to stationary at a constant rate when the controls are released.
|
Visitors Since 1/1/2000:
|