Animation playback misses last frame.

Hi,

I have a sprite listener attached to my sprite sheet so I change animation on the “ended” phase of animation playback but this causes my animation playback to miss the last frame of my animation. Here is my “sprite” event handler:

local function bodySpriteListener( event ) if ( event.phase == "ended" ) then local thisSprite = event.target --"event.target" references the sprite thisSprite:setSequence("idle") thisSprite:play() end end

and my animation sequence:

 local animationSequenceData = { { name = "idle", frames = { 1, 2, 3 }, time = 300, loopCount = 0 }, { name = "chew", frames = { 4, 5, 6, 7, 8 }, time = 500, loopCount = 1 }, }

What am I doing wrong?

Thanks.

My guess would be that the ended phase occurs as soon as the last frame is displayed.  Since you then set a different sequence and play it immediately, that last frame is never shown.

Try putting your code where you setSequence to idle and then play it inside a timer.performWithDelay, where you set the delay based on how long you want that last frame to show.

  • Andrew

Thanks Andew for the reply.

But isn’t “ended” phase of sprite event should be called when the animation playbed is indeed, ended? What if I understood correctly, is when my defined animation is played back completely which means going through all the frames and then, fire up that event saying that “hey guys, I finished playing back that animation, you wanna do somethin’ fancy?”.

If that’s what intended by Corona designers, there might be a bug there, if not, I would truly appreciate if someone would explain this to me.

BTW, I prefer not to use timer.performWithDelay because each time you change time of an animation, you have to remember there is also a timer.performWithDelay attached to it somewhere and go and calculate how much time that it needs as well and update it accordingly.

  • Aidin.

Hi Aidin,

You may well be right.  I actually haven’t used the sprite API’s myself, but it was a guess as to what might be causing the behavior you’re seeing.  I agree that it would make more sense if the ended phase didn’t fire until after the last frame is shown and the length of the frame has passed.

As an alternative, I wonder if you could just repeat the last frame a second time?  That way you wouldn’t have to use timer.performWithDelay, and the frame would still show (the first time).

  • Andrew

Thanks man. I actually used that to get it working but I rather find a solution for this.

to be honest, i have no idea why your last frame is skipped, so all i can offer is the way i write my sequences, what is a little bit different to yours and just works when your sequences are in order (like 1234 and so on), what is the case at your animation.
but i seriously doubt that this will change anything, as your code should have been working already and i assume the “mistake” is somewhere else.

[lua]

local animationSequenceData = {

   { name = “idle”, start = 1, count=3, time = 300, loopCount = 0 },

   { name = “chew”, start=4, count=5, time = 500, loopCount = 1 },

}

[/lua]

another workaround would be the following.
but actually just adding that extra frame to your sequence still would be a nicer less complicared workaround

[lua]

local function bodySpriteListener( event )
 

  if ( event.phase == “loop” and event.sprite.sequence ~= “idle” ) then

    local thisSprite = event.target --“event.target” references the sprite
 

    thisSprite:setSequence(“idle”)

    thisSprite:play()
 

  end

end
[/lua]

[lua]

local animationSequenceData = {

  { name = “idle”, frames = { 1, 2, 3 }, time = 300, loopCount = 0 },

  { name = “chew”, frames = { 4, 5, 6, 7, 8 }, time = 500, loopCount = 2 },

}
[/lua]

Thanks man for your replies but I’m more interested to know how to fix this, rather than hacking it because adding extra frame will need to manually calculate the time again for one frame less and it’s not beautiful.

*bump*

I believe if you change your original listener to the following, the last frame will play.

[lua]

local function bodySpriteListener( event )

    if ( event.phase == “ended” ) then  
        local thisSprite = event.target     --“event.target” references the sprite
        timer.performWithDelay( 50, function() thisSprite:setSequence(“idle”) thisSprite:play() end )

    end

end

[/lua]

