csanyk.com

video games, programming, the internet, and stuff

Tag: delta time

GameMaker Tutorial: Delta Time

What is “delta”?

Delta is a term often used in math and science to indicate change. For example, in physics calculations, the term ΔV (pronounced “delta-vee”) is used to quantify a change in velocity as measured at two different moments in time. If you didn’t have an introduction to this concept in your education, it can seem mysterious why we talk about “delta” like this, but it is a common convention.

Background

In videogames, the game program incorporates a routine called an update loop, which repeatedly applies the rules of the game to the game’s present state in order to create the next game state. Normally, it is desirable that the program control its frame rate, the number of times this update loop executes per second, so that a consistent frame rate is achieved, in order to have the game run smoothly and consistently. Ideally, this frame rate would be constant and unvarying.

Achieving this in practice can be quite complicated. Back in the old days, when games were programmed in Assembly for specific hardware platforms, and the game program ran on the bare metal, the programmer relied on the CPU clock and the timing it took to perform various operations to ensure that everything worked in sync. This required great effort and intimate understanding of the hardware and program code.

For the most part, GameMaker makes things simple enough for basic game development, and you don’t have to worry about regulating the frame rate yourself.

In older versions of GameMaker, there’s a room property, room_speed, which sets a target number of steps the update loop will attempt to execute per second.  Typically, this is set to a value that aligns nicely to the refresh rate of the display — often 30 or 60.

In GMS2, room_speed is replaced by a global option in the project’s properties.  (Cog symbol) -> Main options -> General and set “Game frames per second”.

This simple approach, simply setting a frame rate through the Game FPS or room_speed properties, works well enough for many projects.  Just set the desired frame rate and let GameMaker manage itself to try to achieve this target frame rate.  As long as the program is able to do so, everything will run as desired.

We can run into problems, though, because of the limitations of the computer hardware to perform all the calculations needed during the update loop to generate the next game state.  As long as the computer has plenty of resources, it should have plenty of time to generate the next game state.  But if those resources are not available, or if the game demands too much work be done to allow the available system resources to create the next game state in time, the game will not run smoothly.  The program will not crash, but it may slow down or stutter, or perform in a jerky manner.

It’s not really avoidable; to avoid the problem completely, you would need to write the game in such a way that it was guaranteed that the program always requires the same amount of calculations to be performed per step, and always has the same system resources available to perform those calculations.  The first is very unlikely, and would impose constraints that would make the program’s design very rigid. The second is well beyond your control, unless you’re writing software that has direct control over the hardware it’s running on, rather than writing an application that runs on top of an operating system that also supports numerous other multi-tasking programs and services.

The solution is to write efficient code that has an extremely good chance of running all the calculations for the next game state before that game state is needed for drawing the game to the display, so that there will never be a delay that results in a dropped frame.  Doing this requires that you mostly write small, simple games, and run them on hardware robust enough that it will easily be able to guarantee that sufficient resources are available in order to complete the computation required, on time, every step. Those are both significant downsides, but even so you can create a great many games within those constraints these days, without too much of a problem.

But there’s still things that are out of your control, even if you write a small program with very efficient code. You can’t control what hardware the user will run the game on.  You can’t control what other processes might also be running on that system, in the background, while the game program is running. These uncontrolled factors can defeat all your efficient programming and give a poor experience at runtime on a system with insufficient resources to perform all the instructions that it needs to, on time.

Enter delta time

When the simple approach isn’t good enough, it’s time to turn to a concept involving delta time.  This is simply measuring, each step, the amount of time that has passed since the previous step.  This amount will vary from step to step, depending on how much work the game had to do to generate that step, and how much hardware resources were available to perform that work, in addition to whatever else the operating system may have been doing.

Once you know how much time has passed, you can use this to make adjustments in various things that depend on time, such as movement speed, timers, sprite animation speed, and so on, to keep everything looking smooth.

In a simple game, you would normally just set an instances speed to some value, V, and every step the game would update by moving the instance V pixels in direction theta.

In a delta timed game, you would first measure the amount of time that has passed since the previous step, calculate a ratio between this time and the number of game state updates desired per second (frames per second), and then use that ratio to modify the speed of everything in the game, so that even if the game runs a little behind in calculating everything, the end result is that the instance looks like it moves V pixels in direction theta each step.

Make sense?  It can be a lot to grasp at first, but it’s actually fairly simple.

Let’s look at it now in detail and walk through an implementation.

Implementing delta time

GameMaker provides a built-in global variable, delta_time, which is the time in microseconds that has passed since the previous step.  Since the engine performs this measurement for you, it’s very reliable, and all you need to do is use it.

