【问题标题】:AS3 Timers vs. ENTER_FRAME performanceAS3 计时器与 ENTER_FRAME 性能
【发布时间】:2010-11-09 07:48:07
【问题描述】:

我正在构建一个游戏,它总是有一些移动的东西,所以我使用了很多 Timer 实例来控制重复和触发动作。

现在问题是我开始注意到一些性能“滞后”。这是由于计时器吗?您是否建议改用 ENTER_FRAME 事件?

相关:您是否建议任何其他可以提高性能的游戏的库/方法?简单的 Tween 库本身是不够的。

【问题讨论】:

    标签: flash actionscript-3 actionscript


    【解决方案1】:

    也许更有意义的是,只运行一个计时器... 据我所知,一个正在运行的 Timer 需要一个完整的线程...... 用伪代码来说,Timer线程的主代码就是这样的……

    while (input.isEmpty()) {
        wait(interval);
        output.add({timerId:thisId, tickId: tickId++});
    }
    

    输出是主线程(执行 ABC)每隔一段时间检查一次...有很多计时器,您将有很多线程,这是不必要的开销...此外,对于每个事件,消息从定时器发送到主线程需要弹出双端队列,这很昂贵,因为它必须是线程安全的......然后必须找到相应的定时器,必须创建一个定时器事件(分配是也相当昂贵)然后派出,这也是多次调用的问题......

    所以尝试使用 ONE 计时器,或使用 setInterval ...另外,请考虑 Flash 中的 Event 模型非常好,但价格昂贵 ...它用于解耦,以确保良好的架构 ...同样的原因,它不适合性能危急的情况......再次,调度一个事件是昂贵的......

    我做了一个小课,那是更多的手动(这只是为了说明我的观点,虽然它在理论上可以使用):

    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 目标,对于 10000 个函数,我得到 6.4 的平均 FPS(3 分钟后)和 10-14 MB 的内存使用量(波动来自于 TimerEvent 对象需要被垃圾收集的事实)用计时器调用...使用其他类,我得到 55.2 FPS,内存使用量为 95.0 MB(非常恒定,波动低于 1%),直接调用 200000 个函数...这意味着,在 20 倍时,您获得的帧速率为高出 9 倍,而您只使用了 8 倍的内存...这应该让您了解计时器创建了多少内存...

    这应该会让你有个大概的想法,往哪个方向走……

    [edit]有人问我,为什么我使用私有变量...哲学问题...我的规则:永远不要让外部的任何人直接更改您的对象的状态。 .. 想象Ticker::_tickLengthprotected ...有人将其子类化,并写入该变量...有什么效果? Ticker::tickLength 的值将与区间长度不同...我看不出有什么优势...

    此外,私有字段仅在类中有效...这意味着任何人都可以在子类中重新定义它们而不会发生任何冲突...

    如果我认为,子类应该有一个protected 方式来对超类中定义的状态生效,我会创建一个protected setter ...但是,我仍然可以做出反应...我可以改变/验证/限制值,随意抛出参数和范围错误,调度事件等等......如果你写一个类,你自己有责任维护它的状态的完整性和对它的行为的影响......

    不要暴露你的类的内部工作......你可能需要改变它们,破坏依赖代码......而且:子类化被严重高估了...... :)

    这就是为什么... [/edit]

    问候

    back2dos

    【讨论】:

    • “地区”的东西是怎么回事?
    • 哦。为什么你将成员变量设为私有而不是受保护?
    • 感谢有关计时器的宝贵信息。我得试试你的代码;代码听起来很有希望!
    • @luke 关于区域:我正在使用 flashdevelop 进行动作脚本开发……区域允许自定义折叠,因此我可以折叠类的不同部分……而且它为代码提供了额外的结构…… . 只是我的一个编码约定,可以这么说......
    • 它是关于封装和编写可靠的代码,它要么总是按预期工作,要么抛出运行时错误,如果是这样的话。用它做愚蠢的事情。对我来说,一个好的 API 是强大的、小的和面向问题的。不在乎如何,只要是可靠的。这就是我所期望的,这就是我自己做的原因。顺便说一句,子类化的重点不是摆弄一些超类属性,而是抽象行为的具体实现,是建立 IOC 的一个很好的工具。如果您真的想认真讨论这个问题,您可以就这一切提出问题。
    【解决方案2】:

    我建议使用 ENTER_FRAME 作为游戏引擎的主“刻度”。 ENTER_FRAME 与 Flash Player 的帧率完全一致,这是您的代码将运行的真正最大帧率。计时器等只是近似值,执行速度不能超过 ENTER_FRAME。

    事实上,虽然我最初将计时器用于我所有的东西,但由于混叠问题,我正在慢慢远离它们。如果您将 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 执行一个以上的滴答只会进一步减慢您的速度。与跳过游戏相比,用户可以更好地处理缓慢的游戏。

    【讨论】:

    • 感谢您提供宝贵的信息!问题是我正在使用两者的组合。 ENTER_FRAME 用于我的鼠标移动角色,Timer 用于移动汽车......不过,好消息是我在上述问题中提到的“滞后”仅在 Flash 创作工具中出​​现。当我单独打开 swf(在独立的 Flash 播放器中运行)时,速度是完美的 30 fps。
    【解决方案3】:

    如果您还没有使用 tween 库,我会查看 tweenlite 或 tweenmax。它包括一个延迟调用定时器以及将补间组合在一起。它具有出色的性能并且易于使用。

    看看这里的性能测试

    http://blog.greensock.com/tweening-speed-test/

    乔什

    【讨论】:

      【解决方案4】:

      问题可能来自于计时器并不真正可靠的事实,因为它们不像我们认为的那样独立于 fps。当帧率下降时,由于某种原因,计时器的调用频率也会降低。这与 C、C++ 或其他 OOP 语言中的行为完全不同,因此很多人都掉入了这个陷阱。

      为避免这种情况,请尝试将 ENTER_FRAME 事件用作主游戏循环,并在该循环内评估时间以了解您是否需要对游戏逻辑进行一次或多次更新。 这将使您的代码完全独立于 fps。 您可以使用 flash.utils.getTimer 调用来获取启动后的时间。

      我在我的网站上写了一篇关于此的帖子: http://fabricebacquart.info/wordpress/?p=9

      【讨论】:

        猜你喜欢
        • 2013-04-23
        • 1970-01-01
        • 2015-06-30
        • 1970-01-01
        • 1970-01-01
        • 2011-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多