我正在构建一个总是有一些移动的游戏,所以我使用了很多Timer实例来控制重复和触发运动。
现在问题是我开始注意到一些性能“滞后”。这是由于计时器吗?你建议使用ENTER_FRAME事件吗?
相关:您是否建议任何其他可以提高性能的图书馆/方法?简单的Tween库本身是不够的。
我正在构建一个总是有一些移动的游戏,所以我使用了很多Timer实例来控制重复和触发运动。
现在问题是我开始注意到一些性能“滞后”。这是由于计时器吗?你建议使用ENTER_FRAME事件吗?
相关:您是否建议任何其他可以提高性能的图书馆/方法?简单的Tween库本身是不够的。
也许它会更有意义 只运行一个计时器 对于这个问题 ... 据我所知,运行的Timer需要一个完整的线程...... 把它放在伪代码中,Timer线程的主要代码是这样的......
while (input.isEmpty()) {
wait(interval);
output.add({timerId:thisId, tickId: tickId++});
}
输出是一个出列主线程(执行ABC)现在每次检查...有很多计时器,你将有很多线程,这是一个不必要的开销...也是,对于每个事件,从主线程的计时器需要弹出deque,这是昂贵的,因为它必须是线程安全的...然后必须找到相应的计时器,必须创建一个计时器事件(分配也相当昂贵) )然后发送,这也是多个电话的问题......
所以尝试拥有一个计时器,或者使用setInterval ...另外,考虑一下,flash中的事件模型非常好,但价格昂贵......它用于解耦,以确保一个漂亮的架构......出于同样的原因,这对性能危急情况不利......再一次,派遣一个活动很昂贵......
我做了一个小班,这是一个更多的手册(这只是为了说明我的观点,虽然它在理论上被使用了):
package {
import flash.utils.*;
public class Ticker {
//{ region private vars
private var _interval:int;
private var _tick:uint = 0;
private var _tickLength:Number;
private var _callBacks:Dictionary;
//} endregion
public function Ticker(tickLength:Number = 0) {
this.tickLength = tickLength;
this._callBacks = new Dictionary();
}
//{ region accessors
/**
* the current tick
*/
public function get tick():uint { return _tick; }
/**
* the tick length. set to a non-positive value, to stop ticking
*/
public function get tickLength():Number { return _tickLength; }
public function set tickLength(value:Number):void {
if (this._tickLength > 0) clearInterval(this._interval);
if ((this._tickLength = value) > 0) this._interval = setInterval(this.doTick, value);
}
//} endregion
/**
* add a callback, to be called with every tick
* @param callback function (tick:int):*
*/
public function addCallback(callback:Function):void {
this._callBacks[callback] = callback;
}
/**
* removes a callback previously added and returns true on success, false otherwise
* @param callback
* @return
*/
public function removeCallback(callback:Function):Boolean {
return delete this._callBacks[callback];
}
/**
* executes a tick. actually this happens automatically, but if you want to, you can set tickLength to a non-positive value and then execute ticks manually, if needed
*/
public function doTick():void {
var tick:uint = this._tick++;//actually, this is only superspicion ... amazingly, this makes no difference really ... :D
for each (var callback:* in this._callBacks) callback(tick);
}
}
}
它执行得很好......这里是一个基准测试类(如果你使用CS3 / CS4,你应该可以简单地在fla中使用它作为文档类):
package {
//{ region imports
import flash.display.*;
import flash.events.*;
import flash.sampler.getSize;
import flash.system.System;
import flash.text.*;
import flash.utils.*;
//} endregion
public class Main extends MovieClip {
//{ region configuration
private const timers:Boolean = false;//true for Timer, false for Ticker
private const delay:Number = 500;
private const baseCount:uint = 10000;//base count of functions to be called
private const factor:Number = 20;//factor for Ticker, which is a little more performant
//} endregion
//{ region vars/consts
private const count:uint = baseCount * (timers ? 1 : factor);
private const nullMem:uint = System.totalMemory;//this is the footprint of the VM ... we'll subtract it ... ok, the textfield is not taken into account, but that should be alright ... i guess ...
private var monitor:TextField;
private var frameCount:uint = 0;
private var secCount:uint = 0;
//} endregion
public function Main():void {
var t:Ticker = new Ticker(delay);
var genHandler:Function = function ():Function {
return function (e:TimerEvent):void { };
}
var genCallback:Function = function ():Function {
return function (tick:uint):void { };
}
for (var i:uint = 0; i < count; i++) {
if (timers) {
var timer:Timer = new Timer(delay, 0);
timer.addEventListener(TimerEvent.TIMER, genHandler());
timer.start();
}
else {
t.addCallback(genCallback());
}
}
this.addChild(this.monitor = new TextField());
this.monitor.autoSize = TextFieldAutoSize.LEFT;
this.monitor.defaultTextFormat = new TextFormat("_typewriter");
this.addEventListener(Event.ENTER_FRAME, function (e:Event):void { frameCount++ });
setInterval(function ():void {
monitor.text = "Memory usage: "
+ groupDidgits(System.totalMemory - nullMem)
+ " B\navg. FPS: " + (frameCount /++secCount).toPrecision(3)
+ "\nuptime: " + secCount + "\nwith " + count + " functions";
}, 1000);
}
private function groupDidgits(n:int,sep:String = " "):String {
return n.toString().split("").reverse().map(function (c:String, i:int, ...rest):String { return c + ((i % 3 == 0 && i > 0) ? sep : ""); } ).reverse().join("");
}
}
}
在我的机器上,使用60 FPS targetstet,我得到平均FPS为6.4(3分钟后)和10-14 MB内存使用(波动来自于TimerEvent对象需要被垃圾收集的事实),10000个函数被定时器调用...使用其他类,我得到55.2 FPS,95.0 MB内存使用率(非常恒定,波动率为1%),直接调用200000个函数...这意味着,在因子20,你得到的帧率为9倍更高,你只使用8倍的内存......这应该可以让你知道一个计时器创建的足迹多少...
这应该给你一个粗略的想法,在哪个方向去......
[编辑] 我被问到,为什么我使用私有变量...哲学问题......我的规则:永远不要让任何人从外面直接改变你的对象的状态...想象一下 Ticker::_tickLength
是 protected
...有人将其子类化,并写入该变量......具有什么效果?的价值 Ticker::tickLength
将与间隔长度不同......我真的没有看到优势......
此外,私有字段仅在类中有效...这意味着任何人都可以在子类中重新定义它们而不会发生任何冲突......
如果我认为,那个子类应该有一个 protected
对超类中定义的状态生效的方法,我做了一个 protected
setter ...但是,我仍然可以做出反应...我可以随意改变/验证/钳制值,抛出参数和范围错误,发送事件等等......如果你写一个类,你自己就要负责维持国家的完整性及其对行为的影响......
不要暴露你的类的内部工作...你可能需要更改它们,打破依赖代码......还有:子类化被严重过度... :)
所以这就是为什么 ... [/编辑]
格尔茨
back2dos
也许它会更有意义 只运行一个计时器 对于这个问题 ... 据我所知,运行的Timer需要一个完整的线程...... 把它放在伪代码中,Timer线程的主要代码是这样的......
while (input.isEmpty()) {
wait(interval);
output.add({timerId:thisId, tickId: tickId++});
}
输出是一个出列主线程(执行ABC)现在每次检查...有很多计时器,你将有很多线程,这是一个不必要的开销...也是,对于每个事件,从主线程的计时器需要弹出deque,这是昂贵的,因为它必须是线程安全的...然后必须找到相应的计时器,必须创建一个计时器事件(分配也相当昂贵) )然后发送,这也是多个电话的问题......
所以尝试拥有一个计时器,或者使用setInterval ...另外,考虑一下,flash中的事件模型非常好,但价格昂贵......它用于解耦,以确保一个漂亮的架构......出于同样的原因,这对性能危急情况不利......再一次,派遣一个活动很昂贵......
我做了一个小班,这是一个更多的手册(这只是为了说明我的观点,虽然它在理论上被使用了):
package {
import flash.utils.*;
public class Ticker {
//{ region private vars
private var _interval:int;
private var _tick:uint = 0;
private var _tickLength:Number;
private var _callBacks:Dictionary;
//} endregion
public function Ticker(tickLength:Number = 0) {
this.tickLength = tickLength;
this._callBacks = new Dictionary();
}
//{ region accessors
/**
* the current tick
*/
public function get tick():uint { return _tick; }
/**
* the tick length. set to a non-positive value, to stop ticking
*/
public function get tickLength():Number { return _tickLength; }
public function set tickLength(value:Number):void {
if (this._tickLength > 0) clearInterval(this._interval);
if ((this._tickLength = value) > 0) this._interval = setInterval(this.doTick, value);
}
//} endregion
/**
* add a callback, to be called with every tick
* @param callback function (tick:int):*
*/
public function addCallback(callback:Function):void {
this._callBacks[callback] = callback;
}
/**
* removes a callback previously added and returns true on success, false otherwise
* @param callback
* @return
*/
public function removeCallback(callback:Function):Boolean {
return delete this._callBacks[callback];
}
/**
* executes a tick. actually this happens automatically, but if you want to, you can set tickLength to a non-positive value and then execute ticks manually, if needed
*/
public function doTick():void {
var tick:uint = this._tick++;//actually, this is only superspicion ... amazingly, this makes no difference really ... :D
for each (var callback:* in this._callBacks) callback(tick);
}
}
}
它执行得很好......这里是一个基准测试类(如果你使用CS3 / CS4,你应该可以简单地在fla中使用它作为文档类):
package {
//{ region imports
import flash.display.*;
import flash.events.*;
import flash.sampler.getSize;
import flash.system.System;
import flash.text.*;
import flash.utils.*;
//} endregion
public class Main extends MovieClip {
//{ region configuration
private const timers:Boolean = false;//true for Timer, false for Ticker
private const delay:Number = 500;
private const baseCount:uint = 10000;//base count of functions to be called
private const factor:Number = 20;//factor for Ticker, which is a little more performant
//} endregion
//{ region vars/consts
private const count:uint = baseCount * (timers ? 1 : factor);
private const nullMem:uint = System.totalMemory;//this is the footprint of the VM ... we'll subtract it ... ok, the textfield is not taken into account, but that should be alright ... i guess ...
private var monitor:TextField;
private var frameCount:uint = 0;
private var secCount:uint = 0;
//} endregion
public function Main():void {
var t:Ticker = new Ticker(delay);
var genHandler:Function = function ():Function {
return function (e:TimerEvent):void { };
}
var genCallback:Function = function ():Function {
return function (tick:uint):void { };
}
for (var i:uint = 0; i < count; i++) {
if (timers) {
var timer:Timer = new Timer(delay, 0);
timer.addEventListener(TimerEvent.TIMER, genHandler());
timer.start();
}
else {
t.addCallback(genCallback());
}
}
this.addChild(this.monitor = new TextField());
this.monitor.autoSize = TextFieldAutoSize.LEFT;
this.monitor.defaultTextFormat = new TextFormat("_typewriter");
this.addEventListener(Event.ENTER_FRAME, function (e:Event):void { frameCount++ });
setInterval(function ():void {
monitor.text = "Memory usage: "
+ groupDidgits(System.totalMemory - nullMem)
+ " B\navg. FPS: " + (frameCount /++secCount).toPrecision(3)
+ "\nuptime: " + secCount + "\nwith " + count + " functions";
}, 1000);
}
private function groupDidgits(n:int,sep:String = " "):String {
return n.toString().split("").reverse().map(function (c:String, i:int, ...rest):String { return c + ((i % 3 == 0 && i > 0) ? sep : ""); } ).reverse().join("");
}
}
}
在我的机器上,使用60 FPS targetstet,我得到平均FPS为6.4(3分钟后)和10-14 MB内存使用(波动来自于TimerEvent对象需要被垃圾收集的事实),10000个函数被定时器调用...使用其他类,我得到55.2 FPS,95.0 MB内存使用率(非常恒定,波动率为1%),直接调用200000个函数...这意味着,在因子20,你得到的帧率为9倍更高,你只使用8倍的内存......这应该可以让你知道一个计时器创建的足迹多少...
这应该给你一个粗略的想法,在哪个方向去......
[编辑] 我被问到,为什么我使用私有变量...哲学问题......我的规则:永远不要让任何人从外面直接改变你的对象的状态...想象一下 Ticker::_tickLength
是 protected
...有人将其子类化,并写入该变量......具有什么效果?的价值 Ticker::tickLength
将与间隔长度不同......我真的没有看到优势......
此外,私有字段仅在类中有效...这意味着任何人都可以在子类中重新定义它们而不会发生任何冲突......
如果我认为,那个子类应该有一个 protected
对超类中定义的状态生效的方法,我做了一个 protected
setter ...但是,我仍然可以做出反应...我可以随意改变/验证/钳制值,抛出参数和范围错误,发送事件等等......如果你写一个类,你自己就要负责维持国家的完整性及其对行为的影响......
不要暴露你的类的内部工作...你可能需要更改它们,打破依赖代码......还有:子类化被严重过度... :)
所以这就是为什么 ... [/编辑]
格尔茨
back2dos
我建议使用ENTER_FRAME作为游戏引擎的主“勾号”。 ENTER_FRAME与Flash Player的帧速率完全对齐,帧速率是代码运行的真正最大帧速率。定时器等只是近似值,并且不能比ENTER_FRAME更快地执行。
事实上,虽然我最初使用Timers来处理所有的东西,但是由于混叠问题,我正在慢慢地离开它们。如果将Timer设置为30fps,但Flash Player最终以15fps运行,则Timer将最终在ENTER_FRAME事件之间两次调度它的TIMER事件。如果这些TIMER事件导致昂贵的代码(如果它是你的游戏引擎的嘀嗒声那么他们会这么做),那么它有可能推动玩家的 实际 帧率较低(因为现在你每个ENTER_FRAME滴答两次)。
所以,如果你有想要定期运行的东西,那么Timer很好,但是为了运行接近你的SWF实际帧速率的任何东西,我建议只使用SWF的帧速率并根据需要调整逻辑。
一种方法是计算每个ENTER_FRAME的时间增量。如果你有基于时间的逻辑,这是最好的方法。另一种方法是,如果你的SWF假设一个固定的更新速率(比如基于计时器的代码),那么如果你已经超过了任何给定的ENTER_FRAME的时间增量,那就是调用游戏的tick方法。
我会 不 如果你落后(或者你最终会得到与计时器相同的情况),建议每个ENTER_FRAME做两个滴答。在某个时刻,你的游戏必须放慢速度或变得无法播放(因为增量太大了)。当你已经放慢速度时,每个ENTER_FRAME执行多次勾选只会进一步降低你的速度。用户可以更好地处理 放缓 比他们更好的游戏 跳绳 游戏。
如果你没有使用补间库,我会看看tweenlite或tweenmax。它包括一个延迟的被叫定时器以及将补间分组在一起。它性能卓越,使用简单。
看一下性能测试
http://blog.greensock.com/tweening-speed-test/
玩笑
问题可能来自这样一个事实,即计时器不是真正可靠的,因为它们不像我们认为的那样独立于fps。当帧速率下降时,由于某种原因,定时器也会被不频繁地调用。这与C,C ++或其他OOP语言中的行为完全不同,因此很多人都陷入了这个陷阱。
为了避免这种情况,尝试使用ENTER_FRAME事件作为主游戏循环并在该循环内部,评估时间以了解您是否需要对游戏逻辑进行一次或多次更新。 这将使您的代码完全独立于fps。 您可以使用flash.utils.getTimer调用来获取自启动以来的时间。
我在我的网站上写了一篇关于此的帖子: http://fabricebacquart.info/wordpress/?p=9