2

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

我正在尝试制作一个简单的图表,显示发生某事时的垂直峰值(以及空闲时的一条平线)。该图是实时的,因为它从程序开始并每隔一帧更新一次。该线从“图形”对象的一端开始,一直持续到它到达另一侧(例如,超过 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;
    }
}

同样,本质上,图表上应该有指示实时发生的“动作”的刻度线。图表在到达屏幕末尾(N 秒)后开始向左滚动。换句话说,它在到达指定空间的边缘后会无限期地滚动。以下是我尝试实现的 3 种方法——它们都不是很快,其中一些还有其他问题。

  1. 在图表上的相应时间点创建并绘制一条新线段——基线段(微小的水平线)和刻度线(垂直)。该图仅上升到 N 秒,但仍绘制 N+x 时间的所有刻度线,就好像该图永远向右移动一样。然后,我对图形的矩形区域使用蒙版,并在图形达到 N 秒后随时间将图形向左移动。

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

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

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


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

#3 中的可疑速度罪魁祸首: - 大量移动的 Shapes 以及移动它们所需的 for 循环。- 矢量、位图和背景的特征。例如,Graph 对象本身只是背景形状(缓存为位图)的简单容器,背景形状是图形后面的 alpha 0.75 灰色矩形、一堆标签和轴(也缓存为位图)和图形形状(一个用于基线的缩放“线”形状和存储在向量中的大量“刻度”形状)... - 用户可以将法线向量对象拖到图形后面,这可能会破坏将图形的子对象缓存为位图的任何性能优势,因为它们有在 alpha 0.75 背景形状后面的所有物体上动态渲染。

4

1 回答 1

0

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

您需要做的是将数据保存在某处(数组/向量),然后在它们离开屏幕后处理您的线条/刻度。如果用户想要向左滚动,则使用您保存的数据重新创建刻度(滚动时向右溢出的内容相同)。

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

编辑:

由于您不需要向左滚动,因此很容易。只需将您的每个“刻度”设为自己的显示对象即可。(无论是形状还是精灵),然后在滚动时检查单个刻度是否在屏幕外,如果是 - 将其丢弃。

示例图类:

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);
    }

}
于 2012-09-05T23:51:12.470 回答