2d-inc / Flare-Flutter

Load and get full control of your Rive files in a Flutter project using this library.
https://rive.app/
MIT License
2.55k stars 469 forks source link

Mixing animations using FlareControls stopping previous animations #139

Open stx opened 4 years ago

stx commented 4 years ago

Hi there! We have 2 animations: idle (loop) and speak. These animate different layers.

I would expect that we could use FlareControls().play('idle') initially, and then FlareControls().play('speak') each time we need to play it.

However, the moment "speak" is played, idle is stopped. This seems like odd behavior given the emphasis on mixing animations, or perhaps I'm doing something wrong.

If idle is moved to the FlareActor's animation property, this works as expected. Idle loops, speech plays as necessary.

However, the problem with this strategy comes when we introduce a third mix: idle, jump and speak.

With idle on loop on the FlareActor animation property, + play('jump') on demand + play('speak') on demand causes issues. The moment speak is played, jump is stopped.

Again, these animations are for different layers. This seems like a bug... what is the simplest way to be able to mix and fire animations in this way?

neurowave commented 4 years ago

I'm not sure about the code part, I'll let someone else from our team address that, but on the animation side: can you share the Flare file? I can take a look and make sure there aren't any competing keys in your animations that might be causing this. One way to test this is to mix the animations in Flare by hitting the play button next to each animation (in the animations list).

stx commented 4 years ago

@neurowave Can see it here: https://www.2dimensions.com/a/s-che/files/flare/selfy - idle-1 (enable looping) and speech-1.

On the Flare web side, idle-1 loop + speech-1 mixes perfect.

In Flare Flutter, .play('idle-1') -> .play('speech-1'), idle-1 will be stopped.

luigi-rosso commented 4 years ago

Hey @stx! FlareControls will play one-shot animations over each other and drop previous ones once the incoming animation is ramped up to 100% (1.0) mix. However, looping animations should always stay in the mix, so it's weird that idle-1 is getting stopped/dropped.

I'll try to set up an isolated test for this today to see if I can track down the bug.

In the meantime, you could use the lower level FlareController and get full control over the mixing by calling animation.apply in the correct order yourself. Here's a simple demo with our penguin example: https://github.com/2d-inc/Flare-Flutter/tree/stable/example/penguin_dance

Note you'd have to advance time for each animation manually too (so it gets tedious). We have plans for creating a visual state machine to control all these various animation play/mix states in the future. All directly from Flare!

luigi-rosso commented 4 years ago

I can repro the problem. One hacky fix is to simply set "idle-1" as the animation on the FlareActor, this will play before any of the FlareControls animations and keep looping.

Still looking into why the FlareControls stop "idle-1".

stx commented 4 years ago

@luigi-rosso Thanks for looking into this. Yes, I’ve been using that workaround. The problem is when I want to mix two on-demand plays on top of it. Idle keeps working, but the first play() is stopped when second play() is started.

luigi-rosso commented 4 years ago

I see why it's happening. FlareControls currently removes all the animations that came prior to a fully mixed in one. That's a bug but it has been working that way for so long that I need to spend a little more time with it to figure out what the full impact of fixing that is.

In the meantime, if you want your own FlareControls with a fix, this is the delta: image

Copy/pastable:

if (lastFullyMixed != -1) {
      int removeIndex = 0;
      for (int i = 0; i < lastFullyMixed; i++) {
        if (!_animationLayers[i].animation.isLooping) {
          _animationLayers.removeAt(removeIndex);
        } else {
          // Didn't remove it, nudge index forward
          removeIndex++;
        }
      }
    }

Full gist of the changed file (maybe rename to LoopingFlareControls/looping_flare_controls.dart or something to keep it separate from the main codebase for now): https://gist.github.com/luigi-rosso/a700aabbad8ddc8f64417209889c1b01

stx commented 4 years ago

@luigi-rosso That fix does keep the idle playing, but the much bigger issue is, because there is no workaround using FlareActor that I've found, even with one off animations without interfering layers, playing animation 1 and then 2 will simply stop animation 1 in place. Do you have a solution for that?

luigi-rosso commented 4 years ago

You could completely delete the code that removes animations prior to the fully mixed one (the code just changed above). That’ll keep animations around until they complete. Sorry not at my desk to provide an example. I can do that later today.

stx commented 4 years ago

Yup, that did it. Working perfectly and behaves exactly as I'd expect it now. Thanks a ton!