8bitrocket.com
8Apr/081

Tutorial : Creating an Optimized AS3 Game Timer Loop

Tutorial : Creating an Optimized AS3 Game Timer Loop

It this tutorial we create what I consider to be my most optimized game timer loop. I will start with the game loop created in my last tutorial and optimize it further. We will add three different optimizations to the game loop. First, we will simply lock our canvas before we use the copyPixels operation on it. Then we will unlock it after. Second, we will implement a sleep based active render timer. Third, we will make sure our game loop updates on every event rather than on every frame.

One of the most discussed, least understood and ultimately most frustrating aspects of Flash game programming is keeping a constant frame rate across all of the different SWF players. We seem to get a different frame rate inside of the IDE, in the stand alone player, and in different browser / wmode combinations.

Normally, the theory is to keep your game running at about 30 FPS for smooth game play. So, what most of us do is set our Flash Movie frame rate at 30 FPS or more and hope for the best. If we are lucky enough to either create a game engine that doesn't tax the Flash player, or are skilled enough to create an optimized engine, we will get a pretty constant frame rate in the Flash environment. When we export the game and try it in the external player, we might also get pretty close to 30 FPS. We are really happy, and then we try the game in a browser. Usually this is where the trouble starts In an IE browser with wmode set to the default (Window), we will usually see a 2-5 frame rate drop. If any other browser, especially Firefox, our frame rate can vary wildly downward from there. Now, the latest Firefox update might repair this, but there seems to always be a situation where the frame rate outside of the Flash IDE (and sometimes inside of it) will just not be what you expect.

One other common problem with game timer loops is the game engine taxing the processor in such a way that it causes the game to drop frames and at the same time drop update and render cycles. This results in a slowdown in game play because the rendering engine cannot keep up with demand. Nothing is going to keep a game playing smoothly on a very old machine, but within reason, we should be able to keep our game running pretty smoothly at our desired frame rate. How can we do this?

1. BitmapData.lock and BitmapData.unlock.
The simple theory behind locking a BitmapData object is to stop display objects that reference it from updating themselves (a very costly operation) when the BitmapData object is being rendered with multiple copyPixels operations. When unlock() is called, all of the changes are then reflected in the display object.
In practice, it would look like the following. We are going to take the game loop from my 7800 Asteroids game out of context here, but it is being used just for demonstration purposes.

[cc lang="javascript" width="550"]
checkKeys();
updatePlayer();
updateRocks();
updateMissiles();
checkCollisions();
canvasBD.lock();
drawBackground();
drawPlayer();
drawRocks();
drawMissiles();
drawParts();
canvasBD.unlock();
[/cc]

The above is a simple demonstration of the 7800 Asteroids game loop. Before any of the draw operations, we lock the canvasBD, which is our single display canvas for our game. All of the drawXXX() functions use copyPixel operations to BLIT to this canvas. By locking it, we stop the canvasBitmap display object (not in code above) from being updated until the canvasBD.unlock() is called. This helps to not use as much CPU during the copyPixel operations and is effectively a poor man's doublebuffer operation without the double buffer overhead of having to copyPixels twice - once to the buffer and once to the display object.

2. The Sleep Based Timer using Active Rendering
The sleep based timer is definitely not my unique creation, and the code we will use isn't even my implementation. A fellow Flash Game Head , and friend of mine, Chris Cutler, takes the credit for introducing me to this AS3 implementation of Andrew Davidson's Java based version. Chris created the AS3 version based on Andrew's excellent book, Killer Game Programming in Java. I don't know Andrew, but his book is quite excellent and a great game development reference especially for AS3 programmers.

Andrew introduces a sleep based timer to actually slow down his code so it will run the same on all machines. In Flash we sometimes need to do this, but more regularly, we need to keep we struggle to keep our frame rate constant across platforms. He times the number of milliseconds it takes his game loop to run, and if he has time left over, he sleeps (or pauses) the loop until the next interation. This makes for a smooth loop. He adds to this technique to what he deems, Active Rendering. What he does is actually measure the time it takes to render the screen, and then make adjustments to his sleep timer based on these measurements. In this way, he has ultimate control over his game loop. By implementing this is AS3, so do we. Below is this code added to our game loop:

[cc lang="javascript" width="550"]
private function runGame(e:TimerEvent) {
_beforeTime = getTimer();
_overSleepTime = (_beforeTime - _afterTime) - _sleepTime;

checkKeys();
updatePlayer();
updateRocks();
updateMissiles();
checkCollisions();
canvasBD.lock();
drawBackground();
drawPlayer();
drawRocks();
drawMissiles();
drawParts();
canvasBD.unlock();

_afterTime = getTimer();
_timeDiff = _afterTime - _beforeTime;
_sleepTime = (_period - _timeDiff) - _overSleepTime;
if (_sleepTime <= 0) {
_excess -= _sleepTime
_sleepTime = 2;
}
gameTimer.reset();
gameTimer.delay = _sleepTime;
gameTimer.start();

while (_excess > _period) {
checkKeys();
updatePlayer();
updateRocks();
updateMissiles();
checkCollisions();
_excess -= _period;
}

frameTimer.countFrames();
frameTimer.render();
if (aRock.length ==0 && aActiveParticle.length==0) stopRunningGame();

}
[/cc]

In our Variable Definition Section, we have added these variables:

[cc lang="javascript" width="550"]
public static const FRAME_RATE:int = 40;
private var _period:Number = 1000 / FRAME_RATE;
private var _beforeTime:int = 0;
private var _afterTime:int = 0;
private var _timeDiff:int = 0;
private var _sleepTime:int = 0;
private var _overSleepTime:int = 0;
private var _excess:int = 0;
[/cc]

FRAME_RATE is set to be the number of updates we want per second. I like 40, so I have set mine to that.

_period is the number of milliseconds that we have to perform all of the operations per period.
In this case it is 25 (1000/40)

_beforeTime and _afterTime will create a simple code profiler.
We time all of the operations in our loop and see how long it too in milliseconds..
We hope for it to take less than 25 milliseconds (or _period value).

_timeDiff is the difference between the _afterTime and the _beforeTime.

_sleepTime is the period (25) minus the _timeDiff.
From both of these we subtract our _overSleepTime.

_overSleepTime is a measure of how accurate or sleepTime is on the last interval.
If the sleep time was off by a couple milliseconds, they will be added to the overSleep
for the next interval.

_excess holds all of the excess time that occurs in the render loop.
It is a running total in a way and it keeps the whole loop honest.
When the _excess is greater than our period (25) then we do an extra checkeys(), update(), and check collisions() of our objects.
We don't render them though.

What this code produces is a game loop that looks like it is rendering at the same speed even though in some cases the processor cannot keep up with the load. In that case, we update without rendering to keep objects moving at a constant rate. In the next render() the objects will appear at the new locations.

We have also mode a change to our gameTimer variable to make this work:

[cc lang="javascript" width="550"]
gameTimer=new Timer(_period,1); //changed in part 3 from 50
gameTimer.addEventListener(TimerEvent.TIMER, runGame);
gameTimer.start();
[/cc]

Our gameTimer variable used to be created with just the number of milliseconds to update on each interval. This would make it run constantly until we called gameTimer.stop(). That's not the case any more. We just run our gameTimer one time, then check the value produced (above) and the restart of loop after sleep is necessary. The number of milliseconds to repeat our interval is now to our _period (25), for this first one time run. We do this because our new method of timing will reset and start the timer inside the game loop - like this:

gameTimer.reset();
gameTimer.delay = _sleepTime;
gameTimer.start();

This starts up our gameTimer only after sleeping for the appropriate amount of time.

3. UpdateAfterEvent()
The third and final optimization we will add to this timer will be the use of the event.updateAfterEvent() method.
After taking a look at Keith Peter's latest book on AS3, Actionscript 3.0 Animation (it should be on every Flash Game Developer's book shelf, right next to the collective works of Colin Moock and Jobe Makar) I found this little tid bit.

By calling the event.updateAfterEvent() method of the TimerEvent object, we force Flash to update the screen immediately. In this way, we can set our movie Frame Rate to a relatively low number. I like 40. So, usually I would have set my FrameRate to 60 in hope that I would ge 35-40 in my browser. The result of this actually taxes the processor more because it is always trying to keep up with the 60 intervals per second. Now, with it set to 30, I can get the 40 updates a second I want and it will look smooth in all current browsers.

What do you do if the designer has created all of his animations set to 18 FPS, but the game needs to run at 30? Before now, I would have had to be very clever to handle this. Now, I can just set the movie to 18FPS and the FRAME_RATE (above) to 30. This will let you display animations at 18FPS, but game play can run at 30 updates a second.

Here is the game loop with this code added:

[cc lang="javascript" width="550"]
private function runGame(e:TimerEvent) {
_beforeTime = getTimer();
_overSleepTime = (_beforeTime - _afterTime) - _sleepTime;

checkKeys();
updatePlayer();
updateRocks();
updateMissiles();
checkCollisions();
canvasBD.lock();
drawBackground();
drawPlayer();
drawRocks();
drawMissiles();
drawParts();
canvasBD.unlock();

_afterTime = getTimer();
_timeDiff = _afterTime - _beforeTime;
_sleepTime = (_period - _timeDiff) - _overSleepTime;
if (_sleepTime <= 0) {
_excess -= _sleepTime
_sleepTime = 2;
}
gameTimer.reset();
gameTimer.delay = _sleepTime;
gameTimer.start();

while (_excess > _period) {
checkKeys();
updatePlayer();
updateRocks();
updateMissiles();
checkCollisions();
_excess -= _period;
}

frameTimer.countFrames();
frameTimer.render();
if (aRock.length ==0 && aActiveParticle.length==0) stopRunningGame();
e.updateAfterEvent();
}
[/cc]

Here is the 7800 Asteroids game Running with this new game loop code. This has a movie frame rate set to 30FPS, and in the IDE, external player and browser, it runs at a pretty constant 40-42 FPS.

 

Here is the 7800 Asteroids game Running with the older, non optimized game loop code. This has a movie frame rate set to 50 FPS, and the IDE, external player and browser, the frame rate wildly differs. The game loop is set to run every 15 milliseconds I can get 50-60 FPS in the external player and IDE (sometimes) but it drops to 30-35 when a lot of particles on the screen. In the browser (at least for me) it doesn't get above 35FPS.

 

Here You can download the .fla and new class files for your own use.

0saves
If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.
Comments (1) Trackbacks (0)
  1. Is it just me or the older game loop is now faster and more stable ?


Leave a comment

No trackbacks yet.

This site is protected by Comment SPAM Wiper.