我在openfl中使用内置的NMEPReloader作为我的预加载器的基类。
我正在做的是逐帧播放动画
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并播放,但在这种情况下它会第二次断断续续。
如果我将它移动到另一个显示对象后面播放动画然后将其移动到绘制顺序的前面,它将第二次断断续续。
有没有一种方法可以播放这个动画而不会出现口吃?
答案 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
}
其次,使用单个位图和bitmapdata数组,而不是使用位图数组。虽然对于不应该有太大变化的小动画,这是一种更好的练习,可以帮助提高性能(例如,在预加载器中)。
这是一个扩展sprite的动画类的新实现。它尚未经过测试,但应该像宣传的那样工作。请注意,通常,每个对象使用一个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'资产类加载它们(因此完全放弃了预加载器,在我的拙见中看起来很奇怪)
PS:请原谅我的m和变量的前缀。 m表示成员变量,a表示参数。
答案 1 :(得分:1)
我能够通过使用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++;
}
}
}