AS3 Frame Based setInterval and setTimeout
Sometimes you want to be able to use a timeout or interval but, as they are tied to milliseconds, if the frame-rate of your movie goes down from what you expected - games is a good example - then you can run into trouble.
I recently had this problem with a game, that when the frame rate lowered, cyclic "emitters" (ie creating bubbles underwater) would not be synced with the expeted speed of the game.
The solution. A setInterval and setTimeout that you can set using milliseconds (or frames if you want) and will be called at the expected number of frames. The only caviet with this approach is that it needs to be inited with the stage object, both so it can get an ENTER_FRAME event and the current FPS.
Example Use
-
package {
-
import flash.display.MovieClip;
-
import com.touchmypixel.utils.FrameTimer;
-
-
public class TOTest extends MovieClip{
-
-
public function TOTest() {
-
FrameTimer.init(stage);
-
FrameTimer.setTimeout(2000, test1);
-
FrameTimer.setTimeout(2500, test1);
-
FrameTimer.setFrameTimeout(35, test2);
-
FrameTimer.setFrameTimeout(70, test2);
-
FrameTimer.setInterval(2000, test3);
-
FrameTimer.setFrameInterval(70, test5);
-
-
var iv:int = FrameTimer.setFrameInterval(15, test6);
-
FrameTimer.clearInterval(iv);
-
-
var to:int = FrameTimer.setTimeout(500, test7);
-
FrameTimer.clearTimeout(to);
-
-
}
-
-
private function test7():void {
-
// should never be printed
-
trace("NOT SHOWN------ Timeout: milliseconds");
-
}
-
-
private function test6():void {
-
// should never be printed
-
trace("NOT SHOWN------ Interval: frames");
-
}
-
-
private function test5():void{
-
trace("Interval: frames");
-
}
-
-
private function test3():void{
-
trace("Interval: milliseconds");
-
}
-
-
public function test1()
-
{
-
trace("Timeout: milliseconds");
-
}
-
-
public function test2()
-
{
-
trace("Timeout: frames");
-
}
-
}
-
-
}
Source
It makes use of "internal classes" which I had not heard about until recently. I'm finding them really good in some cases and have heard they are actually faster than using non-defined objects ie {x:0, y:10} as the compiler can make optimizations to them. The only thing you need to remember is that they must be defined outside your package - which can be counter-intuitive and a little confusing at first.
-
/*
-
* FrameTimer.as
-
* Copyright: 2008 Touch My Pixel - www.touchmypixel.com - contact@touchmypixel.com
-
* Please see X for discussion
-
*/
-
-
package com.touchmypixel.utils {
-
-
import com.adobe.utils.DictionaryUtil;
-
import flash.display.Stage;
-
import flash.events.Event;
-
import flash.events.EventDispatcher;
-
import flash.utils.Dictionary;
-
-
public class FrameTimer {
-
-
private static var frameRate:int = 35;
-
private static var scope:Stage;
-
private static var timeouts:Dictionary;
-
private static var intervals:Dictionary;
-
private static var timoutsCounter:int = 1;
-
private static var intervalsCounter:int = 1;
-
private static var millisecondsPerFrame:int = 0;
-
-
public function FrameTimer() {}
-
-
public static function init(_scope:Stage)
-
{
-
frameRate = _scope.frameRate;
-
scope = _scope;
-
timeouts = new Dictionary();
-
intervals = new Dictionary();
-
millisecondsPerFrame = Math.round(1000 / frameRate);
-
scope.addEventListener(Event.ENTER_FRAME, doTimer);
-
}
-
-
static private function doTimer(e:Event):void {
-
var to:TimeoutItem;
-
for each(to in timeouts) {
-
if (--to.framesLeft == 0) {
-
to.callback();
-
delete timeouts[to];
-
}
-
}
-
-
var iv:IntervalItem;
-
for each(iv in intervals) {
-
if (--iv.framesLeft == 0) {
-
iv.callback();
-
iv.framesLeft = iv.frameInterval;
-
}
-
}
-
}
-
-
/*==================================================================================*/
-
public static function setFrameTimeout(callback:Function, frames:int):int
-
{
-
if (scope != null) {
-
timeouts[++timoutsCounter] = new TimeoutItem(frames, callback);
-
return(timoutsCounter);
-
}else {
-
trace("Please INIT with scope");
-
}
-
return(0);
-
}
-
-
public static function setTimeout(callback:Function, milliseconds:int):int
-
{
-
if (scope != null) {
-
timeouts[++timoutsCounter] = new TimeoutItem(Math.round(milliseconds/millisecondsPerFrame), callback);
-
return(timoutsCounter);
-
}else {
-
trace("Please INIT with scope");
-
}
-
return(0);
-
}
-
-
public static function clearTimeout(key:int)
-
{
-
delete timeouts[key];
-
}
-
-
/*==================================================================================*/
-
public static function setFrameInterval(callback:Function, frames:int):int
-
{
-
if (scope != null) {
-
intervals[++intervalsCounter] = new IntervalItem(frames, callback);
-
return(intervalsCounter);
-
}else {
-
trace("Please INIT with scope");
-
}
-
return(0);
-
}
-
-
public static function setInterval(callback:Function, milliseconds:int):int
-
{
-
if (scope != null) {
-
intervals[++intervalsCounter] = new IntervalItem(Math.round(milliseconds/millisecondsPerFrame), callback);
-
return(intervalsCounter);
-
}else {
-
trace("Please INIT with scope");
-
}
-
return(0);
-
}
-
-
public static function clearInterval(key:int)
-
{
-
delete intervals[key];
-
}
-
-
/*==================================================================================*/
-
public static function clearAll()
-
{
-
timeouts = new Dictionary();
-
intervals = new Dictionary();
-
}
-
-
public static function clearAllTimeout()
-
{
-
timeouts = new Dictionary();
-
}
-
-
public static function clearAllIntervals()
-
{
-
intervals = new Dictionary();
-
}
-
}
-
}
-
-
internal class TimeoutItem
-
{
-
public var callback:Function;
-
public var framesLeft:int;
-
-
public function TimeoutItem(_framesLeft:int, _callback:Function)
-
{
-
callback = _callback;
-
framesLeft = _framesLeft;
-
}
-
}
-
-
internal class IntervalItem
-
{
-
public var callback:Function;
-
public var framesLeft:int;
-
public var frameInterval:int;
-
-
public function IntervalItem(_frameInterval:int, _callback:Function)
-
{
-
callback = _callback;
-
frameInterval = _frameInterval;
-
framesLeft = _frameInterval;
-
}
-
}