
If you don’t know the difference between time-based code and frame-based code, chances are you’re using frame-based code and the quality of your game is suffering as a result. If you ever thought the turbo-button on your XT machine was the most-badass invention ever because it could make your computer games play in slow motion, then you’re familiar with some of the unintended side effects of frame-based code. Time-based code strives to provide the same gameplay experience no matter what external factors might be trying to throw the game simulation off. Time-based code should not care about the number of WhateverHZ the player’s CPU has, how many programs he might be running, or if he happened to blow up more stuff than you did during your play tests. Frame based code sucks, because any one of those situations can cause the game to behave drastically different, thus resulting in a gameplay experience that is probably less than perfect.
IMPORTANT: The following discussion uses two case studies to compare a time-based code approach against a frame-based code approach. I have embedded the resulting .swfs here for you to observe, but to truly evaluate a comparison between the two .swfs, I recommend you download the .swfs and compare them after executing locally. The observed benefits of the time-based approach are even more apparent without the performance bottlenecking created by the browser. Click here to grab source and .swfs.
The difference in the code between the two case studies is very minimal. Both examples are functionally identical except for a “move” function that moves the gryphon forward. The frame based move looks something like this:
private function move() : void
{
m_gryphon.x += 3;
}
This function is called every frame, and basically means “Every frame update, the gryphon should be moved by 3 pixels in the positive x direction.”
The result is observed in the Frame Based Case Study. Turning off turbo in the example essentially just injects some time-intensive code into the “game” loop in order to throw off the fps. In a traditional game environment, this same effect could be achieved by another process stealing CPU cycles, or by a game event that suddenly causes a spike in execution for a few ticks (like a stack of boxes suddenly colliding against a polygon).
Depending on the speed of your CPU, you should notice either a tangible or significant drop in the speed with which the “gryphon” travels across the screen (if you didn’t your machine is much much faster than mine, so I encourage you to download the source and tinker with the delay function until you notice results). If there is something causing a delay (in this case, a particularly slow function), the .swf cannot execute as fast as it was executing before. If it is executing slower, the fps is lower. If our fps is lower, it means we are performing our move function call less often, and because our movement amount is constant, we don’t travel as much distance in the same amount of time.
That last sentence gives us the clue as to how to solve this particular problem. The primary symptom of the issue is that when the fps suddenly drops, we have fewer executions of our move function meaning our object will not travel the same distance in the same amount of time. If we track the time between the executions of our move function, we can figure out a delta time. Now we can specify the distance our gryphon moves per frame not as a constant, but as a function of the time between the frames, and the speed of the gryphon. Does that sound familiar? It should. You probably learned it your first week of physics: distance = speed * time.
Armed with this new knowledge, we need to rewrite our move function. In a timer based scenario, the move function looks like this:
private function move() : void
{
var deltaTime : int = m_deltaTimer.calculateDeltaTime();
var speed : Number = 360; // pixels per second
var speedInMilliseconds : Number = speed / 1000; // pixels per millisecond
var distanceTraveled : Number = deltaTime * speedInMilliseconds; // distance = speed * time
m_gryphon.x += distanceTraveled;
}
The only thing in the function that might look like magic is the deltaTimer. You can look at the code in more depth, but all calculateDeltaTime does is calculate the time that has passed since the function was last called. Everything else is just prep-work for our distance = speed * time calculation.
Other than that, the code is identical in these two examples. Observe in the Timer Based Case Study what happens when we toggle turbo on and off.
What you should see is that the time it takes the gryphon to cover the same distance is the same whether or not turbo is on. The movement is obviously more choppy. The fps has been hit with the same cycle stealing code as before. But, now the intentions of our code is preserved. In a game environment this means that the player can’t willfully or accidentally break the simulation.
The logic of movement is now frame rate independent meaning it no longer relies on speed of execution. The gameplay experience will be similar no matter who is playing the game and no matter how awesome or old and crappy their machine might be. We can now describe movement speeds in units that make sense (pixels per second, for example).
A fun side benefit of this development is we can also add time-based effects to our game engine with very little extra additional code. If we want to simulate slow motion, we just need to take the actual delta time and decrease it before our game objects use it to process their movement. Suddenly everything can move in slow motion in response to the player getting a power up, and you didn’t need any special purpose code to support it.
Everything is hunky-dory and magical now, and we can proceed with our plans for world domination, right? Well, not exactly, there are some disadvantages that come with using the method as described above. To make our timing code a little more robust we have to jump through a few more hoops. They’re not difficult to do, but it does involve understanding some higher level concepts so I will tackle them in a separate article. If you really can’t wait, you can get into all the gory details in Glenn Fiedler’s very excellent article on the subject.
Click here to download the source code for the two case studies.
Word.
I’ve been building a time-based engine that I’m hoping will do a lot of good out in the wild. I’ve only put one demo so far: http://blog.sokay.net/2008/01/17/the-liberty-engine-puttin-my-circles-where-my-mouth-is/ (hope you don’t mind the plug) The demo’s pretty outdated already, but I’ve been putting off a 2nd demo while I continue polishing it. I’m being very idealistic with my coding.
Don’t mind the plug at all.
Next Friday’s post should be part 2 in the series regarding timing and game loops, perhaps with a tangential discussion on physics.
So after your post and knowing that you were doing another, I decided to update on the Liberty Engine.
I thought of e-mailing you a link, but I couldn’t find an address anywhere. . . .
http://blog.sokay.net/2008/04/17/liberty-engine-update-running-clean/
Forgive the lack of a contact page. I’ll get one up soon. The time-based ticker code will be going up tomorrow morning. Nice work on Liberty. Keep it up.
[…] was happy to find this article from gamepoetry.com last week. That’s really the reason I felt compelled to put up this […]