It should show for >50ms with the timer set as above, but only 1ms is needed. The issue in general is that you are changing the animation frame during the exact same timer slice that it would *start* being displayed. When the event handler (and all immediately executed code/called code) completes, the openGL buffers are flushed to the screen, and whatever the last frame for the anim you told it to display will be rendered (the idle frame, NOT the last frame of the prior anim, which is now rendered into the openGL buffer (but not transferred to the screen yet).

The 50 ms delay (even 1 ms) will fire off on the next event slice, which will be after the current openGL buffer renders.

My guess would be that the ended phase occurs as soon as the last frame is displayed.  Since you then set a different sequence and play it immediately, that last frame is never shown.

Try putting your code where you setSequence to idle and then play it inside a timer.performWithDelay, where you set the delay based on how long you want that last frame to show.

  • Andrew

Thanks Andew for the reply.

But isn’t “ended” phase of sprite event should be called when the animation playbed is indeed, ended? What if I understood correctly, is when my defined animation is played back completely which means going through all the frames and then, fire up that event saying that “hey guys, I finished playing back that animation, you wanna do somethin’ fancy?”.

If that’s what intended by Corona designers, there might be a bug there, if not, I would truly appreciate if someone would explain this to me.

BTW, I prefer not to use timer.performWithDelay because each time you change time of an animation, you have to remember there is also a timer.performWithDelay attached to it somewhere and go and calculate how much time that it needs as well and update it accordingly.

  • Aidin.

Hi Aidin,

You may well be right.  I actually haven’t used the sprite API’s myself, but it was a guess as to what might be causing the behavior you’re seeing.  I agree that it would make more sense if the ended phase didn’t fire until after the last frame is shown and the length of the frame has passed.

As an alternative, I wonder if you could just repeat the last frame a second time?  That way you wouldn’t have to use timer.performWithDelay, and the frame would still show (the first time).

  • Andrew

Thanks man. I actually used that to get it working but I rather find a solution for this.

to be honest, i have no idea why your last frame is skipped, so all i can offer is the way i write my sequences, what is a little bit different to yours and just works when your sequences are in order (like 1234 and so on), what is the case at your animation.
but i seriously doubt that this will change anything, as your code should have been working already and i assume the “mistake” is somewhere else.

[lua]

local animationSequenceData = {

   { name = “idle”, start = 1, count=3, time = 300, loopCount = 0 },

   { name = “chew”, start=4, count=5, time = 500, loopCount = 1 },

}

[/lua]

another workaround would be the following.
but actually just adding that extra frame to your sequence still would be a nicer less complicared workaround

[lua]

local function bodySpriteListener( event )
 

  if ( event.phase == “loop” and event.sprite.sequence ~= “idle” ) then

    local thisSprite = event.target --“event.target” references the sprite
 

    thisSprite:setSequence(“idle”)

    thisSprite:play()
 

  end

end
[/lua]

[lua]

local animationSequenceData = {

  { name = “idle”, frames = { 1, 2, 3 }, time = 300, loopCount = 0 },

  { name = “chew”, frames = { 4, 5, 6, 7, 8 }, time = 500, loopCount = 2 },

}
[/lua]

Thanks man for your replies but I’m more interested to know how to fix this, rather than hacking it because adding extra frame will need to manually calculate the time again for one frame less and it’s not beautiful.

*bump*

I believe if you change your original listener to the following, the last frame will play.

[lua]

local function bodySpriteListener( event )

    if ( event.phase == “ended” ) then  
        local thisSprite = event.target     --“event.target” references the sprite
        timer.performWithDelay( 50, function() thisSprite:setSequence(“idle”) thisSprite:play() end )

    end

end

[/lua]

It should show for >50ms with the timer set as above, but only 1ms is needed. The issue in general is that you are changing the animation frame during the exact same timer slice that it would *start* being displayed. When the event handler (and all immediately executed code/called code) completes, the openGL buffers are flushed to the screen, and whatever the last frame for the anim you told it to display will be rendered (the idle frame, NOT the last frame of the prior anim, which is now rendered into the openGL buffer (but not transferred to the screen yet).

The 50 ms delay (even 1 ms) will fire off on the next event slice, which will be after the current openGL buffer renders.

I am having the same problem, and I’ve solved it the same way, by not running my “end” script until I delay first.

However, this is simply bad — the playing should not be ended until the ‘time’ expires on the last frame!