A microsecond (μs) is one millionth of a second, or 1/1000000, or 0.000001. Typical GameMaker projects target a framerate of 30 or 60 steps per second, so each step is 1/30th or 1/60th of a second, or about 33333.333…μs per step for a 30fps game, and 16666.666…μs for a 60fps game.

So, if your game were running ideally, updating the game state every step exactly on time, every step’s delta_time should be exactly 33333.3… or 16666.6…  In the real world, you won’t hit those numbers exactly, but you’d expect that they should be very close, most of the time. (And in GameMaker, the value of delta_time drops the decimal, which introduces a problem down the road that we’ll have to deal with somehow… [[Hmm… maybe it’d be better to use a room speed that yields a whole number ideal delta_time?]] We’ll get to that in a bit.)  To the extent that delta_time differs from this ideal value, you can adjust all of the time-based variables that influence the game state, and if you do it all correctly, thereby smooth everything out.

It would be nice if you could just have a “use delta time” checkbox in your project, and all the built-in time-dependent variables — speed, hspeed, vspeed, image_speed, alarm[], etc. would just receive these adjustments by virtue of the engine, automagically.  Wouldn’t that be nice?

But for whatever reason, they don’t give us that feature, but at least they give us the built-in delta_time variable to work with.  So we’ll have to do that.  And of course, every other user-defined variable that we add to our project that depends on time will also need to be adjusted with delta_time.

So, if the game is running too slow, delta_time will be higher than the ideal value, because the amount of time that passed was longer than the ideal time.  In the game, instead of moving N pixels per step for 60 steps in a second, the instance would move N pixels per step, but only hit some number less than 60 steps per second, resulting in slowdown and jerky movement and animation.

To fix this, we should be able to see how:

ideal_delta_time = 1/room_speed * 1000000; //typically 33333 or 16666

This simplifies to 1000000 / room_speed. Next, we need to compare the measured delta between the previous step and this step (delta_time) to the ideal delta time:

dt_ratio = delta_time / ideal_delta_time

Now that we have the ratio for this step, we must use this ratio for all time-dependent calculations 

speed = ideal_speed * dt_ratio;

or, since computers multiply with fewer cpu instructions than they can divide:

speed = ideal_speed * delta_time * room_speed * 0.000001;

Or in other words, to move at an average speed of N pixels per step, you adjust the actual speed every step by multiplying it by delta_time/ideal_time:

speed = ideal_speed * (delta_time/ideal_time)

(Note, many games that use delta_time eschew the built-in speed system, instead using variables that are not managed by the engine, to give the programmer complete control over the motion. This is a very common approach taken with GameMaker projects, because the way the built-in variables are managed by the runner don’t offer the flexibility and control needed.)

Once we have the dt_ratio defined for this step, we don’t want to have to re-calculate this delta/ideal ratio again and again for every calculation performed in this step, so it’s best to do it once per step as a global variable, and have every instance that needs to use it reference this global value:

global.step_delta = delta_time/ideal_time;

That’s the basics of it.

Difficulties

The main problem that people have is that they have to adjust every variable in their game that is time-dependent.  Not just speed, but alarms, image animation, and possibly even such things as the pitch and duration of sound effects.  This can get tricky and drive you nuts.  Especially when you have things going on in your game like non-linear acceleration.

Another tricky problem with delta_time is the accumulation of rounding errors.

Let’s look at what happens when we want to use delta_time to achieve an average speed of 6 pixels/step:

speed = ideal_speed * delta_time * room_speed * 0.000001;

speed = 6 * delta_time * 30 * 0.000001

Let’s say that delta time is always exactly the ideal delta_time, 33333 microseconds.  That would give us:

speed = 6 * 33333 * 30 * 0.000001

speed = 5.99994 //shit. um...

Close enough, right?  Well, it adds up over time.  And keep in mind, if delta_time varies from the ideal_delta_time, the error can be larger.

Can’t we just round it?

Well… no.  We can’t.  Because it’s entirely possible, and even likely, that you can have a legitimate fractional speed as a result of delta_time adjustments, especially in the event that your game is lagging frames!  And rounding the value in that case would actually result in even more error.

Well, so what? Can’t we just live with it?

Maybe? I’m not sure what else you can do, honestly.  But this does leave us with problems. Even in the simple demo project that I’ve created, after just a minute or so, I can observe my control object deviate out of sync with the various delta-based objects.  Even the different delta-based objects will fall out of sync with each other, due to tiny differences in the way they calculate their deltas.

Most distressing of all, I’ve seen the objects glitch at their reversal point, getting stuck in the edge of the screen.  This isn’t consistent, and seems to depend on fluctuations in delta_time at just the right time to adjust the instance far enough outside of the room that the Intersect Boundary event will be triggers on two successive steps, resulting in the instance oscillating, becoming stuck on the boundary.

I’m still looking for a solution for how to fix this issue.

Manual errata

The manual gives us the following incorrect example:

speed = spd * (ot - delta_time);

Where ot is the value of delta_time from one step ago, e.g. a “delta_time_previous”.

This is wrong. The manual is in error, this is not how delta_time works.  It seems whoever wrote the manual was under a mistaken impression that delta_time is basically a point-in-time value of System.DateTime(Now) that is updated every Step, and that you would then need to calculate the actual delta by subtracting the current value for Now by the value of Now from the previous step.

The way delta_time really works, it is the number of microseconds since the previous step, essentially it is already the result of the expression “ot - delta_time” used in the example.  The correct implementation for the example should be:

speed = spd * delta_time/ideal_delta_time;

Alarms and animations

If you want to use delta_time to smooth out your movement, you should also consider whether you need to also consider delta_time with other time-based variables, such as image_speed.

Sprite animation is controlled by image_speed, and is normally set to a value of 1 by default, resulting in the sprite cycling through its sub-images at a speed of 1 per step.

An instance’s image_speed can be adjusted to speed up or slow down the animation speed.  At speeds slower than 1, the same sub-image will be drawn for more than 1 step in a row.  At speeds higher than 1, the animation will skip over sub-images.  At speeds less than 0, the animation will cycle in reverse.

If you’re using delta_time, and want your animation speed to also be delta_time-based, then the best thing to do is set image_speed to 0, and control animation manually.  Each step, increment image_index by 1 * ideal_time/delta_time:

image_index += ideal_time/delta_time;

Sprites can be set up and used in too many different ways to achieve customized ends, so if you’re storing sub-images in a sprite resource and selecting them in some other way than cycling through them as an animation, this is not necessarily going to apply.

For Alarms, you’ll want to stop using the Alarm Events that are built into GameMaker, and implement your own timer system. This is simple to do.

Step Event:

if timer >= -1
{
    timer -= ideal_time/delta_time;
    if timer <= 0
    {
          //do stuff
    }
}

You can have as many timer variables as  you need, and name them however you want, store them in an array if you want, or another data structure if you want.

Once you get used to it, you’ll probably like this better than the built-in Alarm system. YellowAfterlife’s article on Custom GameMaker Alarms is a great read for more information.

Capping the delta

Delta_time is intended to smooth out minor variations in fps, but if you have a longer interruption, delta_time will break your game. It’s useful to understand why and how, so you can prevent problems.

You’ll need to cap the amount of delta that you want the game to react to.  For example, if your game loses application focus, it will stop processing, essentially pausing the action.  The timer that delta_time is based on still keeps track of time however. This means that when you return application focus to the game, so much time will have passed that the ratio between ideal_time and delta_time will be enormous.  If your game uses this enormous ratio to adjust your game speed, it will almost certainly break the game.

Say you have switched application focus for a second to respond to an instant message you receive in another app. The instant you return to the game, the delta_time between the next step and the step your game paused at is going to be several seconds, instead of a value close to 1/30th or 1/60th of a second.  This means that in that step, everything in the game is going to move the equivalent of several seconds worth of distance, missing collisions that would have happened during those “missing” seconds”, and skip several seconds worth of animation frames, and count down several seconds worth of alarms, in just one step.  Things will go haywire — unless you cap the ideal:delta ratio.

You’ll want to experiment with your game and see where this limit should be set. It should be a value that is well short of any game-breaking bugs that a too-large ratio would cause. The exact value will depend on the specifics of your game.

To implement the cap is simple, though. Revising our earlier dt_ratio expression, we just use clamp() to cap it:

dt_ratio = clamp(delta_time / ideal_delta_time, min_cap, max_cap); 

The value of max_cap is your maximum allowed delta_time/ideal_time; min_cap is your minimum allowed delta_time/ideal_time.

Controlling the tempo

Delta_time can be used for more than just smoothing out your frame rate.  You can use these same ideas in slightly different ways to “control time” — that is to create a slow motion effect, or to speed things up like a “fast forward” effect.  To do this, just add in an extra factor, which I’ll call tempo:

speed_this_step = ideal_speed * (delta_time / ideal time) * tempo;

  • If tempo is equal to 1, the game will run at normal speed.
  • If tempo is >1, the game will speed up, moving faster than normal.  You can do this within some limits, but if you speed up the game too much, you’ll start having to deal with “high speed collisions” — situations where an instance moves so far that it can skip over objects that it should have collided with — which will require more work to handle correctly.
  • If tempo <1, the game will slow down.
  • if tempo == 0, the game will freeze.
  • If tempo < 0, the game will run backwards! (Not perfectly, though; any use of randomization will cause a loss of determinism which means that things won’t run backwards exactly as they ran forwards, only in reverse — and negative tempo won’t cause events like Collision, Create, and Destroy to “undo” or run in reverse.  But for certain uses, especially if used briefly and sparingly, this may not matter.)

