AS3:使用环绕(图形)向左移动许多相同的形状 - 我的尝试太慢了

时间:2012-09-05 21:49:10

标签: performance actionscript-3 graph air scroll

我在Flash Pro(AS3)中开发一个简单的教育应用程序时处于尴尬的停滞状态。

我试图制作一个简单的图表,显示某些事情发生时的垂直峰值(以及闲置时的平线)。该图是实时的,因为它从程序开始并且每隔一帧更新一次。该行从" Graph"的一端开始。反对并继续直到它到达另一侧(比如说超过60秒)。到达结束后,整个图形开始向左滚动,为更多数据腾出空间,最后添加数据。它旨在永久保持滚动和记录数据(直到用户停止/暂停)。

以下是一个例子:http://img442.imageshack.us/img442/4784/runninggraph.png

实施此类图表的最佳方式是什么? 一个简单的滚动图表,由基线和刻度组成。

不应该这么难......或者我想。我尝试了4种不同的方式(其中两种证明100%工作但速度太慢)。那里肯定有更好的东西!也许矩阵变换+ bitmapFill?


问题的结束;简化代码如下。


public class Graph extends Sprite
{
    include "constants.as";
    const pixelFactor:Number = (1 / GRAPH_TIMESCALE) * 1050; //60,000ms
    const amountToMove:Number = POLLING_RATE * pixelFactor, inverseTS:Number = 1.0/GRAPH_TIMESCALE;
    var graphHolder:GraphHolder; // A simple (large) sprite with many Graph instances
    var baseLine:Shape = new Shape(), stretched:Boolean = false;
    var apArray:Array = [], apRecycle:Vector.<Shape> = new <Shape>[];
    var apLength:int = 0, firstPeak:Shape;
    //var numberIndicator:TextField = new TextField();

    public function Graph(graphHolder:GraphHolder) 
    {
        //Set variables
        this.graphHolder = graphHolder; 
        mouseEnabled = false; mouseChildren = false;

        addChild(baseLine);
        with (baseLine)
        {
            x = 55; y = 10; // the base-line. Starts 55 pixels out so labels can be added
            graphics.lineStyle(2,0x000000); // Thickness 2, color black
            graphics.lineTo(1050,0);
            scaleX = 0.0005; // base-line starts as a little dot, expands with time
        }
    }

    // When not scrolling, draws idle baseline. When scrolling, drags peaks backwards.
    function drawIdle(timeElapsed:int):void
    {
        if (timeElapsed <= GRAPH_TIMESCALE) baseLine.scaleX = inverseTS * timeElapsed;
        else 
        {
            //if (graphNo < 1) graphHolder.scrollAxis(pixelFactor);
            if (!stretched) { baseLine.scaleX = 1.001; stretched = true; }

            // scroll all peaks
            if (apLength)
            {
                for (var i:int = 0; i < apLength; i++)
                {
                    apArray[i].x -= amountToMove;
                }
                firstPeak = apArray[0];
                if (firstPeak.x < 55) 
                { 
                    firstPeak.visible = false;
                    apRecycle[apRecycle.length] = firstPeak; // peaks are recycled instead of removed
                    apArray.shift(); // apArray is a linked-list Array; less shift cost

                    --apLength;
                }
            }
        }
        //lbd.copyPixels(graphHolder.baseCache,lbr,new Point(timeElapsed * pixelFactor,0), null, null, true);
        //line.graphics.lineTo(timeElapsed * pixelFactor,10);
    }
    function drawAP(timeElapsed:int):void
    {
        // Check for a peak in the recycle array
        if (apRecycle.length)
        {
            firstPeak = apRecycle.pop();
            firstPeak.x = ( (timeElapsed > GRAPH_TIMESCALE) ? 1050 : (timeElapsed * pixelFactor) ) + 54; //55-1
            firstPeak.visible = true;
            apArray[apArray.length] = firstPeak;
        }
        else
        {
            // Line drawn from baseline up 7 pixels.
            var peakShape:Shape = new Shape(); 
            //peakShape.cacheAsBitmap = true;
            addChild(peakShape);
            with (peakShape)
            {
                //cacheAsBitmap = true;
                graphics.lineStyle(2, 0x000000);
                graphics.moveTo(1, 10);
                graphics.lineTo(1, 3);
                x = ( (timeElapsed > GRAPH_TIMESCALE) ? 1050 : (timeElapsed * pixelFactor) ) + 54; //55-1
                apArray[apArray.length] = peakShape;
            }
        }
        ++apLength;
    }

    /* Reset baseline as tiny dot, remove peaks, reset peak array. 
       All peaks in recycle remain to draw from during next cycle. */
    function resetGraph():void
    {
        baseLine.scaleX = 0.0005; stretched = false;
        for each (var peak:Shape in apArray) removeChild(peak);
        apArray.length = 0; apLength = 0;
    }
}

