Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flare controls fix to enable time-based mixing #223

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
66 changes: 43 additions & 23 deletions flare_flutter/lib/flare_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import 'flare.dart';
import 'flare_actor.dart';
import 'flare_controller.dart';

/// [TimedAnimation] holds a reference to [FlareAnimationLayer]s
/// and [currentTime] maintains that layers current animation timeline
/// for mixing purposes
class TimedAnimation {
FlareAnimationLayer layer;
double currentTime; // how many seconds have elapsed
}

/// [FlareControls] is a concrete implementation of the [FlareController].
///
/// This controller will provide some basic functionality, such as
Expand All @@ -15,10 +23,13 @@ class FlareControls extends FlareController {

/// The current [ActorAnimation].
String _animationName;
final double _mixSeconds = 0.1;

/// The [FlareAnimationLayer]s currently active.
final List<FlareAnimationLayer> _animationLayers = [];
/// The [TimedAnimation]s currently active.
final List<TimedAnimation> _animations = [];

/// Used as a reference for each animation layer
/// to stay in sync
double _ticker = 0.0;

/// Called at initialization time, it stores the reference
/// to the current [FlutterActorArtboard].
Expand All @@ -34,15 +45,23 @@ class FlareControls extends FlareController {
/// to the end of the list of currently playing animation layers.
void play(String name, {double mix = 1.0, double mixSeconds = 0.2}) {
_animationName = name;

if (_animationName != null && _artboard != null) {
int layerIndex = _animations.indexWhere((ani) => ani.layer.name == name);
ActorAnimation animation = _artboard.getAnimation(_animationName);
if (animation != null) {
_animationLayers.add(FlareAnimationLayer()

if (animation != null && layerIndex == -1) {
_animations.add(TimedAnimation()
..layer = (FlareAnimationLayer()
..name = _animationName
..animation = animation
..mix = mix
..mixSeconds = mixSeconds);
..mixSeconds = mixSeconds)
..currentTime = mix * mixSeconds);
isActive.value = true;
} else if (layerIndex >= 0) {
/// If we already have reference to this, update the seconds
_animations[layerIndex].layer.mixSeconds = mixSeconds;
}
}
}
Expand All @@ -58,39 +77,40 @@ class FlareControls extends FlareController {
@override
bool advance(FlutterActorArtboard artboard, double elapsed) {
/// List of completed animations during this frame.
List<FlareAnimationLayer> completed = [];
List<TimedAnimation> completed = [];

_ticker += elapsed;

/// This loop will mix all the currently active animation layers so that,
/// if an animation is played on top of the current one, it'll smoothly mix
/// between the two instead of immediately switching to the new one.
for (int i = 0; i < _animationLayers.length; i++) {
FlareAnimationLayer layer = _animationLayers[i];
layer.mix += elapsed;
layer.time += elapsed;
/// between the two instead of immediately switching to the new one.
for (int i = 0; i < _animations.length; i++) {
FlareAnimationLayer layer = _animations[i].layer;
layer.time = _ticker;
_animations[i].currentTime += layer.name == _animationName ? elapsed : -elapsed;
_animations[i].currentTime = max(0.0, min(layer.mixSeconds, _animations[i].currentTime));

double mix = (_mixSeconds == null || _mixSeconds == 0.0)
? 1.0
: min(1.0, layer.mix / _mixSeconds);
layer.mix = max(0.0, min(1.0, _animations[i].currentTime / layer.mixSeconds));

/// Loop the time if needed.
if (layer.animation.isLooping) {
layer.time %= layer.animation.duration;
}

/// Apply the animation with the current mix.
layer.animation.apply(layer.time, _artboard, mix);
layer.animation.apply(layer.time, _artboard, layer.mix);

/// Add (non-looping) finished animations to the list.
if (layer.time > layer.animation.duration) {
completed.add(layer);
/// Axe it after it's finished mixing
if (layer.mix == 0) {
completed.add(_animations[i]);
}
}

/// Notify of the completed animations.
for (final FlareAnimationLayer animation in completed) {
_animationLayers.remove(animation);
onCompleted(animation.name);
for (final TimedAnimation animation in completed) {
_animations.remove(animation);
onCompleted(animation.layer.name);
}
return _animationLayers.isNotEmpty;
return _animations.isNotEmpty;
}
}
3 changes: 1 addition & 2 deletions flare_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ environment:
dependencies:
flutter:
sdk: flutter
flare_dart:
path: ../flare_dart/
flare_dart: ^2.3.4
meta: ^1.0.5
dev_dependencies:
flutter_test:
Expand Down