簡體   English   中英

AS3計時器與ENTER_FRAME性能

[英]AS3 Timers vs. ENTER_FRAME performance

我正在構建一個總是有一些移動的游戲,所以我使用了很多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 ......但是,我仍然可以做出反應...我可以更改/驗證/鉗制值,拋出隨意的參數和范圍錯誤,調度事件等......如果你寫一個類,你自己負責維護其狀態的完整性及其對行為的影響......

不要暴露你的類的內部工作...你可能需要更改它們,破壞相關的代碼......還有:子類化被嚴重過度... :)

這就是為什么... [/ edit]

格爾茨

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM