Author Topic: Request: FlxTimer  (Read 2523 times)

Kronoshifter

  • Member
  • **
  • Posts: 34
  • Karma: +0/-0
    • View Profile
Request: FlxTimer
« on: Wed, Sep 29, 2010 »
I'm just wondering why there hasn't been a FlxTimer class yet. I know that there's FlxG.elapsed, but it feels really clunky sometimes. I'd definitely prefer this:
Code: [Select]
var timer:FlxTimer = new FlxTimer(5);
timer.add(someFunction);
timer.start();
addTimer(timer);
to this:
Code: [Select]
var timer:Number = 5;
timer -= FlxG.elapsed;
if (timer <= 0)
{
     timer = 5;
     someFunction();
}

Ok, I completely abandoned this on the forums, instead go to the Flash Game Dojo, I have an up to date page on this class there.
http://flashgamedojo.com/wiki/index.php?title=FlxTimer
« Last Edit: Wed, Nov 24, 2010 by Kronoshifter »

Kronoshifter

  • Member
  • **
  • Posts: 34
  • Karma: +0/-0
    • View Profile
Re: Request: FlxTimer
« Reply #1 on: Wed, Sep 29, 2010 »
Ok, I actually came up with something on my own. It was actually fairly simple, although it required some modification of a few Flixel classes.

Here's the FlxTimer class:
Code: [Select]
package org.flixel
{

/**
* ...
* @author Kronoshifter
*/
public class FlxTimer
{
protected var _delay:Number;
protected var _timeLeft:Number;
protected var _running:Boolean;
protected var _dispatchOnStart:Boolean;

protected var _listener:Function;
protected var _listenerParams:Array;

/**
* The timer's current state; true if the timer is running, otherwise false.
*/
public function get running():Boolean
{
return _running;
}

/**
* Creates a FlxTimer object. Call add() to add a method to be called by the timer.
* Note: For this to work, the FlxState and FlxObject classes must have new methods added to support it.
*
* @param delay The delay, in seconds, of the timer.
* @param dispatchOnStart Whether this timer should dispatch its listener when the start() method is called.
*/
public function FlxTimer(delay:Number, dispatchOnStart:Boolean = false)
{
_delay = delay;
_timeLeft = delay;
_dispatchOnStart = dispatchOnStart;

_running = false;
}

/**
* Starts the timer. If the timer is started, it will be restarted.
*/
public function start():void
{
if (FlxG.pause) return;

if (_running)
{
stop();
start();
}
else
{
if (_dispatchOnStart) dispatch();
_running = true;
}
}

/**
* Stops the timer
*/
public function stop():void
{
_running = false;
_timeLeft = _delay;
}

/**
* Adds a method to listen for the timer's dispatch.
* Each FlxTimer can only have one listener at a time. Calling this method more than once will overwrite the current listener and parameters.
*
* @param listener The method to be called by the timer
* @param ...params The parameters that the method needs
*/
public function add(listener:Function, ...params):Function
{
_listener = listener;
_listenerParams = params;

return listener;
}

public function update():void
{
if (_running)
{
_timeLeft -= FlxG.elapsed;
if (_timeLeft <= 0)
{
dispatch();
_timeLeft = _delay;
}
}
}

/**
* Calls the method listening for this timer's dispatch
*/
protected function dispatch():void
{
switch (_listenerParams.length)
{
case 0:
_listener();
break;

case 1:
_listener(_listenerParams[0]);
break;

default:
_listener.apply(null, _listenerParams);
break;
}
}

public function toString():String
{
return '[FlxTimer: delay = ' + _delay.toString() + ']';
}

}

}

The way I handle the listener being called was inspired by the AS3 Signals library.

For this to work, add these two methods to FlxState and FlxObject:
Code: [Select]
/**
 * Adds a FlxTimer object to the game loop.
 * FlxState is adding this FlxTimer to its built-in timer group to automate updating.
 *
 * @param Timer The <code>FlxTimer</code> you want to add.
 *
 * @return The same <code>FlxTimer</code> that was passed in.
 */
public function addTimer(Timer:FlxTimer):FlxTimer
{
timerGroup[timerGroup.length] = Timer;
return Timer;
}

