mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-06-27 10:18:12 +08:00
Adding exit time to state machine.
This commit is contained in:
Binary file not shown.
@ -65,7 +65,8 @@ abstract class StateTransitionBase extends StateMachineLayerComponent {
|
||||
int _duration = durationInitialValue;
|
||||
static const int durationPropertyKey = 158;
|
||||
|
||||
/// Duration of the trasition (mix time) in milliseconds.
|
||||
/// Duration of the trasition (mix time) in milliseconds or percentage (0-100)
|
||||
/// based on flags.
|
||||
int get duration => _duration;
|
||||
|
||||
/// Change the [_duration] field value.
|
||||
@ -82,4 +83,30 @@ abstract class StateTransitionBase extends StateMachineLayerComponent {
|
||||
}
|
||||
|
||||
void durationChanged(int from, int to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// ExitTime field with key 160.
|
||||
static const int exitTimeInitialValue = 0;
|
||||
int _exitTime = exitTimeInitialValue;
|
||||
static const int exitTimePropertyKey = 160;
|
||||
|
||||
/// Duration in milliseconds that must elapse before allowing the state to
|
||||
/// change. If the flags mark this property as being percentage based, the
|
||||
/// value is in 0-100% of the outgoing animation's duration
|
||||
int get exitTime => _exitTime;
|
||||
|
||||
/// Change the [_exitTime] field value.
|
||||
/// [exitTimeChanged] will be invoked only if the field's value has changed.
|
||||
set exitTime(int value) {
|
||||
if (_exitTime == value) {
|
||||
return;
|
||||
}
|
||||
int from = _exitTime;
|
||||
_exitTime = value;
|
||||
if (hasValidated) {
|
||||
exitTimeChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void exitTimeChanged(int from, int to);
|
||||
}
|
||||
|
@ -352,6 +352,11 @@ class RiveCoreContext {
|
||||
object.duration = value;
|
||||
}
|
||||
break;
|
||||
case StateTransitionBase.exitTimePropertyKey:
|
||||
if (object is StateTransitionBase && value is int) {
|
||||
object.exitTime = value;
|
||||
}
|
||||
break;
|
||||
case KeyFrameDoubleBase.valuePropertyKey:
|
||||
if (object is KeyFrameDoubleBase && value is double) {
|
||||
object.value = value;
|
||||
@ -831,6 +836,7 @@ class RiveCoreContext {
|
||||
case StateTransitionBase.stateToIdPropertyKey:
|
||||
case StateTransitionBase.flagsPropertyKey:
|
||||
case StateTransitionBase.durationPropertyKey:
|
||||
case StateTransitionBase.exitTimePropertyKey:
|
||||
case LinearAnimationBase.fpsPropertyKey:
|
||||
case LinearAnimationBase.durationPropertyKey:
|
||||
case LinearAnimationBase.loopValuePropertyKey:
|
||||
@ -980,6 +986,8 @@ class RiveCoreContext {
|
||||
return (object as StateTransitionBase).flags;
|
||||
case StateTransitionBase.durationPropertyKey:
|
||||
return (object as StateTransitionBase).duration;
|
||||
case StateTransitionBase.exitTimePropertyKey:
|
||||
return (object as StateTransitionBase).exitTime;
|
||||
case LinearAnimationBase.fpsPropertyKey:
|
||||
return (object as LinearAnimationBase).fps;
|
||||
case LinearAnimationBase.durationPropertyKey:
|
||||
@ -1255,6 +1263,9 @@ class RiveCoreContext {
|
||||
case StateTransitionBase.durationPropertyKey:
|
||||
(object as StateTransitionBase).duration = value;
|
||||
break;
|
||||
case StateTransitionBase.exitTimePropertyKey:
|
||||
(object as StateTransitionBase).exitTime = value;
|
||||
break;
|
||||
case LinearAnimationBase.fpsPropertyKey:
|
||||
(object as LinearAnimationBase).fps = value;
|
||||
break;
|
||||
|
@ -25,6 +25,10 @@ class LinearAnimation extends LinearAnimationBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
double get startSeconds => (enableWorkArea ? workStart : 0).toDouble() / fps;
|
||||
double get endSeconds =>
|
||||
(enableWorkArea ? workEnd : duration).toDouble() / fps;
|
||||
double get durationSeconds => endSeconds - startSeconds;
|
||||
void apply(double time, {required CoreContext coreContext, double mix = 1}) {
|
||||
for (final keyedObject in _keyedObjects.values) {
|
||||
keyedObject.apply(time, mix, coreContext);
|
||||
|
@ -4,11 +4,15 @@ import 'package:rive/src/rive_core/animation/loop.dart';
|
||||
class LinearAnimationInstance {
|
||||
final LinearAnimation animation;
|
||||
double _time = 0;
|
||||
double _totalTime = 0;
|
||||
double _lastTotalTime = 0;
|
||||
int _direction = 1;
|
||||
bool _didLoop = false;
|
||||
bool get didLoop => _didLoop;
|
||||
double _spilledTime = 0;
|
||||
double get spilledTime => _spilledTime;
|
||||
double get totalTime => _totalTime;
|
||||
double get lastTotalTime => _lastTotalTime;
|
||||
LinearAnimationInstance(this.animation)
|
||||
: _time =
|
||||
(animation.enableWorkArea ? animation.workStart : 0).toDouble() /
|
||||
@ -17,7 +21,9 @@ class LinearAnimationInstance {
|
||||
if (_time == value) {
|
||||
return;
|
||||
}
|
||||
_time = value;
|
||||
var diff = _totalTime - _lastTotalTime;
|
||||
_time = _totalTime = value;
|
||||
_lastTotalTime = _totalTime - diff;
|
||||
_direction = 1;
|
||||
}
|
||||
|
||||
@ -35,7 +41,10 @@ class LinearAnimationInstance {
|
||||
void reset() => _time = startTime;
|
||||
bool get keepGoing => animation.loop != Loop.oneShot || !_didLoop;
|
||||
bool advance(double elapsedSeconds) {
|
||||
_time += elapsedSeconds * animation.speed * _direction;
|
||||
var deltaSeconds = elapsedSeconds * animation.speed * _direction;
|
||||
_lastTotalTime = _totalTime;
|
||||
_totalTime += deltaSeconds;
|
||||
_time += deltaSeconds;
|
||||
double frames = _time * animation.fps;
|
||||
var start = animation.enableWorkArea ? animation.workStart : 0;
|
||||
var end = animation.enableWorkArea ? animation.workEnd : animation.duration;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/animation/animation_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/layer_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/transition_condition.dart';
|
||||
import 'package:rive/src/generated/animation/state_transition_base.dart';
|
||||
@ -24,6 +25,38 @@ class StateTransition extends StateTransitionBase {
|
||||
}
|
||||
|
||||
bool get isDisabled => (flags & StateTransitionFlags.disabled) != 0;
|
||||
bool get pauseOnExit => (flags & StateTransitionFlags.pauseOnExit) != 0;
|
||||
bool get enableExitTime => (flags & StateTransitionFlags.enableExitTime) != 0;
|
||||
double mixTime(LayerState stateFrom) {
|
||||
if (duration == 0) {
|
||||
return 0;
|
||||
}
|
||||
if ((flags & StateTransitionFlags.durationIsPercentage) != 0) {
|
||||
var animationDuration = 0.0;
|
||||
if (stateFrom is AnimationState) {
|
||||
animationDuration = stateFrom.animation?.durationSeconds ?? 0;
|
||||
}
|
||||
return duration / 100 * animationDuration;
|
||||
} else {
|
||||
return duration / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
double exitTimeSeconds(LayerState stateFrom) {
|
||||
if (exitTime == 0) {
|
||||
return 0;
|
||||
}
|
||||
if ((flags & StateTransitionFlags.exitTimeIsPercentage) != 0) {
|
||||
var animationDuration = 0.0;
|
||||
if (stateFrom is AnimationState) {
|
||||
animationDuration = stateFrom.animation?.durationSeconds ?? 0;
|
||||
}
|
||||
return exitTime / 100 * animationDuration;
|
||||
} else {
|
||||
return exitTime / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool import(ImportStack importStack) {
|
||||
var importer =
|
||||
@ -53,5 +86,7 @@ class StateTransition extends StateTransitionBase {
|
||||
@override
|
||||
void durationChanged(int from, int to) {}
|
||||
@override
|
||||
void exitTimeChanged(int from, int to) {}
|
||||
@override
|
||||
void stateToIdChanged(int from, int to) {}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import 'package:rive/src/generated/animation/transition_bool_condition_base.dart
|
||||
export 'package:rive/src/generated/animation/transition_bool_condition_base.dart';
|
||||
|
||||
class TransitionBoolCondition extends TransitionBoolConditionBase {
|
||||
@override
|
||||
bool validate() => super.validate() && (input is StateMachineBool);
|
||||
@override
|
||||
bool evaluate(HashMap<int, dynamic> values) {
|
||||
var boolInput = input as StateMachineBool;
|
||||
|
@ -8,6 +8,8 @@ class TransitionDoubleCondition extends TransitionDoubleConditionBase {
|
||||
@override
|
||||
void valueChanged(double from, double to) {}
|
||||
@override
|
||||
bool validate() => super.validate() && (input is StateMachineDouble);
|
||||
@override
|
||||
bool evaluate(HashMap<int, dynamic> values) {
|
||||
var doubleInput = input as StateMachineDouble;
|
||||
dynamic providedValue = values[input.id];
|
||||
|
@ -4,6 +4,8 @@ import 'package:rive/src/generated/animation/transition_trigger_condition_base.d
|
||||
export 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
|
||||
|
||||
class TransitionTriggerCondition extends TransitionTriggerConditionBase {
|
||||
@override
|
||||
bool validate() => super.validate() && (input is StateMachineTrigger);
|
||||
@override
|
||||
bool evaluate(HashMap<int, dynamic> values) {
|
||||
dynamic providedValue = values[input.id];
|
||||
|
@ -11,6 +11,8 @@ import 'package:rive/src/rive_core/rive_animation_controller.dart';
|
||||
class LayerController {
|
||||
final StateMachineLayer layer;
|
||||
LayerState? _currentState;
|
||||
LayerState? _stateFrom;
|
||||
bool _holdAnimationFrom = false;
|
||||
LinearAnimationInstance? _animationInstanceFrom;
|
||||
StateTransition? _transition;
|
||||
double _mix = 1.0;
|
||||
@ -41,8 +43,10 @@ class LayerController {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (_transition != null && _transition!.duration != 0) {
|
||||
_mix = (_mix + elapsedSeconds / (_transition!.duration / 1000))
|
||||
if (_transition != null &&
|
||||
_stateFrom != null &&
|
||||
_transition!.duration != 0) {
|
||||
_mix = (_mix + elapsedSeconds / _transition!.mixTime(_stateFrom!))
|
||||
.clamp(0, 1)
|
||||
.toDouble();
|
||||
} else {
|
||||
@ -50,7 +54,9 @@ class LayerController {
|
||||
}
|
||||
var keepGoing = _mix != 1;
|
||||
if (_animationInstanceFrom != null && _mix < 1) {
|
||||
if (!_holdAnimationFrom) {
|
||||
_animationInstanceFrom!.advance(elapsedSeconds);
|
||||
}
|
||||
_animationInstanceFrom!.animation.apply(_animationInstanceFrom!.time,
|
||||
mix: 1 - _mix, coreContext: core);
|
||||
}
|
||||
@ -77,6 +83,9 @@ class LayerController {
|
||||
return false;
|
||||
}
|
||||
for (final transition in stateFrom.transitions) {
|
||||
if (transition.isDisabled) {
|
||||
continue;
|
||||
}
|
||||
bool valid = true;
|
||||
for (final condition in transition.conditions) {
|
||||
if (!condition.evaluate(inputValues)) {
|
||||
@ -84,9 +93,32 @@ class LayerController {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid && stateFrom is AnimationState && transition.enableExitTime) {
|
||||
var fromAnimation = stateFrom.animation!;
|
||||
if (_animationInstance != null &&
|
||||
fromAnimation == _animationInstance!.animation) {
|
||||
var lastTime = _animationInstance!.lastTotalTime;
|
||||
var time = _animationInstance!.totalTime;
|
||||
var exitTime = transition.exitTimeSeconds(stateFrom);
|
||||
if (exitTime < fromAnimation.durationSeconds) {
|
||||
exitTime += (lastTime / fromAnimation.durationSeconds).floor() *
|
||||
fromAnimation.durationSeconds;
|
||||
}
|
||||
if (time < exitTime) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (valid && _changeState(transition.stateTo)) {
|
||||
_transition = transition;
|
||||
_stateFrom = stateFrom;
|
||||
if (transition.pauseOnExit &&
|
||||
transition.enableExitTime &&
|
||||
_animationInstance != null) {
|
||||
_animationInstance!.time = transition.exitTimeSeconds(stateFrom);
|
||||
}
|
||||
if (_mix != 0) {
|
||||
_holdAnimationFrom = transition.pauseOnExit;
|
||||
_animationInstanceFrom = _animationInstance;
|
||||
}
|
||||
if (_currentState is AnimationState) {
|
||||
|
@ -1,4 +1,7 @@
|
||||
class StateTransitionFlags {
|
||||
static const int disabled = 1 << 0;
|
||||
static const int durationIsPercentage = 1 << 1;
|
||||
static const int enableExitTime = 1 << 2;
|
||||
static const int exitTimeIsPercentage = 1 << 3;
|
||||
static const int pauseOnExit = 1 << 4;
|
||||
}
|
||||
|
Reference in New Issue
Block a user