How cool is that!  You can do a lot of things with this.

You can have a global.tempo, for controlling the overall speed of your game, and an instance.tempo factor that is unique to each instance.  Change the value of global.tempo, and the entire game will speed up or slow down; change the value of instance.tempo for selected instances, and only that instance will be affected.

speed_this_step = ideal_speed * (delta_time / ideal time) * global.tempo * instance_tempo;

A good range for tempo, for most games, is probably anywhere from 0.10 to 2.0 or 3.0 or so. But it will depend a lot on the specifics of your project, so you’ll need to experiment and test.

You use tempo to create a pause system easily, by applying delta_time to objects that are used in the game, setting global.tempo to 0, and controlling the global.tempo using objects that are not bound by delta_time so that they will remain responsive when the rest of the game is paused.

You could also create a script that performs this function:

///delta(ideal_value, global.tempo, instance_tempo) 
///@description returns value_this_step computed from ideal_value based on delta time, global and instance tempo values.

return argument[0] * (delta_time / ideal_time) * argument[1] * argument[2]; // We assume here that ideal_time is defined elsewhere in the project, probably as a #macro, or is represented inline with the direct calculation here.

Then when you use this script, you avoid code duplication, and it will make the code more readable:

speed = delta(10, 1, 1);

Beware bogus approaches

There are a few common approaches to implementing delta_time-like solutions that you may run into if you look at other people’s code.  I’ll touch on them briefly here, in order to explain why they’re not ideal.

Changing room_speed.  In older versions of GameMaker, before Studio, the program could change the room_speed and the GameMaker engine would adjust its speed on the fly.  This was very easy to do, but wrong.  For example, if you set the room_speed to 0, or a negative number, that would break the game.  A room_speed of 0 would freeze the game, and make everything stop, even the runner, so that the game would never update again.  And negative room_speed values are not allowed.

Later on, GameMaker was updated so that the room_speed variable could be changed during runtime (so it wasn’t a read-only variable) but that changes did not take effect at runtime for the current room, meaning you could not change room_speed in the current room and see immediate results; only by leaving and returning to the room, or restarting the room would the new room_speed be in effect.  This effectively ended adjusting room_speed as a viable approach.

Programmers who have the idea of how to do a technique like delta_time, but haven’t heard of it before, will sometimes “re-invent the wheel”, usually by using fps_real and room_speed to create a ratio similar to delta_time/ideal time.  This approach can seem at first to work, but it is always off because fps_real is the actual, achieved fps for the previous frame.  For the current frame, however, fps_real could actually be a very different value from what it was one step ago, meaning that if your game just did a huge amount of work in the previous frame, say to initialize new instances that were just created, and it doesn’t need to do that work in the current frame, the calculations could be way off, giving a poor result.  As long as fps_real is not changing dramatically from one step to the next, and is reasonably close to the target fps for the game, it will appear to work well, but when fps is very inconsistent, which is when you really need it, it will not work well.

If you see these techniques in use in a project, it can be a pain to convert from the bogus delta time technique to the good technique.  You’ll need to meticulously review the code for the entire project and find every place that uses the bad technique, and refactor it to use the good technique. Depending on how well documented the code is, and how expressive the code style is, this can easily get out of hand.  The best thing to do is to use version control and create a branch, and work on the conversion in the branch, and just focus on converting the delta time system before you go about making any other changes.  Take notes and put comments in the code to keep track of your progress, remind you of things that you need to do, need to test, or need to figure out.

Demo

In writing this article, I created a demo project to help me understand the concept of delta_time, and test various approaches. You can download it for free and play with it yourself.

Summary

  • Delta_time is simply the number of microseconds that have elapsed since the previous step. This value can be used to smooth out unevenness in frame rate if the program drops below the ideal frame rate.
  • It’s easier to implement delta_time if you decide to use it from the very beginning.
  • The basic delta time expression is: 
    N * delta_time * room_speed * 0.000001
  • You can adjust the tempo of the game even more by using global and instance tempo variables, and multiplying by those as well:
    N * delta_time * room_speed * 0.000001 * global.tempo * tempo
  • You can create a simple pause system (in part) by setting the global.tempo (or a custom pause factor varable) to 0.
  • Remember to consider things like animation speed and timers as well as movement.
  • Avoid using the built-in Alarms Events, movement functions and speed variables; it’s easier to manage everything if you take complete control over the position, speed, etc. by using your own system.
  • Cap the amount that delta_time can adjust your game, to keep things from going out of control after a long pause between frames.
  • Delta_time will result in values that are slightly off the ideal target value due to decimal imprecision/rounding errors.
  • Test thoroughly!
csanyk.com © 2016
%d bloggers like this: