预加载器中的 OpenFL 动画总是第一次卡顿

OpenFL animation in preloader always stutters first time

我使用内置的 NMEPReloader 作为 openfl 预加载器的基础 class。

我正在逐帧播放动画

class AttractAnimation extends Sprite
{
var currentFrame:Int;
var previousFrame:Int;
var bitmapFrames:Array<Bitmap>;
var loadScreenTimer:Timer;

public function new()
{
    super();
    currentFrame = 0;
    previousFrame = -1;
    bitmapFrames = new Array<Bitmap>();
}

public function assignBitmapData(bitmapData : Array<BitmapData>){
    for(bmap in bitmapData){
        var frame = new Bitmap( null, flash.display.PixelSnapping.AUTO, true );
        frame.bitmapData = bmap;
        pushFramesAsBitmap(frame);
    }
}
public function pushFramesAsBitmap(frameAsBitmap:Bitmap) {
    bitmapFrames.push(frameAsBitmap);

    frameAsBitmap.visible = false;
    addChild(frameAsBitmap);
}

public function startAnimation(fps:Int) {
    var milliseconds:Float = 1000 / fps;
    loadScreenTimer = new Timer(milliseconds, 10);
    loadScreenTimer.addEventListener(TimerEvent.TIMER, updateAnimation);
    loadScreenTimer.start();
}

    private function updateAnimation(e : TimerEvent):Void {
        if (currentFrame > bitmapFrames.length -1) { currentFrame = 0; }
        if (previousFrame != -1) {
            bitmapFrames[previousFrame].visible = bitmapFrames[previousFrame].__combinedVisible = false;
        }
        bitmapFrames[currentFrame].visible = bitmapFrames[currentFrame].__combinedVisible = true;
        previousFrame = currentFrame;
        currentFrame++;
    }

}

当我调用 playAnimation 时,我看到它卡顿得非常糟糕,下次我播放动画时它会流畅地播放。

我试过将 alpha 设置为 0 播放动画,然后将其设置回 1 并播放,但在这种情况下第二次播放时会卡顿。

同样,如果我将它移到另一个显示对象后面播放动画,然后将它移到绘制顺序的前面,它会第二次卡顿。

有什么方法可以播放这个动画而不卡顿吗?

由于您的实现非常简单并且不依赖于太多外部 API,因此计时器可能是一个原因。请记住,预加载器实际上会优先加载所有内容,因此延迟是可以预料的,除非您采取措施隐藏或阻止它。由于滞后,定时器事件很可能会连续多次触发。这会导致视觉错误和卡顿。

首先,首先尝试使用 enterFrame 事件侦听器而不是计时器,这在大多数(如果不是所有)情况下都是一个糟糕的设计选择。

//Using this event listener
addEventListener(Event.ENTER_FRAME, OnUpdateAnimation);

//which would call this function every frame
function OnUpdateAnimation(e:Event):Void
{
    //calculate delay since last frame
    //play next frame
}

其次,不使用位图数组,而是使用单个位图和位图数据数组。虽然对于不应有太大变化的小动画来说,这是一种更好的做法并且可以帮助提高性能(例如,在预加载器中)。

这是动画 class 扩展精灵的新实现。它尚未经过测试,但应该像宣传的那样工作。请注意,通常,为每个对象使用一个 EnterFrame 侦听器并不是一个好的做法;你通常会有一个主更新函数,它会遍历所有可更新的对象。不过是这种情况,就够了。

class Animation extends Sprite 
{

    private var mBitmapDataList:Array<BitmapData>; //a list of bitmapdata
    private var mBitmap:Bitmap; //the bitmap that will contain all bitmapdata
    private var mCurrentIndex:Int; //the currently displayed bitmapdata
    private var mFrameLength:Float; //the duration of a frame in miliseconds
    private var mDeltaTime:Float = 0; //the current delta time for time-based updates

