作为一个游戏开发者,我想大家或多或少的都从一些老鸟那里听到过,不能用系统自带的Timer的说法。
不论是Windows编程中的SetTimer,还是AS3中的Timer,都不适合用于游戏编程中,建议少用,或者干脆不用。
纠其原因,最关键的还是因为Timer的不准确性,由于Timer事件优先级的原因,很难保证Timer会准时的干你想干的事情。
而游戏里面大部分情况下对时间的准确性是有很大的要求的,有时间需要精确到ms,甚至更低。比如在赛车游戏中,时间就是玩家最关注的点之一,不能有半点马虎。
对于玩家来说,时间就是金钱,时间就是生命啊。。。
不过幸运的是,我们有 getTimer 这个方法,该方法返回 Flash Player 运行以来的毫秒数。这是个万金油,我们可以通过取两帧之间的时间间隔来达到精确控制时间的目的。
先看以下代码:
package org.easily.ticker { import flash.events.Event; import flash.utils.getTimer; import org.easily.display.Container; public class TickerLauncher extends Container { public var tickerMgr:TickerManager = TickerManager.getInstance(); public var lastTime:Number = 0; public function TickerLauncher() { super(); start(); } public function start():void { lastTime = getTimer(); addEventListener(Event.ENTER_FRAME, onUpdate); } public function stop():void { removeEventListener(Event.ENTER_FRAME, onUpdate); } protected function onUpdate(event:Event):void { // TODO Auto-generated method stub var time:Number = getTimer(); var dtime:Number = time - lastTime; lastTime = time; tickerMgr.doTick(dtime); } override public function dispose():void { // TODO Auto Generated method stub stop(); super.dispose(); } } }
在 onUpdate 该方法中,计算了上一帧到这一帧之间的时候间隔 dtime ,有了这个时间,我们就可以制作一个基于该间隔时间的定时器。
比如我要启用一个100 ms 的定时器,可以这样:
var delay:Number = 100; var totalTime:Number = 0; function onTimer(dtime:Number):void { totalTime += dtime; if (totalTime >= delay) { //定时器到了 } }
一个定时器的雏形大概就是上面这个样子了。
可以包装一下,包装一个 Ticker 类,如下:
package org.easily.ticker { /** * @author Easily */ public class Ticker { public var timerFunc:Function; public var compFunc:Function; public function Ticker(timerFunc_:Function = null, compFunc_:Function = null) { timerFunc = timerFunc_; compFunc = compFunc_; } public function start():void { TickerManager.getInstance().addTicker(this); } public function stop():void { TickerManager.getInstance().removeTicker(this); } public function reset():void { } public function doTick(dtime:Number):void { } public function dispose():void { stop(); reset(); timerFunc = null; compFunc = null; } } }
doTick 方法中就是上面 onTimer 类似的逻辑了。
这里没有实现,是因为我写了两种定时器,一种是基于时间的定时器,一种是基于帧频的定时器。
下面分别是两种定时器的代码:
package org.easily.ticker { import flash.utils.Dictionary; /** * @author Easily */ public class TimeTicker extends Ticker { public var delay:Number; public var repeatCount:int; public var tickTime:Number; public var tickCount:int; public function TimeTicker(delay_:Number, repeatCount_:int = 0, timerFunc_:Function = null, compFunc_:Function = null) { super(timerFunc_, compFunc_); delay = Math.abs(delay_); repeatCount = Math.max(0, repeatCount_); reset(); } override public function reset():void { tickCount = 0; tickTime = 0; } override public function doTick(dtime:Number):void { tickTime += dtime; while (tickTime >= delay) { tickTime -= delay; ++tickCount; if (timerFunc != null) { timerFunc(); } if (repeatCount > 0 && tickCount >= repeatCount) { if (compFunc != null) { compFunc(); } dispose(); return; } } } } }
package org.easily.ticker { import flash.utils.Dictionary; /** * @author Easily */ public class FrameTicker extends Ticker { public var totalCount:int; public var tickCount:int; public var frequency:int; public var repeatCount:int; public function FrameTicker(frequency_:int = 1, repeatCount_:int = 0, timerFunc_:Function = null, compFunc_:Function = null) { super(timerFunc_, compFunc_); frequency = Math.max(1, frequency_); repeatCount = Math.max(0, repeatCount_); reset(); } override public function reset():void { tickCount = 0; } override public function doTick(dtime:Number):void { ++tickCount; if (tickCount == frequency) { tickCount = 0; ++totalCount; if (timerFunc != null) { timerFunc(dtime); } if (repeatCount > 0 && totalCount >= repeatCount) { if (compFunc != null) { compFunc(); } dispose(); } } } } }
有了两种定时器,应该需要一个管理器才方便,于是有了一个 TickerManager:
package org.easily.ticker { import org.easily.utils.ArrayUtils; /** * @author Easily */ public class TickerManager { public static var sInstance:TickerManager = new TickerManager(); public static function getInstance():TickerManager { return sInstance; } public var tickerList:Array = []; public function TickerManager() { if (sInstance) { throw new Error("TickerManager Singleton already constructed!"); } } public function get length():int { return tickerList.length; } public function doTick(dtime:Number):void { for each (var ticker:Ticker in ArrayUtils.clone(tickerList)) { ticker.doTick(dtime); } } public function addTicker(ticker:Ticker):void { ArrayUtils.push(tickerList, ticker); } public function removeTicker(ticker:Ticker):void { ArrayUtils.remove(tickerList, ticker); } } }
为了使用方便,不用写太多没必要的代码,于是写了一些静态方法,TimeTicker:
//实用的静态方法 public static var sTickerId:int = 0; public static var sTickerMap:Dictionary = new Dictionary(); /** * 设置一个指定时间后回调的定时器 * * @param delay:延迟时间 * @param callback:回调 */ public static function setTimeout(delay:Number, callback:Function):int { var ticker:TimeTicker = new TimeTicker(delay, 1, null, onTimeout); ticker.start(); var id:int = ++sTickerId % int.MAX_VALUE; sTickerMap[id] = ticker; return id; function onTimeout():void { clearTimeout(id); callback(); } } /** * 移除一个定时器 * * @param id:由setTimeout返回的id */ public static function clearTimeout(id:int):void { if (id == 0) { return; } var ticker:TimeTicker = sTickerMap[id]; if (ticker) { ticker.dispose(); delete sTickerMap[id]; } } /** * 设置一个定时器,间隔delay执行一次 * * @param delay:间隔时间 * @param callback:回调 * @param immediately:是否立即回调一次 * */ public static function setInterval(delay:Number, callback:Function):int { var ticker:TimeTicker = new TimeTicker(delay, 0, callback); ticker.start(); var id:int = ++sTickerId % int.MAX_VALUE; sTickerMap[id] = ticker; return id; } /** * 移除一个定时器 * * @param id:由setInterval返回的id */ public static function clearInterval(id:int):void { if (id == 0) { return; } var ticker:TimeTicker = sTickerMap[id]; if (ticker) { ticker.dispose(); delete sTickerMap[id]; } } /** * 设置一个指定次数的定时器 * * @param delay:间隔时间 * @param repeatCount:次数 * @param callback:回调 * @param complete:完成后的回调 */ public static function setTicker(delay:Number, repeatCount:int, callback:Function, compFunc:Function = null):int { var ticker:TimeTicker = new TimeTicker(delay, repeatCount, callback, onComplete); ticker.start(); var id:int = ++sTickerId % int.MAX_VALUE; sTickerMap[id] = ticker; return id; function onComplete():void { if (compFunc != null) { compFunc(); } clearTicker(id); } } /** * 移除一个定时器 * * @param id:由setTicker返回的id */ public static function clearTicker(id:int):void { if (id == 0) { return; } var ticker:TimeTicker = sTickerMap[id]; if (ticker) { ticker.dispose(); delete sTickerMap[id]; } } /** * 重置定时器 * @param id * */ public static function resetTimer(id:int):void { if (id == 0) { return; } var ticker:TimeTicker = sTickerMap[id]; if (ticker) { ticker.reset(); } } /** * 重置一个定时器的延时 * * @param id:ID * @param delay:新时间 */ public static function resetTime(id:int, delay_:int):void { if (id == 0) { return; } var ticker:TimeTicker = sTickerMap[id]; if (ticker) { ticker.delay = delay_; } }
FrameTicker:
//实用的静态方法 public static var sTickerId:int = 0; public static var sTickerMap:Dictionary = new Dictionary(); /** * 设置一个以帧率计算的回调 * * @param callback:回调 * @param frequency:频率 */ public static function setInterval(callback:Function, frequency:int = 1):int { var ticker:FrameTicker = new FrameTicker(frequency, 0, callback); ticker.start(); var id:int = ++sTickerId % int.MAX_VALUE; sTickerMap[id] = ticker; return id; } /** * 清除回调 */ public static function clearInterval(id:int):void { if (id == 0) { return; } var ticker:FrameTicker = sTickerMap[id]; if (ticker) { ticker.dispose(); delete sTickerMap[id]; } }
我正在做的项目中目前做到的时间的帧方面的管理就是使用这种方式,应付1000个以下的定时器是没有太大问题的,当然如果一个游戏到了需要这么多定时器的时候,我觉得就应该优化了。