预加载器中的 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++;
}
}
}
我使用内置的 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++;
}
}
}