    private var mTime:Float; //a variable containing the current time since the last frame change
    public function new(aFrameLength:Float) 
    {
        super();
        mFrameLength = aFrameLength;
        mBitmapDataList = new Array<BitmapData>();
        mBitmap = new Bitmap();
        addChild(mBitmap); //add the bitmap to the scene 
                        //(it will display nothing as no bitmapdata is set)
        mCurrentIndex = 0;
    }

    //add a bitmapdata instead of a complete bitmap object
    public function addBitmapData(aBD:BitmapData):Void
    {
        mBitmapDataList.push(aBD);
    }

    public function play():Void
    {
        addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true);
    }

    private function onEnterFrame(e:Event):Void 
    {
        var currentTime = Lib.getTimer(); //get the current time
        var elapsed:Float = (currentTime - mDeltaTime); //calculate the time since the last update
        mDeltaTime = currentTime;

        mTime += elapsed; //add the elapeed time to mTime
        if (mTime >= mFrameLength) //if it is time to change frame...
        {
            mTime = 0;
            mCurrentIndex++;
            if (mCurrentIndex == mBitmapDataList.length) mCurrentIndex = 0;
            mBitmap.bitmapData = mBitmapDataList[mCurrentIndex]; //change it
        }
    }
}

这样,即使在卡顿的极端情况下,也只会跳过第一帧。另请注意,在某些目标上,您将无法显示图像,因为它们尚未加载。另一种解决方案是不嵌入资产 <assets path="assets" embed="false"/> 并使用 OpenFL 的资产 class 加载它们(因此完全放弃预加载器,在我看来这是一个花哨的东西)

PS:请原谅我在变量上加了 m 和 a 前缀。 m 用于成员变量,a 用于参数。

我可以通过使用 spritesheet 而不是单个图像来停止卡顿。

我更改了名称,因为我们添加了第二个动画。你可以看到我现在使用 Tilesheet 而不是 Bitmap 数组。

我再次更新了以下答案以包含建议的 onEnterFrame 更改。使用 spritesheet 消除了卡顿现象,但现在使用 enterframe 事件使动画更加流畅。

class AnimatedSprite extends Sprite
{
public var currentFrame:Int;

private var loadScreenTimer:Timer;
private var tiles: Array<Int>;
private var tilesheet : Tilesheet;

private var fps : Int;
private var mDeltaTime:Float = 0; //the current delta time for time-based updates
private var mTime:Float; //a variable containing the current time since the last frame change


public function new(framesPerSecond : Int, looping : Bool = false)
{
    super();
    currentFrame = 0;
    fps = framesPerSecond;
}

//public methods
public function assignBitmapData(bitmapData : BitmapData, tileData : Array<Rectangle>) : Void {
    tilesheet = new Tilesheet(bitmapData);
    tiles = new Array<Int>();

    for(i in 0...tileData.length){
        tiles.push(tilesheet.addTileRect(tileData[i]));
    }
    mTime = 0;
}

public function playAnimation() : Void {
    trace("playAnimation");
    visible = true;
    currentFrame = 0;
    addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true);
}

//private methods
private function endAnimation(?e : TimerEvent) : Void {
    removeEventListener(Event.ENTER_FRAME, onEnterFrame);
    graphics.clear();
    trace("end animation");
    currentFrame = 0;
    mTime = 0;
}

private function onEnterFrame(e:Event):Void
{
    trace("enter frame");
    var currentTime = Lib.getTimer(); //get the current time
    var elapsed:Float = (currentTime - mDeltaTime); //calculate the time since the last update
    mDeltaTime = currentTime;

    mTime += elapsed; //add the elapeed time to mTime
    if (mTime >= 1000 / fps) //if it is time to change frame...
    {
        mTime = 0;
        graphics.clear();
        tilesheet.drawTiles(this.graphics, [0, 0, tiles[currentFrame], 1, 1]);
        trace("Draw frame : " + currentFrame);
        if (currentFrame == tiles.length-1) endAnimation();

        currentFrame++;
    }
}
}