Fast Frame Swap

SocksSocks London, UK.Member Posts: 12,822
edited June 2014 in Working with GS (Mac)
GameSalad's animation behaviour is a weird thing, not just because of its arbitrary frame rates (something that's hopefully been addressed in the nightly builds), but also its lack of control and features, especially given that it lives inside an application predominantly designed for game making.

So, with that in mind, I present . . . . . drum roll . . . . a faster frame swap, a replacement for the animation behaviour.

Advantages this has over the animation behaviour are . . .

. . . . .

1) Where the animation behaviour pauses/glitches when first played, this method plays straight away.

2) It is possible to get animations to speed up and slow down.

3) It can go (a lot) faster than the standard animation behaviour.

4) The frame rates are accurate, unlike the current animation behaviour.

5) It can play backwards as well as forwards.

6) Frame based sound sync stays locked in place regardless of frame rate.

7) When you drag a sequence into the animation behaviour the order often gets screwed up, so you have to manually reorder the frames, no big deal on a 21 frame walk cycle, but a nightmare when you are dealing with multiple long frame sequences, this method avoids all that frame wrangling.

. . . . .

It is also super simple to implement and is completely table, attribute and rule free.
Its basically works like this:

Constrain image to floor( game.Time *30)%21


30 is the frame rate you want.

21 is the length of your image sequence (assuming it loops)


. . . . .

Other notes:

The above example assumes an image sequence starting on frame '0' and having no file name, just the numbers (so basically an image sequence going 0, 1, 2, 3, 4, 5 . . . etc [see attached file's image sequence for an example of this]). But If your frame doesn't start on 0, but instead starts on 1 then simply add 'plus 1' like this:

Constrain image to (floor( game.Time *30)%21)+1

If your sequence isn't just numerals, but instead has an actual name (so basically an image sequence going Filename_0, Filename_1, Filename_2, Filename_3, Filename_4, Filename_5 . . . etc) then simply add it in like this:

Constrain image to "Filename_"..floor( game.Time *30)%21

If you want your animation to run backwards, simply make the game.time multiplier negative, like this:

Constrain image to floor( game.Time *-30)%21

Feel free to swap game.time for self.time - and even introduce offsets if you want or need your sequence to start on a particular frame.

For example, if you had an explosion, you'd want to use self.time rather than game.time - as you'd always want the explosion to start on the first frame (usually when the actor containing it is spawned), but if you had a spinning coin it wouldn't really matter on which frame it started.


. . . . .

Example files:

Frame rates: https://www.mediafire.com/?5y29l4g27u7gmr1
Speed up: https://www.mediafire.com/?dcj81y2qb679lyv
Backwards: https://www.mediafire.com/?1k91133bg4v8mup


The first example file ('frame rates') shows GameSalad's standard animation behaviour going as fast as it can (it's set on 30fps) alongside this new method - and you can really see how much faster this allows you to play animation sequences.


. . . . .
«13

Comments

  • gyroscopegyroscope I am here.Member, Sous Chef, PRO Posts: 6,598
    edited January 2014

    Looking terrific, @Socks, I look forward to seeing your example files, as well as putting your concept into practice (One day... a particular project I have in mind won't see the light of day for quite some time... )

    Anyhow, spot on! :-)

    ""You are in a maze of twisty passages, all alike." - Zork        temp domain http://spidergriffin.wix.com/alphaghostapps

  • SocksSocks London, UK.Member Posts: 12,822
    edited January 2014
    Point 6) illustrated: (Frame based sound sync stays locked in place regardless of frame rate.)

    Play the linked file, see how there is a sound as each foot hits the ground, now go into the actor (there is only one) and change the frame rate, at the moment it is 25fps . . . floor( game.Time *25)%21 . . . change it to something else, let's use 40fps as an example, so it should now read . . .

    floor( game.Time *40)%21

    . . . now play this, notice that the footstep sound (basically a click) is still synced to the animation.

    . . . . . .

    Link: https://www.mediafire.com/?iovrqqwlpdraije
  • SocksSocks London, UK.Member Posts: 12,822
    @gyroscope

    Cheers gyroscope, hopefully the idea, as simple as it is, will be useful to people.
  • wpatenwpaten Member, PRO Posts: 281
    Wow!!! Nice work man.
  • SocksSocks London, UK.Member Posts: 12,822
    @wpaten

    Cheers wpaten.
  • izamizam Member, PRO Posts: 503
    This is unbelievable! Thank you @Socks !
  • kinzuakinzua Member Posts: 554
    Really cool finding.. Awesome
  • darrelfdarrelf Member Posts: 243
    I'm continually amazed at how clever people are on this forum.
    I actually find it frustrating I'm not good enough to contribute something myself...
  • GSAnimatorGSAnimator Member Posts: 312
    @Socks Cool! I'm also using a boolean attribute or a Timer behavior along with the custom animation so that you can control it if you only need the animation to play once.
    for example: attack, dead .. etc.

    But maybe you guys have better idea ;)

    Zhong.
  • BBEnkBBEnk Member Posts: 1,764
    I've done something similar to this but this is cleaner, I like it. Good Job!..
  • Braydon_SFXBraydon_SFX Member, Sous Chef, PRO, Bowlboy Sidekick Posts: 9,271
    Excellent as always, @Socks! Well done and thanks for sharing!
  • RThurmanRThurman Member, Sous Chef, PRO Posts: 2,879
    @Socks -- Great idea! Superfine!
  • SocksSocks London, UK.Member Posts: 12,822
    edited January 2014
    @GSAnimator.com
    @Socks Cool! I'm also using a boolean attribute or a Timer behavior along with the custom animation so that you can control it if you only need the animation to play once.
    for example: attack, dead .. etc.

    But maybe you guys have better idea ;)

    Zhong.
    For something that only plays through once I'd usually just do something like this - a rule that is looking for your last frame before carrying out its rules - and those rules are empty (i.e. do nothing) - and in the otherwise section place the stuff you want to happen before you get to the last frame.

    So basically when the frame number is (in this example) 20 do nothing (i.e. stop) . . . but until we reach frame 20 then play through the image sequence.

    You can use any frame number you like as the stop point.

    image
  • SocksSocks London, UK.Member Posts: 12,822
  • wpatenwpaten Member, PRO Posts: 281
    Something that Corona has that GS doesn't is an easy piece of code called onComplete. Meaning, when, whatever it is I am doing is done, Go do this next thing I tell you to do. It's especially handy when playing animations.

    I remember pulling my hair out in the beginning with GS, looking for something like that without having to do everything with timers.

    This is absolutely awesome. Thanks again Socks.
  • SocksSocks London, UK.Member Posts: 12,822
    @wpaten
    Something that Corona has that GS doesn't is an easy piece of code called onComplete. Meaning, when, whatever it is I am doing is done, Go do this next thing I tell you to do. It's especially handy when playing animations.
    Yeah, that's kind what the frame check does, when we reach the last frame then go do this other thing.

    For example if you were to put a jump animation into the (currently empty) rules section in the above screen shot, the actor would run until it got to frame 20 then it would jump.
  • SocksSocks London, UK.Member Posts: 12,822
    edited January 2014
    Just thought of one other advantage of this method (in the OP) over the standard animation behaviour, at the moment the animation behaviour runs at pretty arbitrary rates . .

    example: 29, 28, 27, 26, 25, 24, 23, 22 and 21fps all run at 20.1fps . . . 20fps runs at 16.5fps . . . 16, 17, 18 and 19 fps all run at 15.1fps . . . etc etc.

    From what I hear these shaky frame rates are being dealt with, the new more accurate frame rates have made it into the Nightly Builds, and will, in time, come to the standard release.

    So what that means is that when the frame rates are corrected and you open up a project you are working on that uses the animation behaviour, your animations will all speed up, some by as much as 9fps, this won't be an issue for most people, but if you have any time crucial animations (lip sync, animations that need to complete at reasonably precise points . . . and so on) then a sudden 20-30% speed increase might be an issue.

    As the method in the OP is locked to the game.time (or self.time) it won't suffer from this issue.
  • quantumsheepquantumsheep Member Posts: 8,188
    This looks like fun, @socks :D

    I'll give it a whirl on a future game for sure, but one question!

    How does this work if you have a looping animation?

    Cheers!

    QS =D

    Dr. Sam Beckett never returned home...
    Twitter: https://twitter.com/Quantum_Sheep
    Web: https://quantumsheep.itch.io

  • SocksSocks London, UK.Member Posts: 12,822
    edited January 2014
    @quantumsheep
    How does this work if you have a looping animation?

    Simple . . . it already loops ! In the following example it loops after 21 frames:

    Constrain image to floor( game.Time *30)%21


  • quantumsheepquantumsheep Member Posts: 8,188
    @quantumsheep
    How does this work if you have a looping animation?

    Simple . . . it already loops ! In the following example it loops after 21 frames:

    Constrain image to floor( game.Time *30)%21


    \o/

    Dr. Sam Beckett never returned home...
    Twitter: https://twitter.com/Quantum_Sheep
    Web: https://quantumsheep.itch.io

  • SocksSocks London, UK.Member Posts: 12,822
    edited January 2014
    @quantumsheep
    @quantumsheep
    How does this work if you have a looping animation?

    Simple . . . it already loops ! In the following example it loops after 21 frames:

    Constrain image to floor( game.Time *30)%21


    \o/


    Download a couple of the demo files in the OP to see what I mean ! :)>-
  • JGary321JGary321 Member Posts: 1,246
    @Socks I love you how come up with clever ideas like this.

    As far as performance goes, do you think this will have much of an impact using all these constrains? The title I am working on will have around 10-20 animated actors on screen at once. I was already going to use tables/image names for the animations for better control, but I am hesitant on using the contrains. Obviously I will test it before finalizing, just didn't know if you had any preliminary data in regards to performance with a higher number of actors.

    Thanks.
  • CodeMonkeyCodeMonkey Head Chef, Member, PRO Posts: 1,803
    edited January 2014
    I do prefer using a counter attribute with a Timer behavior with 1/FPS for the every timer, or the mod+Rule condition trigger to increment the counter, because you can control the start and end of the animation instead of relying on game.Time which the animation may start somewhere in the middle of the frame you want. And passing in a text attribute for the beginning part of the image name allows one to have control of which animation to do. *cough* tables *cough*
    Use tables that have a list of animations with the following properties
    baseName , FPS, maxFrames
    and if you really want to get fancy: startFrame, endFrame instead of maxFrames

    and even fancier, a column that says the animation loops or not.
  • SocksSocks London, UK.Member Posts: 12,822
    edited January 2014
    @CodeMonkey
    I do prefer using a counter attribute with a Timer behavior with 1/FPS for the every timer, or the mod+Rule condition trigger to increment the counter, because you can control the start and end of the animation instead of relying on game.Time which the animation may start somewhere in the middle of the frame you want.
    I address this in my original post . . .

    Feel free to swap game.time for self.time - and even introduce offsets if you want or need your sequence to start on a particular frame.

    For example, if you had an explosion, you'd want to use self.time rather than game.time - as you'd always want the explosion to start on the first frame (usually when the actor containing it is spawned), but if you had a spinning coin it wouldn't really matter on which frame it started.
    Defining which frame the animation starts on is simple enough using an offset:
    (floor(( self.Time *30)+19)%25)

    This runs at 30 fps, it starts on frame 19 and the sequence/loop is 25 frames long.

    . . . . . . . .
    And passing in a text attribute for the beginning part of the image name allows one to have control of which animation to do. *cough* tables *cough*

    I also addressed this in my original post . . .

    If your sequence isn't just numerals, but instead has an actual name (so basically an image sequence going Filename_0, Filename_1, Filename_2, Filename_3, Filename_4, Filename_5 . . . etc) then simply add it in like this:

    Constrain image to "Filename_"..floor( game.Time *30)%21

    . . . . . . . .

    Use tables that have a list of animations with the following properties
    baseName , FPS, maxFrames
    and if you really want to get fancy: startFrame, endFrame instead of maxFrames

    and even fancier, a column that says the animation loops or not.

    I think a lot of what is attractive about using floor( game.Time *30)%21 is that it eschews all these tables and attributes and timers and rules - and is a simple one line animation driver that can do petty much everything you'd want to do with an animation and is trivially simple to set up and modify - and having no attributes or tables (etc) you can freely cut and paste it from one actor / project to another.
  • SocksSocks London, UK.Member Posts: 12,822
    edited January 2014
    As far as performance goes, do you think this will have much of an impact using all these constrains? The title I am working on will have around 10-20 animated actors on screen at once. I was already going to use tables/image names for the animations for better control, but I am hesitant on using the contrains. Obviously I will test it before finalizing, just didn't know if you had any preliminary data in regards to performance with a higher number of actors.

    Thanks.
    I can't see 20 actors being an issue, but just like the animation behaviour it all depends on the content, if you have 100 actors each playing a 512 x 512 pixel 500 frame long image sequence at 60fps it will be more of a strain than if it were just 40 actors each playing a 64 x 64 pixel 20 frame long image sequence at 30fps . . . .

    The constrain behaviour has no absolute fixed amount of 'strain' it puts on the processor, using constrain behaviour to track game.time and self.time and device.clock.xxx doesn't seem to be particularly taxing, but obviously if the constrain behaviour was constraining something's x position to . . .

    40*sin( game.Time *100)+ 40*sin( 50*cos( 40*sin( game.Time *100)+ 40*cos( 50*sin( game.Time *100) *(3*sin( 50*cos( game.Time *100) *(3*cos( game.Time *200))*200))*100)+80*100) *(3*sin( 50*sin( game.Time *100) *(3*cos( game.Time *200))*200))*100)+80 . . . . etc etc

    . . . then you wouldn't really want too many of them on the screen at once as that's a lot of calculation to crunch through.

    So . . . everything in context.
  • SocksSocks London, UK.Member Posts: 12,822
    edited January 2014
    @anyonewhoisinterested!

    Weirdly, you can dump the 'floor' function and this method still works just fine, I'm not 100% sure how or why, but it works.

    Using game.time or self.time produces numbers with lots of decimal points, so my first instinct was to chop off the decimal points (using 'floor') so that you only got a whole number - which would match the whole numbers in the names of the frames in the image sequence . . .

    My thinking was that if the constrain behaviour (using game.time) produced this:

    Run_Cycle_Frame_17.6732

    And the actual frame name was:

    Run_Cycle_Frame_17

    Then you would get a white box where the image should be as there is no such frame as Run_Cycle_Frame_17.6732 . . . but GameSalad seems to ignore the decimal places and quite happily displays Run_Cycle_Frame_17.

    So, this method can be simplified even further and there is one less calculation for GS to do at run time (no need for each frame to be 'floor'ed).

    So it can simply read . . . . (game.Time*30)%21


    . . . . . . . . . . . . . . . . . . . .


    So, what would happen if you had an image that was numbered with decimal points (for example - Run_Cycle_Frame_2.567) ?

    And you asked GS to display Run_Cycle_Frame_2.567 ?

    It behaves as expected and displays Run_Cycle_Frame_2.567.

    But . . . . if you also have a file called Run_Cycle_Frame_2 in the same project . . . and you delete the image called Run_Cycle_Frame_2.567 then GameSalad will display Run_Cycle_Frame_2 !!?!??

    So, GS will display two different images without you changing the code at all.

    This is not really an issue in the real world, but interesting nonetheless (to me at least ! ;) )






  • slowcutslowcut Member, PRO Posts: 164
    @Socks if you dumb the floor functions, double check it on the device.

    I have made the experience, that my iOS devices will calculate more accurate than the GS Viewer.
    Your image constrain animation could be such a calculation which would run perfect (if simplified) in the viewer, but not in the adhoc. But I have not tried yet.

    If you do not want use the floor function to round your values, you can also use the padReal or padInt functions to get rid off the decimal points.
    I can imagine that they are less performance hungry.

    The padInt will round the value down
    - padInt(123.678,3) will return 123
    - padInt(123.456,3) will return 123

    The padReal will round values up or down
    -padReal(123.678,3,0) will return 124
    -padReal(123.456,3,0) will return 123

  • JSprojectJSproject Member Posts: 730
    @Socks Great find and very well described (as per usual). Also, I second what @slowcut wrote.
Sign In or Register to comment.