因此,基本上,图表上应该有标记,表示&#34;动作&#34;那是实时发生的。图形在到达屏幕末尾(N秒)后开始向左滚动。换句话说,它在到达指定空间的边缘后无限期地滚动。以下是我试图实现这三种方法 - 其中没有一种是快速的,其中一些还有其他问题。

  1. 在图表上的相应时间点创建并绘制新的线段 - 基线线段(微小水平线)和刻度线(垂直线)。图表仅上升到N秒,但仍然会绘制N + x时刻的所有刻度线,就像图表永远向右移动一样。然后我使用一个遮罩作为图形的矩形区域,并且一旦达到N秒,就会随着时间的推移将图形向左移动。

    • 这开始很慢,但几分钟后导致令人难以置信的减速,因为Flash显然仍然在屏幕左侧处理整个图表。
    • 直接将图线段绘制到图形对象是不好的,因为每个线段显然都是一个对象,并且这些无数对象中的每一个在绘制后都没有显式(或隐式)引用。 (&#34;看不见,心不在焉&#34; ==慢与错。)
  2. 创建一个包含刻度线(小垂直线)和基线(微小水平线)形状的bitmapdata对象,并将每个帧copypixel下一个位图(线段)放到图形对象上(现在也是一个位图)。 ScrollRect向左滚动。简单,非常类似#1,除了使用bitmapData和copyPixel而不是显示对象。

    • 这是个坏消息,因为在2048像素之后,不能再向位图绘制数据。
    • 缩放应用程序会导致所有位图显得粗糙和锯齿,并且会引入间隙(尽管标记每个位图&#34;平滑&#34;),可能是因为包含图形的线段数量很多这些都是完全不同的,并且在其他像素边界附近绘制。
    • GPU无法加速Scrollrect(应用程序需要在移动设备上运行)
    • 维护并发位图,将一个位图复制并粘贴到另一个上以刷新图形(&#34; blitting&#34;?)是单调乏味的,但与矢量技术不同,它在子像素级别也非常不准确。此外,从头开始重新计算所有峰值或在背景中创建,销毁和合并多个宽度-2048位图所需的计算从长远来看可能会更加昂贵。
  3. 有一个形状,一条水平线(基线),&#34;放大&#34;填写图表,然后停止(N秒)。因此,它是&#34;基线&#34;因为它似乎随着时间在图表上伸展,然后在N秒之后什么都不做。然后,基线顶部的每个刻度线都是一个Shape对象,并在基线顶部绘制。然后foreach循环改变所有刻度的x坐标,直到它们到达图的左边,此时它们变得不可见并添加到循环向量中,如果它包含任何内容,将用于任何后续峰值。右(在N处绘制)。

    • 仍然很慢,但比#1快得多,并且在图表开始滚动时保持恒定的速度。
    • 我无法弄清楚如何加快这个过程。我已尝试缓存背景和图形,甚至顶部的行作为位图(甚至尝试使用cacheasbitmapmatrix),但似乎普通矢量渲染在调试播放器上速度最快。
  4. 吐温。实际上,还没有使用补间实现图形本身,但到目前为止,用Tweens替换我的enterFrame移动处理程序(每帧手动更改对象的坐标)使得所有动画都变得更慢,更慢。去搞清楚。所以我认为它不会对图表产生帮助......我也不能使用TweenLite(许可证)。


  5. #3,我认为我走在正确的轨道上,但我担心我对AVM渲染的细节知之甚少。峰值应该使用位图而不是形状吗?我是否应该使用与#3##3对象相同的技术(即将峰值添加到一个大的移动&#34; tickSprite&#34;但仍然&#34;回收&#34;当他们离开屏幕时的峰值)?在这种情况下,尽管屏幕外没有刻度,但包含的Sprite将继续被推到屏幕左侧。如果它在屏幕左侧完全为空,它是否会线性占用资源?

    怀疑是#3中的速度罪魁祸首: - 大量移动的形状和移动它们所需的for循环。 - 矢量,位图和背景的特征。例如,Graph对象本身只是一个背景形状(缓存为位图)的简单容器,它是图形后面的alpha 0.75灰色矩形,一组标签和轴(也缓存为位图),以及图形形状(一个缩放&#34; line&#34;基线形状和众多&#34; tick&#34;存储在矢量中的形状)... - 用户可以拖动图表后面的法向矢量对象,这可能会将图形的子项缓存为位图,从而消除任何性能优势,因为它们必须在alpha 0.75背景形状后面的所有内容中进行运动。

1 个答案:

答案 0 :(得分:0)

选项1是唯一可行的方式。

您需要做的是将数据保存在某个位置(数组/向量),然后在屏幕外显示行/标记。如果用户想要向左滚动,则使用您保存的数据重新创建刻度(滚动时向右溢出的内容相同)。

最终虽然理论上会耗尽内存,但您可能需要实现本地存储技术,例如带有某种种子(日期时间?)的本地共享对象来存储数据,因此它并不总是在内存中。

修改

由于您不需要向左滚动,因此非常简单。 只需将每个'滴答'作为自己的显示对象。 (可以是Shape或Sprite),然后在滚动时检查单个刻度是否在屏外,如果是 - 处理它。

示例图表类:

public class Graph extends Sprite

    private var container:Sprite;

    public function Graph():void {
        //create your baseline

        //create the container
        container = new Sprite();
        addChild(container);

        this.addEventListener(Event.ENTER_FRAME, drawIdle);
    }

    private function drawIdle(e:Event){
        //move your baseline?

        //scroll container
        container.x += scrollAmount;


        //iterate through all the ticks
        var i:int = container.numChildren;
        var tick:Shape;

        //iterate through the container children backwards (has to be backwards since your potentially removing items)
        while(i--){
            tick = container.getChildAt(i);
            //check if the tick is off screen to the left
            if(container.x + tick.x < 0){
                //if so, remove it so it can be garbage collected
                container.removeChild(tick);
            }
    }

    public function drawAp(timeElapsed):void {
        //create your tick
        var tick:Shape = new Shape(); 

        //draw your tick
        tick.graphics.beginFill(0);
        tick.graphics.drawRect(0,0,tickWidth,tickHeight);
        tick.graphics.endFill();

        //position your tick
        tick.x = whatever;
        tick.y = whatever;

        //add it to your tick container
        container.addChild(tick);
    }

}