/**
 * Removes a FlxTimer object from the game loop.
 * FlxState is removing this FlxTimer from its built-in timer group.
 *
 * @param Timer The <code>FlxTimer</code> you want to remove.
 *
 * @return The removed <code>FlxTimer</code>.
 */
public function removeTimer(Timer:FlxTimer):FlxTimer
{
timerGroup.splice(timerGroup.indexOf(Timer), 1);
return Timer;
}

public function updateTimers():void
{
var len:int = timerGroup.length;
for (var i:int = 0; i < len; i++)
{
timerGroup[i].update();
}
}

timerGroup is a Vector. It should be declared and instantiated as so
Code: [Select]
protected var timerGroup:Vector.<FlxTimer>;
timerGroup = new Vector.<FlxTimer>(); // This goes in the constructor

Now just call the updateTimers() method in the update() method.

I'm open to suggestions on ways to improve what I've done here. Especially if there's anything about using a Vector instead of an Array. I've heard that an Array is actually faster than a Vector if the Vector isn't using primitive types.
« Last Edit: Sat, Oct 2, 2010 by Kronoshifter »

Kronoshifter

  • Member
  • **
  • Posts: 34
  • Karma: +0/-0
    • View Profile
Re: Request: FlxTimer
« Reply #2 on: Wed, Sep 29, 2010 »
Ok, I found a much better way to implement FlxTimer. Add this code at the end of FlxG:
Code: [Select]
/**
 * An array that holds FlxTimer objects
 */
static protected var timerGroup:Array = [];

/**
 * Adds a FlxTimer to the game loop.
 * FlxG is adding this FlxTimer to its built-in timer group to automate updating.
 *
 * @param Timer The <code>FlxTimer</code> you want to add.
 *
 * @return The same <code>FlxTimer</code> that was passed in.
 */
static public function addTimer(Timer:FlxTimer):FlxTimer
{
timerGroup[timerGroup.length] = Timer;
return Timer;
}

/**
 * Removes a FlxTimer object from the game loop.
 * FlxG is removing this FlxTimer from its built-in timer group.
 *
 * @param Timer The <code>FlxTimer</code> you want to remove.
 *
 * @return The removed <code>FlxTimer</code>.
 */
static public function removeTimer(Timer:FlxTimer):FlxTimer
{
timerGroup.splice(timerGroup.indexOf(Timer), 1);
return Timer;
}

static internal function updateTimers():void
{
var i:int;
var len:int;

var t:FlxTimer;

len = timerGroup.length;
for (i = 0; i < len; i++)
{
t = timerGroup[i] as FlxTimer;
t.update();
}
}

Now go into FlxGame, there is a section in the update method that looks like this:
Code: [Select]
//State updating
FlxObject._refreshBounds = false;
FlxG.updateInput();
FlxG.updateSounds();

Add the call to FlxG.updateTimers(), so now it will look like this.
Code: [Select]
//State updating
FlxObject._refreshBounds = false;
FlxG.updateInput();
FlxG.updateSounds();
FlxG.updateTimers()

I apologize for the triple post, I just felt that I needed to get this better implementation before too many people screwed with their code. Implementing it like this allows for the use of FlxTimer in classes that don't inherit from FlxState or FlxObject, plus there is less of a chance to add bad code and completely screw up your copy of Flixel. I do suggest adding addTimer() and removeTimer() methods to those two classes, but this time they will look like this:
Code: [Select]
public function addTimer(Timer:FlxTimer):FlxTimer
{
return FlxG.addTimer(Timer);
}

public function removeTimer(Timer:FlxTimer):FlxTimer
{
return FlxG.removeTimer(Timer);
}

They go in FlxState and FlxObject, in case I wasn't clear enough before.
« Last Edit: Thu, Sep 30, 2010 by Kronoshifter »

Kronoshifter

  • Member
  • **
  • Posts: 34
  • Karma: +0/-0
    • View Profile
Re: Request: FlxTimer
« Reply #3 on: Sat, Oct 2, 2010 »
Ok, for anyone actually using this class, I've fixed a bug where a timer would still run while the game was paused. In case anyone's wondering, you just need to add this line at the beginning of the update() method:
Code: [Select]
if (FlxG.pause) return;