我在 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 种方法——它们都不是很快,其中一些还有其他问题。
在图表上的相应时间点创建并绘制一条新线段——基线段(微小的水平线)和刻度线(垂直)。该图仅上升到 N 秒,但仍绘制 N+x 时间的所有刻度线,就好像该图永远向右移动一样。然后,我对图形的矩形区域使用蒙版,并在图形达到 N 秒后随时间将图形向左移动。
- 开始时这很慢,但几分钟后导致令人难以置信的减速,因为 Flash 显然仍在处理屏幕左侧的整个图表。
- 将线段直接绘制到图形对象是不好的,因为每个线段显然都是一个对象,而这些无数对象中的每一个在绘制后都没有显式(或隐式)引用。(“眼不见,心不烦”==缓慢而错误。)
创建一个包含刻度线(小的垂直线)和基线(微小的水平线)形状的位图数据对象,并且每一帧都将下一个位图(线段)复制到图形对象(现在也是位图)上。ScrollRect 向左滚动。很简单,很像#1,除了使用 bitmapData 和 copyPixel 而不是显示对象。
- 这是个坏消息,因为在 2048 像素之后,无法再向位图绘制数据。
- 缩放应用程序会导致所有位图看起来粗糙且参差不齐,并且会引入间隙(尽管将每个位图标记为“平滑”),这可能是因为组成图形的线段数量众多,它们都是完全不同的并且绘制在其他图形附近像素边界。
- Scrollrect 无法通过 GPU 加速(应用需要在移动设备上运行)
- 维护并发位图,将一个复制并粘贴到另一个上以刷新图形(“blitting”?)是乏味的,但与矢量技术不同,它在亚像素级别也非常不准确。此外,从每帧从头开始重新计算所有峰值或在后台创建、销毁和合并多个宽度 2048 位图所需的计算从长远来看可能会更加昂贵。
有一个形状,一条水平线(基线),“放大”以填充图形,然后停止(N 秒)。因此,它是“基线”,因为它似乎随着时间在图表上延伸,然后在 N 秒后什么也不做。然后,基线顶部的每个刻度都是一个 Shape 对象,并绘制在基线顶部。然后,foreach 循环会更改所有刻度的 x 坐标,直到它们到达图表的左侧,此时它们将变为不可见并添加到回收向量中,如果它包含任何内容,该向量将用于来自来自的任何后续峰值右侧(绘制在 N 处)。
- 仍然很慢,但比 #1 快得多,并且在图形开始滚动时保持恒定速度。
- 我不知道如何加快这个过程。我已经尝试将背景和图形甚至顶部的线缓存为位图(甚至尝试使用 cacheasbitmapmatrix),但在调试播放器上,纯矢量渲染似乎是最快的。
吐温。实际上,还没有用补间实现图形本身,但到目前为止,用 Tweens 替换我的 enterFrame 移动处理程序(每帧手动更改对象的坐标)已经使所有动画变得非常非常慢。去搞清楚。所以我认为这对图表没有帮助......我也不能使用 TweenLite(许可)。
对于#3,我认为我走在正确的轨道上,但我担心我对 AVM 渲染的细节知之甚少。我应该使用位图而不是形状作为峰值吗?我是否应该对#3 的对象使用与#1 相同的技术(即,将峰值添加到一个大的移动“tickSprite”但当它们离开屏幕时仍然“回收”峰值)?在这种情况下,尽管屏幕外没有刻度,但包含的 Sprite 将继续被推到屏幕左侧。如果屏幕左侧完全是空的,它还会线性占用资源吗?
#3 中的可疑速度罪魁祸首: - 大量移动的 Shapes 以及移动它们所需的 for 循环。- 矢量、位图和背景的特征。例如,Graph 对象本身只是背景形状(缓存为位图)的简单容器,背景形状是图形后面的 alpha 0.75 灰色矩形、一堆标签和轴(也缓存为位图)和图形形状(一个用于基线的缩放“线”形状和存储在向量中的大量“刻度”形状)... - 用户可以将法线向量对象拖到图形后面,这可能会破坏将图形的子对象缓存为位图的任何性能优势,因为它们有在 alpha 0.75 背景形状后面的所有物体上动态渲染。