作为一个游戏开发者,我想大家或多或少的都从一些老鸟那里听到过,不能用系统自带的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个以下的定时器是没有太大问题的,当然如果一个游戏到了需要这么多定时器的时候,我觉得就应该优化了。