diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index 15456e786..faec23182 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -3311,19 +3311,19 @@ class TrackEntry { _bindings.spine_track_entry_set_mix_time(_entry, mixTime); } - /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by AnimationStateData - /// {@link AnimationStateData#getMix(Animation, Animation)} based on the animation before this animation (if any). - ///

+ /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + /// [AnimationStateData.getMix] based on the animation before this animation (if any). + /// /// A mix duration of 0 still mixes out over one frame to provide the track entry being mixed out a chance to revert the /// properties it was animating. A mix duration of 0 can be set at any time to end the mix on the next - /// {@link AnimationState#update(float) update}. - ///

+ /// [AnimationState.update]. + /// /// The mixDuration can be set manually rather than use the value from - /// {@link AnimationStateData#getMix(Animation, Animation)}. In that case, the mixDuration can be set for a new - /// track entry only before {@link AnimationState#update(float)} is first called. + /// [AnimationStateData.getMix]. In that case, the mixDuration can be set for a new + /// track entry only before [AnimationState.update] is first called. ///

- /// When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a delay <= 0, the - /// {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}. If mixDuration is set + /// When using [AnimationState.addAnimation] with a delay <= 0, the + /// [getDelay] is set using the mix duration from the [AnimationStateData]. If mixDuration is set /// afterward, the delay may need to be adjusted. For example: /// entry.delay = entry.previous.getTrackComplete() - entry.mixDuration; double getMixDuration() { @@ -3334,6 +3334,12 @@ class TrackEntry { _bindings.spine_track_entry_set_mix_duration(_entry, mixDuration); } + /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to [MixBlend.replace]. + /// + /// Track entries on track 0 ignore this setting and always use {@link MixBlend#first}. + /// + /// The mixBlend can be set for a new track entry only before [AnimationState.apply] is first + /// called. MixBlend getMixBlend() { return MixBlend.values[_bindings.spine_track_entry_get_mix_blend(_entry)]; } @@ -3342,16 +3348,16 @@ class TrackEntry { _bindings.spine_track_entry_set_mix_blend(_entry, mixBlend.value); } - /// The track entry for the previous animation when mixing from the previous animation to this animation, or NULL if no - /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a double linked list with MixingTo. + /// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no + /// mixing is currently occurring. When mixing from multiple animations, mixingFrom makes up a linked list. TrackEntry? getMixingFrom() { final from = _bindings.spine_track_entry_get_mixing_from(_entry); if (from.address == nullptr.address) return null; return TrackEntry._(from, _state); } - /// The track entry for the next animation when mixing from this animation, or NULL if no mixing is currently occuring. - /// When mixing from multiple animations, MixingTo makes up a double linked list with MixingFrom. + /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is + /// currently occurring. When mixing to multiple animations, mixingTo makes up a linked list. TrackEntry? getMixingTo() { final to = _bindings.spine_track_entry_get_mixing_to(_entry); if (to.address == nullptr.address) return null; @@ -3369,22 +3375,62 @@ class TrackEntry { _bindings.spine_track_entry_reset_rotation_directions(_entry); } + /// If this track entry is non-looping, the track time in seconds when [getAnimationEnd] is reached, or the current + /// [getTrackTime] if it has already been reached. If this track entry is looping, the track time when this + /// animation will reach its next [getAnimationEnd] (the next loop completion). double getTrackComplete() { return _bindings.spine_track_entry_get_track_complete(_entry); } + /// The listener for events generated by this track entry, or null. + /// + /// A track entry returned from [AnimationState.setAnimation] is already the current animation + /// for the track, so the track entry listener will not be called for [EventType.start]. void setListener(AnimationStateListener? listener) { _state._setTrackEntryListener(_entry, listener); } } -enum EventType { start, interrupt, end, complete, dispose, event } +/// The event type passed to [AnimationStateListener] +enum EventType { + /// Emitted when [TrackEntry] has been set as the current entry. [EventType.end] will occur when this entry will no + /// longer be applied. + start, + /// Emitted when another entry has replaced the current entry. This entry may continue being applied for + /// mixing. + interrupt, + /// Emitted when this entry will never be applied again. This only occurs if this entry has previously been set as the + /// current entry ([EventType.start] was emitted). + end, + /// Emitted every time the current entry's animation completes a loop. This may occur during mixing (after + /// [EventType.interrupted] is emitted). + /// + /// If [TrackEntry.getMixingTo] of the entry reported by the event is not null, the entry is mixing out (it is not the current entry). + /// + /// Because this event is triggered at the end of [AnimationState.apply], any animations set in response to + /// the event won't be applied until the next time the [AnimationState] is applied. + complete, + /// Emitted when this entry will be disposed. This may occur without the entry ever being set as the current entry. + /// + /// References to the entry should not be kept after dispose is called, as it may be destroyed or reused. + dispose, + /// Invoked when the current entry's animation triggers an event. This may occur during mixing (after + /// [EventType.interrupt] is emitted), see [TrackEntry.getEventThreshold]. + /// + /// Because this event is triggered at the end of [AnimationState.apply], any animations set in response to + /// the event won't be applied until the next time the [AnimationState] is applied. + event +} +/// Stores the setup pose values for an [Event]. +/// +/// See Events in the Spine User Guide. class EventData { final spine_event_data _data; EventData._(this._data); + /// The name of the event, which is unique across all events in the skeleton. String getName() { final Pointer value = _bindings.spine_event_data_get_name(_data).cast(); return value.toDartString(); @@ -3439,15 +3485,21 @@ class EventData { } } +/// Stores the current pose values for an {@link Event}. +/// +/// See [AnimationStateListener], [EventType.event], and +/// Events in the Spine User Guide. class Event { final spine_event _event; Event._(this._event); + /// The events's setup pose data. EventData getData() { return EventData._(_bindings.spine_event_get_data(_event)); } + /// The animation time this event was keyed. double getTime() { return _bindings.spine_event_get_time(_event); } @@ -3496,13 +3548,22 @@ class Event { } } +/// The callback to implement for receiving [TrackEntry] events. It is always safe to call [AnimationState] methods when receiving +/// events. +/// +/// TrackEntry events are collected during [AnimationState.update] and [AnimationState.apply] and +/// fired only after those methods are finished. +/// +/// See [TrackEntry.setListener] and [AnimationState.setListener]. typedef AnimationStateListener = void Function(EventType type, TrackEntry entry, Event? event); +/// Stores mix (crossfade) durations to be applied when {@link AnimationState} animations are changed. class AnimationStateData { final spine_animation_state_data _data; AnimationStateData._(this._data); + /// The SkeletonData to look up animations when they are specified by name. SkeletonData getSkeletonData() { return SkeletonData._(_bindings.spine_animation_state_data_get_skeleton_data(_data)); } @@ -3515,6 +3576,9 @@ class AnimationStateData { _bindings.spine_animation_state_data_set_default_mix(_data, defaultMix); } + /// Sets a mix duration by animation name. + /// + /// See [setMix]. void setMixByName(String fromName, String toName, double duration) { final fromNative = fromName.toNativeUtf8(allocator: _allocator); final toNative = toName.toNativeUtf8(allocator: _allocator); @@ -3523,6 +3587,8 @@ class AnimationStateData { _allocator.free(toNative); } + /// Returns the mix duration to use when changing from the specified animation to the other, or the [getDefaultMix] if + /// no mix duration has been set. double getMixByName(String fromName, String toName) { final fromNative = fromName.toNativeUtf8(allocator: _allocator); final toNative = toName.toNativeUtf8(allocator: _allocator); @@ -3532,19 +3598,29 @@ class AnimationStateData { return duration; } - void setMix(Animation from, Animation to, double duration) { + /// Sets the mix duration when changing from the specified animation to the other. + /// + /// See [TrackEntry.mixDuration]. + Future setMix(Animation from, Animation to, double duration) async { _bindings.spine_animation_state_data_set_mix(_data, from._animation, to._animation, duration); } + /// Returns the mix duration to use when changing from the specified animation to the other, or the [getDefaultMix] if + /// no mix duration has been set. double getMix(Animation from, Animation to) { return _bindings.spine_animation_state_data_get_mix(_data, from._animation, to._animation); } + /// Removes all mix durations. void clear() { _bindings.spine_animation_state_data_clear(_data); } } +/// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies +/// multiple animations on top of each other (layering). +/// +/// See Applying Animations in the Spine Runtimes Guide. class AnimationState { final spine_animation_state _state; final spine_animation_state_events _events; @@ -3561,8 +3637,7 @@ class AnimationState { } } - /// Increments the track entry times, setting queued animations as current if needed - /// @param delta delta time + /// Increments each track entry [TrackEntry.getTrackTime], setting queued animations as current if needed. void update(double delta) { _bindings.spine_animation_state_update(_state, delta); @@ -3608,33 +3683,33 @@ class AnimationState { _bindings.spine_animation_state_events_reset(_events); } - /// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the - /// animation state can be applied to multiple skeletons to pose them identically. + /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple + /// skeletons to pose them identically. + /// + /// Returns true if any animations were applied. void apply(Skeleton skeleton) { _bindings.spine_animation_state_apply(_state, skeleton._skeleton); } - /// Removes all animations from all tracks, leaving skeletons in their previous pose. - /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. + /// Removes all animations from all tracks, leaving skeletons in their current pose. + /// + /// It may be desired to use [setEmptyAnimations] to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. void clearTracks() { _bindings.spine_animation_state_clear_tracks(_state); } - /// Removes all animations from the tracks, leaving skeletons in their previous pose. - /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. + /// Removes all animations from the track, leaving skeletons in their current pose. + /// + /// It may be desired to use [setEmptyAnimations] to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. void clearTrack(int trackIndex) { _bindings.spine_animation_state_clear_track(_state, trackIndex); } - /// Sets the current animation for a track, discarding any queued animations. - /// @param loop If true, the animation will repeat. - /// If false, it will not, instead its last frame is applied if played beyond its duration. - /// In either case TrackEntry.TrackEnd determines when the track is cleared. - /// @return - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after AnimationState.Dispose. + /// Sets an animation by name. + /// + /// See [setAnimation]. TrackEntry setAnimationByName(int trackIndex, String animationName, bool loop) { final animation = animationName.toNativeUtf8(allocator: _allocator); final entry = _bindings.spine_animation_state_set_animation_by_name(_state, trackIndex, animation.cast(), loop ? -1 : 0); @@ -3643,20 +3718,23 @@ class AnimationState { return TrackEntry._(entry, this); } + /// Sets the current [animation] for a track at [trackIndex], discarding any queued animations. If the formerly current track entry was never + /// applied to a skeleton, it is replaced (not mixed from). + /// + /// If [loop] is true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its + /// duration. In either case [TrackEntry.getTrackEnd] determines when the track is cleared. + /// + /// Returns a track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the [EventType.dispose] event occurs. TrackEntry setAnimation(int trackIndex, Animation animation, bool loop) { final entry = _bindings.spine_animation_state_set_animation(_state, trackIndex, animation._animation, loop ? -1 : 0); if (entry.address == nullptr.address) throw Exception("Couldn't set animation ${animation.getName()}"); return TrackEntry._(entry, this); } - /// Adds an animation to be played delay seconds after the current or last queued animation - /// for a track. If the track is empty, it is equivalent to calling setAnimation. - /// @param delay - /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation - /// duration of the previous track minus any mix duration plus the negative delay. + /// Queues an animation by name. /// - /// @return A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after AnimationState.Dispose + /// See [addAnimation]. TrackEntry addAnimationByName(int trackIndex, String animationName, bool loop, double delay) { final animation = animationName.toNativeUtf8(allocator: _allocator); final entry = _bindings.spine_animation_state_add_animation_by_name(_state, trackIndex, animation.cast(), loop ? -1 : 0, delay); @@ -3665,47 +3743,82 @@ class AnimationState { return TrackEntry._(entry, this); } + /// Adds an [animation] to be played after the current or last queued animation for a track at [trackIndex]. If the track is empty, it is + /// equivalent to calling [setAnimation]. + /// + /// If [delay] > 0, sets [TrackEntry.getDelay]. If [delay] <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration (from the [AnimationStateData]) plus the specified delay (ie the mix + /// ends at (delay = 0) or before (delay < 0) the previous track entry duration). If the + /// previous entry is looping, its next loop completion is used instead of its duration. + /// + /// Returns a track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the [EventType.dispose] event occurs. TrackEntry addAnimation(int trackIndex, Animation animation, bool loop, double delay) { final entry = _bindings.spine_animation_state_add_animation(_state, trackIndex, animation._animation, loop ? -1 : 0, delay); if (entry.address == nullptr.address) throw Exception("Couldn't add animation ${animation.getName()}"); return TrackEntry._(entry, this); } - /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + /// Sets an empty animation for a track at [trackIndex], discarding any queued animations, and sets the track entry's + /// [TrackEntry.getMixDuration] to [mixDuration]. An empty animation has no timelines and serves as a placeholder for mixing in or out. + /// + /// Mixing out is done by setting an empty animation with a mix duration using either [setEmptyAnimation], + /// [setEmptyAnimations], or [addEmptyAnimation]. Mixing to an empty animation causes + /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation + /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of + /// 0 still mixes out over one frame. + /// + /// Mixing in is done by first setting an empty animation, then adding an animation using + /// [addAnimation] with the desired delay (an empty animation has a duration of 0) and on + /// the returned track entry, set the [TrackEntry.setMixDuration]. Mixing from an empty animation causes the new + /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value + /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new + /// animation. TrackEntry setEmptyAnimation(int trackIndex, double mixDuration) { final entry = _bindings.spine_animation_state_set_empty_animation(_state, trackIndex, mixDuration); return TrackEntry._(entry, this); } - /// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the - /// specified mix duration. - /// @return - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after AnimationState.Dispose. + /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's + /// [TrackEntry.getMixDuration]. If the track is empty, it is equivalent to calling + /// [setEmptyAnimation]. /// - /// @param trackIndex Track number. - /// @param mixDuration Mix duration. - /// @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation - /// duration of the previous track minus any mix duration plus the negative delay. + /// See [setEmptyAnimation]. + /// + /// If [delay] > 0, sets [TrackEntry.getDelay]. If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration plus the specified delay (ie the mix ends at (delay = 0) or + /// before (delay < 0) the previous track entry duration). If the previous entry is looping, its next + /// loop completion is used instead of its duration. + /// + /// Returns a track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the [EventType.dispose] event occurs. TrackEntry addEmptyAnimation(int trackIndex, double mixDuration, double delay) { final entry = _bindings.spine_animation_state_add_empty_animation(_state, trackIndex, mixDuration, delay); return TrackEntry._(entry, this); } + /// Returns the track entry for the animation currently playing on the track, or null if no animation is currently playing. TrackEntry? getCurrent(int trackIndex) { final entry = _bindings.spine_animation_state_get_current(_state, trackIndex); if (entry.address == nullptr.address) return null; return TrackEntry._(entry, this); } + /// Returns the number of tracks that have animations queued. int getNumTracks() { return _bindings.spine_animation_state_get_num_tracks(_state); } - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix + /// duration. void setEmptyAnimations(double mixDuration) { _bindings.spine_animation_state_set_empty_animations(_state, mixDuration); } + /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower + /// or faster. Defaults to 1. + /// + /// See [TrackEntry.getTimeScale] for affecting a single animation. double getTimeScale() { return _bindings.spine_animation_state_get_time_scale(_state); } @@ -3714,10 +3827,15 @@ class AnimationState { _bindings.spine_animation_state_set_time_scale(_state, timeScale); } + /// The [AnimationStateData] to look up mix durations. AnimationStateData getData() { return AnimationStateData._(_bindings.spine_animation_state_get_data(_state)); } + /// The listener for events generated for all tracks managed by the AnimationState, or null. + /// + /// A track entry returned from [setAnimation] is already the current animation + /// for the track, so the track entry listener will not be called for [EventType.start]. void setListener(AnimationStateListener? listener) { _stateListener = listener; } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 958aaab77..a7d12e3b8 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -847,7 +847,7 @@ public class AnimationState { this.timeScale = timeScale; } - /** The AnimationStateData to look up mix durations. */ + /** The {@link AnimationStateData} to look up mix durations. */ public AnimationStateData getData () { return data; } @@ -1199,13 +1199,13 @@ public class AnimationState { } /** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no - * mixing is currently occuring. When mixing from multiple animations, mixingFrom makes up a linked list. */ + * mixing is currently occurring. When mixing from multiple animations, mixingFrom makes up a linked list. */ public @Null TrackEntry getMixingFrom () { return mixingFrom; } /** The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is - * currently occuring. When mixing to multiple animations, mixingTo makes up a linked list. */ + * currently occurring. When mixing to multiple animations, mixingTo makes up a linked list. */ public @Null TrackEntry getMixingTo () { return mixingTo; }